-
Notifications
You must be signed in to change notification settings - Fork 1.6k
Add &own T
#965
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
Closed
Closed
Add &own T
#965
Changes from all commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,385 @@ | ||
- Feature Name: own | ||
- Start Date: Tue Mar 10 23:39:14 CET 2015 | ||
- RFC PR: (leave this empty) | ||
- Rust Issue: (leave this empty) | ||
|
||
# Summary | ||
|
||
Add reference and slice types that take ownership of the objects they reference. | ||
|
||
# Examples | ||
|
||
```rust | ||
fn f() { | ||
let x: String = String::new(); | ||
let x_ref: &own String = &own x; | ||
let y: String = *x_ref; | ||
} | ||
|
||
fn g() { | ||
let xs: [String; 2] = [String::new(), String::new()]; | ||
let x_refs: &own [String] = &own xs[..]; | ||
let ys: Vec<String> = x_refs.into_iter().collect(); | ||
} | ||
``` | ||
|
||
# Motivation | ||
|
||
Today it is not easily possible to express the idea of a reference that you can | ||
move out of. It can be emulated with enums: | ||
|
||
```rust | ||
fn f() { | ||
let x: Option<String> = Some(String::new()); | ||
let x_ref: &mut Option<String> = &mut x; | ||
let y: String = x_ref.take().unwrap(); | ||
} | ||
``` | ||
|
||
There are some disadvantages related to performance and ergonomics: | ||
|
||
- Having to use `Some(variable)` to create a movable reference is inconvenient. | ||
- `&mut Option<T>` is uglier than `&own T`. | ||
- In the code above, the destructors of both `x` and `y` will have to run even | ||
though the `String` has been moved out of `x`. | ||
- `&mut Option<T>` is not safe in the way references are safe in Rust. A `&T` | ||
always points to a valid `T`. A `&mut Option<T>` can point to `None`. The | ||
receiver either has to deal with panics or check (and handle) the presence of | ||
`None`. (Similar to languages such as C# where you can get | ||
`NullReferenceExceptions`.) | ||
|
||
Another problem are traits. The following is currently not possible: | ||
|
||
```rust | ||
trait GivesYouAString { | ||
fn string(self) -> String; | ||
} | ||
|
||
impl GivesYouAString for String { | ||
fn string(self) -> String { self } | ||
} | ||
|
||
fn f() { | ||
let x = String::new(); | ||
let y = &mut x as &mut GivesYouAString; // not object safe | ||
let z = y.string(); // cannot move out of mutable reference | ||
} | ||
``` | ||
|
||
With `&own` this would be easy: | ||
|
||
```rust | ||
trait GivesYouAString { | ||
fn string(&own self) -> String; | ||
} | ||
|
||
impl GivesYouAString for String { | ||
fn string(&own self) -> String { *self } | ||
} | ||
|
||
fn f() { | ||
let x = String::new(); | ||
let y = &own x as &own GivesYouAString; | ||
let z = y.string(); | ||
} | ||
``` | ||
|
||
Right now you can work around this by using: | ||
|
||
```rust | ||
impl GivesYouAString for String { | ||
fn string(self: Box<Self>) -> String { *self } | ||
} | ||
|
||
// ... | ||
|
||
let y = Box::new(x) as Box<GivesYouAString>; | ||
let z = y.string(); | ||
} | ||
``` | ||
|
||
but this requires an additional allocation. | ||
|
||
A practical use case of this is the `Any` trait. With move semantics we can pass | ||
an arbitrary type to a function and consume the contained object: | ||
|
||
```rust | ||
fn append_hello_world(x: &own Any) -> String { | ||
if x.is::<String>() { | ||
x.downcast_move::<String>().unwrap().push("hello world") | ||
} else if x.is::<i32>() { | ||
x.downcast_move::<i32>().unwrap().to_string().push("hello world") | ||
} else { | ||
String::new("hello world") | ||
} | ||
} | ||
``` | ||
|
||
---- | ||
|
||
For slices the situation is somewhat worse. To move out of a slice, the user has | ||
to replace every element in the slice by `Option<T>`. If the size of the slice | ||
is unknown at compile time, then this requires a `Vec<Option<T>>` allocation. | ||
Combining all of these aspects, one might as well just pass a `Vec<T>` directly. | ||
|
||
With `&own` slices and pointers it's quite easy to implement variadic functions | ||
that take arbitrary, type-safe, arguments. | ||
|
||
```rust | ||
/// Collects all of the arguments that are strings into a vector. | ||
fn collect_strings(args: &own [&own Any]) -> Vec<String> { | ||
let mut strings = vec!(); | ||
for arg in args.into_iter() { | ||
if arg.is::<String>() { | ||
let string = x.downcast_move::<String>().unwrap(); | ||
strings.push(string); | ||
} | ||
} | ||
strings | ||
} | ||
``` | ||
|
||
which can be used like this: | ||
|
||
```rust | ||
fn f() { | ||
let arg1 = String::new(); | ||
let arg2 = 1u32; | ||
let arg3 = String::new(); | ||
let strings = collect_strings(&own [&own arg1, &own arg2, &own arg3][..]); | ||
} | ||
``` | ||
|
||
--- | ||
|
||
Even without `&own` slices it is possible to implement variadic functions with | ||
a little bit of compiler magic: | ||
|
||
```rust | ||
fn f(args: ..&own Any) { | ||
// args is an anonymous type that implements Iterator<Item=&own Any> | ||
} | ||
|
||
fn g() { | ||
let arg1 = String::new(); | ||
let arg2 = 1u32; | ||
let arg3 = String::new(); | ||
f(&own arg1, &own arg2, &own arg3); | ||
} | ||
``` | ||
|
||
If `Any` were a built-in type, then this could even be simplified to | ||
|
||
```rust | ||
fn f(args: ..) { | ||
// args is an anonymous type that implements Iterator<Item=&own Any> | ||
} | ||
|
||
fn g() { | ||
let arg1 = String::new(); | ||
let arg2 = 1u32; | ||
let arg3 = String::new(); | ||
f(arg1, arg2, arg3); | ||
} | ||
``` | ||
|
||
But this is somewhat off-topic. | ||
|
||
# Detailed design | ||
|
||
A `&own T` reference looks exactly like a `&mut T` reference, i.e., if `T` is a | ||
sized type, then it's simply a pointer to the referenced data. If `T` is a | ||
trait or slice (`[U]`), then it's a fat pointer. | ||
|
||
A `&own T` reference behaves exactly like a `&mut T` reference except that you | ||
can move out of it via dereferencing. This consumes the reference. If the | ||
reference is not explicitly consumed, the compiler will consume the reference | ||
implicitly. For example: | ||
|
||
```rust | ||
fn f(_x: &own String) { | ||
} | ||
``` | ||
|
||
Modulo optimization, this is equivalent to | ||
|
||
```rust | ||
fn f(_x: &own String) { | ||
let _unused = *_x; | ||
} | ||
``` | ||
|
||
Creating a `&own T` reference makes the referenced object indefinitely | ||
inaccessible except through the `&own` reference. | ||
|
||
```rust | ||
fn f(x: String) { | ||
{ | ||
let _y = &own x; | ||
} | ||
x.push(""); // error: x has been moved | ||
} | ||
``` | ||
|
||
Creating a `&own T` reference moves the drop obligation for the referenced | ||
object into the reference. For example: | ||
|
||
```rust | ||
fn f(x: String, flag: bool) { | ||
if flag { | ||
g(&own x); | ||
} else { | ||
h(); | ||
} | ||
} | ||
``` | ||
|
||
Assuming non-zeroing drop, the compiler will generate the following pseudo code: | ||
|
||
```rust | ||
fn f(x: String, flag: bool) { | ||
let mut x_needs_drop = true; | ||
|
||
if flag { | ||
x_needs_drop = false; | ||
g(&own x); | ||
} else { | ||
h(); | ||
} | ||
|
||
if x_needs_drop { | ||
drop x; | ||
// This is pseudo code because the user cannot access `x` at this point. | ||
} | ||
} | ||
``` | ||
|
||
Creating a `&own T` reference, however, does not move the drop obligation for | ||
the container into the reference. For example: | ||
|
||
```rust | ||
fn f(x: Box<String>, flag: bool) { | ||
if flag { | ||
g(&own *x); | ||
} else { | ||
h(); | ||
} | ||
} | ||
``` | ||
|
||
Again assuming non-zeroing drops, the compiler generates | ||
|
||
```rust | ||
fn f(x: Box<String>, flag: bool) { | ||
let mut x_needs_drop = true; | ||
|
||
if flag { | ||
x_needs_drop = false; | ||
g(&own *x); | ||
} else { | ||
h(); | ||
} | ||
|
||
if x_needs_drop { | ||
drop *x; | ||
} | ||
deallocate(x); | ||
} | ||
``` | ||
|
||
This means that there are two drop flags: One for the container and one for the | ||
contained object. (With zeroing drops both of these flags are implicit.) | ||
|
||
It's possible to take a sub-`&own` reference if the type does not implement | ||
`Drop`. For example: | ||
|
||
```rust | ||
struct X { | ||
a: String, | ||
b: String, | ||
} | ||
|
||
fn f(x: &own X) -> &own String { | ||
&own x.b | ||
} | ||
|
||
// This is equivalent to | ||
|
||
fn g(x: &own X) -> &own String { | ||
let X { a: _, b: ref out b } = *x; | ||
b | ||
} | ||
|
||
// One could even write | ||
|
||
fn h(x: &own X) -> (&own String, &own String) { | ||
let a = &own x.a; | ||
let b = &own x.b; | ||
(a, b) | ||
} | ||
``` | ||
|
||
A `&own T` supports implicit coercions to `&mut T` and `&T`: | ||
|
||
```rust | ||
fn f(x: String) { | ||
g(&own x); | ||
// implicit move out of the anonymous reference here | ||
} | ||
|
||
fn g(x: &mut T) { | ||
} | ||
``` | ||
|
||
## Slices | ||
|
||
Even though slices are not special, we'll describe the behavior that can be | ||
expected from `&own [T]` slices here: | ||
|
||
Subslicing causes the objects that are no longer in the slice to be dropped. | ||
|
||
Taking a `&own` reference to a single element causes all other elements to be | ||
dropped: | ||
|
||
```rust | ||
fn f(xs: &own [String]) { | ||
let x = &own xs[0]; | ||
} | ||
``` | ||
|
||
At the moment it is not possible to move out of custom containers such as | ||
`VecDeque<T>`. However, this is not a serious problem because we can still | ||
create owning slices via a method: | ||
|
||
```rust | ||
impl<T> VecDeque<T> { | ||
pub fn owned_sices(&mut self) -> (&own [T], &own [T]) { | ||
let slices = mem::transmute(self.as_slices()); | ||
self.tail = 0; | ||
self.head = 0; | ||
slices | ||
} | ||
} | ||
``` | ||
|
||
The two slices that have been returned own their content and once the slices | ||
have been dropped the empty `VecDeque` is accessible again. | ||
|
||
# Drawbacks | ||
|
||
You can't create `&own` slices with the `&own vec[..]` syntax because Rust | ||
currently does not allow moving out of containers other that `Box`. | ||
|
||
# Alternatives | ||
|
||
## What other designs have been considered? | ||
|
||
None | ||
|
||
## What is the impact of not doing this? | ||
|
||
Can't move out of references. | ||
|
||
# Unresolved questions | ||
|
||
None right now. |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.
let mut x