-
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
RFC - Add an owning "borrowed" pointer type &move
#1617
Changes from all commits
c9ebd81
10ae3c2
e4b6898
1a37683
44ab79f
c914f40
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,103 @@ | ||
- Feature Name: `move_pointer` | ||
- Start Date: 2016-05-12 | ||
- RFC PR: (leave this empty) | ||
- Rust Issue: (leave this empty) | ||
|
||
# Summary | ||
[summary]: #summary | ||
|
||
Introduce a new pointer type `&move` that logically owns the pointed-to data, but does not control the backing memory. Also introduces the DerefMove trait to provide access to such a pointer. | ||
|
||
# Motivation | ||
[motivation]: #motivation | ||
|
||
This provides an elegant solution to passing DSTs by value, allowing `Box<FnOnce>` to work. It also will allow other usecases where trait methods should take self by "value". | ||
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. The RFC doesn’t seem to actually specify how 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 the idea. I don't think there's any problems with that (as the trait is still unstable). I can add it as an appendix to the RFC document. 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. The compiler can implement by-value |
||
|
||
# Detailed design | ||
[design]: #detailed-design | ||
|
||
- Add a new pointer type `&move T` | ||
- This pointer will get the same lifetime rules as `&` and `&mut` (such that it cannot outlive the allocation) | ||
- but, it will allow the value to become invalid before the allocation does. | ||
- Deref coercions as per RFC #241 apply. | ||
- Variables of type `&move T` will only allow mutating `T` if they themselves are `mut` | ||
- Add a new unary operation `&move <value>`. With a special case such that `&move |x| x` parses as a move closure, requiring parentheses to parse as an owned pointer to a closure. | ||
- This precedence can be implemented by ignoring the `move` when parsing unary operators if it is followed by a `|` or `||` token. | ||
- Add a new operator trait `DerefMove` to allow smart pointer types to return owned pointers to their contained data. | ||
- A type that implements `DerefMove` cannot implement `Drop` (as `DerefMove` provides equivalent functinality) | ||
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.
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.
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. Does Imagine that fn main() {
let x = Box::new(0);
// I never moved out of the box, so it gets leaked here?
} 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. It provides the same functionality as Drop. See the example in the RFC.... that said, I'm not sure how dropping of non-moved members would be handled. |
||
|
||
```rust | ||
trait DerefMove: DerefMut | ||
{ | ||
/// Return an owned pointer to inner data | ||
fn deref_move(&mut self) -> &move Self::Target; | ||
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 could very well be wrong, but shouldn’t this take let x = Box::new(...);
{
drop(*x.deref_move());
}
// use x (even though its contents have been destroyed!) 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. Good point. I suppose making it unsafe is the cop-out option (which semi-fits, because you'd need unsafe code to implement it). It can't really be &own, because that "moves" the container into the 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 is the crux of the problem. We need typestate and an associated "empty container" type to do this correctly. 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. Hmm I have an idea where 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 teaser: "type state" in my parlance would allow for something like: fn foo(arg: {'lifetime, BeforeType, DuringType, AfterType}, ...) -> ...; e.g. fn take_ref<'a, T>(self: {'a, T, MutBorrowed<T>, T}) -> &'a mut T;
fn take_mut_ref<'a, T>(self: {'a, T, Aliased<T>, T}) -> &'a T; Now suppose there is some fn move_in<'unused, T>(self: {'unused, Uninit<T>, Whatever, T}, T) -> ();
fn move_out<'unused, T>(self: {'unused, T, Whatever, Uninit<T>}) -> T; Now suppose that fn take_move_ref<'a, T>(self: {'a, T, MutBorrowed<T>, T::Toggle}) -> &'a move T;
// Type App
fn take_move_ref::<T::Toggle>: fn (self: {'a, T::Toggle, MutBorrowed<T::Toggle>, T::Toggle::Toggle}) -> &'a move T::Toggle;
// Eliminate ::Toggle::Toggle
fn take_move_ref::<T::Toggle>: fn (self: {'a, T::Toggle, MutBorrowed<T::Toggle>, T}) -> &'a move T::Toggle;
type OutPtr<'a, T> = & 'a move T::Toggle; And lo 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 re-borrowing: fn reborrow_mut<'a: 'b, T>(self: {'b, &'a mut T, &'a mut MutBorrowed<T>, &'a mut T}) -> &'b mut T;
fn reborrow_move<'a: 'b, T>(self: {'b, &'a move T, &'a move MutBorrowed<T>, &'a move T::Toggle}) -> &'b move T; and for the original motivation: struct Box<T>(&'static move T);
type DeadBox<T>(&'static move Unwrap<T>);
fn straight_outta_box<'a, T>(self: {'a , Box<T>, Box<MutBorrowed<T>>, DeadBox<T>}) -> &'a move T {
reborrow_move(self.0)
} 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. And finally, dropping, which is very easy: impl<T> Drop for Box<T>
{
fn drop_interior(arg: &move Interior<Box<T>>) {
let x: &move T = move_out(arg).0;
// arg: OutPtr<Box<T>> // no-op to drop
drop(reborrow_move(x));
// x: OutPtr<T> // we can still use it (no-op to drop, too)
let ptr: *u8 = x as _;
unsafe {
heap::deallocate(ptr, mem::size_of::<T>(), mem::align_of::<T>());
}
}
} Dropping 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. @glaebhoerl pinging you because of our shared interest in these things. 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. Ok, in hindsight the Otherwise I do think this stuff works, though granted its a major addition to the language. |
||
/// Equivalent to `Drop::drop` except that the destructor for `Self::Target` is not called | ||
fn deallocate(&mut self); | ||
} | ||
``` | ||
|
||
When an owned pointer is dropped (without having been moved out of), the destructor for the contained data is called (unlike `&mut` pointers, which are just borrows). The backing memory for this pointer is not freed until a point after the `&move` is dropped (likely either at the end of the statement, or at the end of the owning block). | ||
|
||
For example, the following code moves out of a `Box<T>` into a `&move T` and passes it to a function | ||
```rust | ||
fn takes_move(val: &move SomeStruct) { | ||
// ... | ||
} | ||
fn main() { | ||
let val = Box::new( SomeStruct::new() ); | ||
takes_move( &move val ); | ||
println!("Hello"); | ||
} | ||
``` | ||
This becomes the following operations | ||
```rust | ||
fn main() { | ||
let val = Box::new( SomeStruct::new() ); | ||
takes_move( DerefMove::deref_move(&mut val) ); | ||
DerefMove::deallocate(&mut val); | ||
println!("Hello"); | ||
} | ||
``` | ||
|
||
|
||
# Drawbacks | ||
[drawbacks]: #drawbacks | ||
|
||
- Adding a new pointer type to the language is a large change | ||
|
||
# Alternatives | ||
[alternatives]: #alternatives | ||
|
||
- Previous discussions have used `&own` as the pointer type | ||
- This name is far closer to the actual nature of the pointer. | ||
- But, since `own` is not a reserved word, such a change would be breaking. | ||
|
||
# Unresolved questions | ||
[unresolved]: #unresolved-questions | ||
|
||
- Potential interactions of what happens when a `&move` is stored. | ||
- If a `&move` is stored in the current scope, when is the original storage freed? | ||
- If a `&move` isn't stored, is the storage freed right then, or when it would have otherwise gone out of scope? | ||
|
||
- `IndexMove` trait to handle moving out of collection types in a similar way to `DerefMove` | ||
- Should (can?) the `box` destructuring pattern be implemented using `DerefMove`? | ||
- This would be a larger RFC specifying the `box` pattern using the `Deref` family of traits | ||
|
||
|
||
# Appendix: Implementations of `DerefMove` | ||
```rust | ||
impl<T: ?Sized> DerefMove for Box<T> | ||
{ | ||
fn deref_move(&mut self) -> &move Self::Target { | ||
unsafe { | ||
&move *(self.0) | ||
} | ||
} | ||
fn deallocate(&mut self) { | ||
unsafe { | ||
heap::deallocate(self.0, mem::size_of_val(&*self.0), mem::align_of_val(&*self.0)); | ||
} | ||
} | ||
} | ||
``` | ||
|
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.
What does 'logically owns' mean?
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.
That the pointer owns the pointed-to data, and is responsible for calling the destructor on it (and can be moved out of)