-
Notifications
You must be signed in to change notification settings - Fork 1.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
&move, DerefMove, DerefPure and box patterns #1646
Changes from 2 commits
5d68cad
cc6b0be
b25139b
33b267a
6d3bf18
a7f4bbb
598c926
64748e4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,223 @@ | ||
- Feature Name: missing_derefs | ||
- Start Date: 2016-06-09 | ||
- RFC PR: (leave this empty) | ||
- Rust Issue: (leave this empty) | ||
|
||
# Summary | ||
[summary]: #summary | ||
|
||
Add `&move` pointers, the `DerefMove` trait, and the unsafe | ||
`DerefPure` traits. Allow using `DerefPure` derefs in lvalues. | ||
|
||
# Motivation | ||
[motivation]: #motivation | ||
|
||
Rust's `Box` has a few features that are not implementable by library | ||
traits: it is possible to match on `Box` with box patterns, and to | ||
move out of it. | ||
|
||
User-defined types also want to make use of these features. | ||
|
||
Also, it is not possible to use pattern matching on structures that | ||
contain smart pointers. We would want this to be possible. | ||
|
||
# Detailed design | ||
[design]: #detailed-design | ||
|
||
## DerefPure | ||
|
||
Add a `DerefPure` trait: | ||
```Rust | ||
pub unsafe trait DerefPure : Deref {} | ||
``` | ||
|
||
Implmenenting the `DerefPure` trait tells the compiler that dereferences | ||
of the type it is implemented for behave like dereferences of normal | ||
pointers - as long as the receiver is borrowed, the compiler can merge, | ||
move and remove calls to the `Deref` methods, and the returned pointer | ||
will stay the same. | ||
|
||
Also, the methods must not panic and (if `DerefMove` is implemented) may | ||
be called on a partially-initialized value. | ||
|
||
If a type implements `DerefPure`, then user-defined dereferences of it | ||
are implemented with a `deref` lvalue projection as if they were a built-in | ||
pointer. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This paragraph is confusing to me. I guess it's talking about how we would treat a "deref-pure" deref in MIR? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. (If so, I am not sure what I think -- I understand why one would want to do this, but it seems to be backing away from a big advantage of MIR, that it desugars away implicit method calls of this kind. For example -- what happens if such a call panics (clearly it should not, but still)? Perhaps we could initially use an lvalue for the purposes of borrowck and then lower down further as we prepare for optimization. But I guess that might itself inhibit optimizations we want to enable, if they reason about lvalues. Have to think on this. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If such a call panics, that is UB. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think that this should trip an assertion in (at least) debug builds. |
||
|
||
Types implementing `DerefPure` can be used in `box` patterns. This works | ||
like all the other reference patterns. For example, if `Vec` implements | ||
`DerefPure` and `BasicBlockData.statements` is a `Vec`: | ||
|
||
```Rust | ||
match self.basic_blocks[*start] { | ||
BasicBlockData { | ||
statements: box [], | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This would certainly be nice, but the use of the keyword In retrospect, I wish we had a general "deref" pattern (perhaps one that we also used with references) that was not tied to the (The syntax here is fairly orthogonal to the RFC; just thinking out loud. :) |
||
terminator: ref mut terminator @ Some(Terminator { | ||
kind: TerminatorKind::Goto { .. }, .. | ||
}), .. | ||
} => { /* .. */ } | ||
_ => return | ||
}; | ||
``` | ||
|
||
## &move | ||
|
||
Add a new mutability `move`. `&move` references are references that own their | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This isn't a new mutability. (mutable) Local variables already have this mutability. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Local variables are currently special-cased. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That doesn't mean it doesn't exist. I believe There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That's a matter of how you structure the specification - bikeshedding. |
||
contents, but not the memory they refer to. | ||
|
||
When parsing a `move` closure, `&move |..` is parsed as `& (move |..` - | ||
as creating a `move` closure and taking an immutable reference to it, rather | ||
than creating a non-moving closure and taking an `&move` reference to it. Of | ||
course, you can force the other choice by explicit parentheses - `&move (|..`. | ||
|
||
Unlike some other proposals, the [RFC1214] rules remain the same - a | ||
`&'a move T` reference requires that `T: 'a`. We may want to relax these | ||
rules. | ||
|
||
`&move` references are tracked by the move checker just like ordinary | ||
values. They are linear - when they are dropped, their unmoved contents | ||
are dropped. It is possible to initialize/reinitialize them just like normal | ||
variables. | ||
|
||
Outside of the move checker, `&move` references always have valid contents. | ||
If you want to create a temporary uninitialized `&move` reference, you can | ||
use `mem::forget`: | ||
|
||
```Rust | ||
unsafe fn move_val_init_from_closure<T, F: FnOnce() -> T>(p: *mut T, f: F) | ||
{ | ||
let ptr = &move *p; | ||
mem::forget(*ptr); | ||
*ptr = f(); // if `f` panics, `*ptr` is not dropped. | ||
} | ||
``` | ||
|
||
An `&move x.y` borrow, unlike the other borrows, actually moves out of | ||
`x.y`. This applies to all borrows, including implicit reborrows. I think | ||
this would make implicit reborrows useless, but it is the consequence of | ||
the rules. | ||
|
||
Of course, it is possible to borrow `&move` references as either `&` or | ||
`&mut`, and not possible to borrow `&` or `&mut` references as `&move`. | ||
|
||
## DerefMove | ||
|
||
This allows moving out of user-defined types. | ||
|
||
Add a `DerefMove` trait: | ||
```Rust | ||
pub trait DerefMove: DerefMut + DerefPure { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For the sake of discussion, what if I want to implement There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What do you mean by that? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nevermind. I was trying to come up with an example where one might what to implement |
||
fn deref_move(&mut self) -> &move Self::Target; | ||
} | ||
``` | ||
|
||
The `DerefMove` trait can't be called directly, in the same manner | ||
as `Drop` and for exactly the same reason - otherwise, this | ||
would be possible: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why isn't the argument to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Because then it would drop There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If that's not what the implementation of Permission wise, the Am I missing something? EDIT: Notice that your example for why There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ping @Ericson2314 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You still won't be able to call Well okay, it will only result in leaks, but that kind of random leaking feels bad.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @arielb1 That's equivalent to calling I feel like There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. DerefMove needs typestate to be safe and expressive. |
||
|
||
```Rust | ||
fn example<T>(data: T) -> T { | ||
let b = Box::new(data); | ||
drop(b.deref_move()); | ||
*b // would return dropped data | ||
} | ||
``` | ||
|
||
It is also restricted in the same manner as `Drop` with regards to | ||
implementations and dropck. Of course, a type is allowed to implement | ||
both `Drop` and `DerefMove` - `Box` implements them both. | ||
|
||
If a type implements `DerefMove`, then the move checker treats it | ||
as a tree: | ||
|
||
x | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This does not render well in the markdown... |
||
- *x | ||
|
||
It is not possible to move out of the ordinary fields of such a | ||
type, similarly to types implementing `Drop`. | ||
|
||
When such a type is dropped, `*x` (aka `x.deref_move()`) is dropped | ||
first if it was not moved from already, similarly to `Box` today. Then | ||
the normal destructor and the destructors of the fields are called. | ||
|
||
This means that `Vec<T>` can be implemented as | ||
|
||
```Rust | ||
pub struct Vec<T> { | ||
buf: RawVec<T>, | ||
len: usize, | ||
} | ||
|
||
impl<T> ops::Deref for Vec<T> { | ||
type Target = [T]; | ||
|
||
fn deref(&self) -> &[T] { | ||
unsafe { | ||
let p = self.buf.ptr(); | ||
assume(!p.is_null()); | ||
slice::from_raw_parts(p, self.len) | ||
} | ||
} | ||
} | ||
|
||
impl<T> ops::DerefMut for Vec<T> { | ||
/* id. */ | ||
} | ||
|
||
impl<T> ops::DerefMove for Vec<T> { | ||
#[unsafe_destructor_blind_to_params] | ||
fn deref_move(&mut self) -> &move [T] { | ||
unsafe { | ||
let p = self.buf.ptr(); | ||
assume(!p.is_null()); | ||
slice::from_raw_parts_move(p, self.len) | ||
} | ||
} | ||
} | ||
|
||
unsafe impl<T> ops::DerefPure for Vec<T> {} | ||
|
||
// no `Drop` impl is needed - `RawVec` handles | ||
// that | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A type is allowed to implement both I've copied some text below that I think does imply this is the case. If so, it would be good to have an example of that (even an artificial one that just calls
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sure it is. |
||
``` | ||
|
||
# Drawbacks | ||
[drawbacks]: #drawbacks | ||
|
||
The new mutability kind adds a significant amount of complexity to the | ||
middle of the user-visible type-system. I think the move checker already | ||
supports most of that complexity, but there probably will be unexpected | ||
problems. | ||
|
||
There may be some way to have the entire thing safe. However, all proposals | ||
that I have seen were very complicated. | ||
|
||
# Alternatives | ||
[alternatives]: #alternatives | ||
|
||
We may want to relax the [RFC1214] rules to allow `&'static move T` as an | ||
equivalent to `Unique<T>`. | ||
|
||
Add more features of the move checker to the type-system, e.g. strongly | ||
linear `&out`. That is quite complex, and requires more considerations | ||
wrt. panics. | ||
|
||
# Unresolved questions | ||
[unresolved]: #unresolved-questions | ||
|
||
How to formalize the requirements for `DerefPure`? | ||
|
||
Are there any issues with implementing `&move` lvalues "just like other lvalues"? | ||
|
||
How do we do exhaustiveness checking on `box` patterns if there are also | ||
normal patterns? For example, how do we discover that the box pattern is | ||
useless here: | ||
|
||
```Rust | ||
match x: Rc<Option<_>> { | ||
Rc { .. } => {} | ||
box None => {}, | ||
} | ||
``` | ||
|
||
[RFC1214]: https://github.com/rust-lang/rfcs/blob/master/text/1214-projections-lifetimes-and-wf.md |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be nice to have some examples of this, or at least something a bit more concrete
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have the
Vec
example