From ea780d3d19dfb8b019bf0aa41217303f49e7be88 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?John=20K=C3=A5re=20Alsaker?= Date: Tue, 10 Jan 2017 03:47:11 +0100 Subject: [PATCH 1/7] Immovable types RFC --- text/0000-immovable-types.md | 144 +++++++++++++++++++++++++++++++++++ 1 file changed, 144 insertions(+) create mode 100644 text/0000-immovable-types.md diff --git a/text/0000-immovable-types.md b/text/0000-immovable-types.md new file mode 100644 index 00000000000..58fe53f6a4c --- /dev/null +++ b/text/0000-immovable-types.md @@ -0,0 +1,144 @@ +- Feature Name: immovable_types +- Start Date: 2017-01-09 +- RFC PR: (leave this empty) +- Rust Issue: (leave this empty) + +# Summary +[summary]: #summary + +This add an new built-in trait `Move` which all existing types will implement. Types which do not implement it cannot move after they have been borrowed. + +# Motivation +[motivation]: #motivation + +Interacting with C/C++ code often require data that cannot change its location in memory. To work around this we allocate such data on the heap. For example the standard library `Mutex` type allocates a platform specific mutex on the heap. This prevents the use of `Mutex` in global variables. If we add immovable types, we can have an alternative immovable mutex type `StaticMutex` which we could store in global variables. If the lifetime of the mutex is limited to a lexical scope, we could also have a `StaticMutex` in the stack frame and avoid the allocation. + +The key motivation for this proposal is to allow generators to have "stack frames" which do not move in memory. The ability to take references to local variables rely on those variable being static in memory. If a generator is moved, the local variables contained inside also move, which invalidates references to them. So references to local variables stored inside the generator cannot be allowed. + +Since generators can only move during suspend points we can require that references to local variables do not live across suspend points and so they would not get invalidated. This is still quite restrictive compared to normal functions and will result in code containing unnecessary allocations. If generators are immovable, no such restrictions apply, and references would work like in normal functions. It does however place a burden on the user of those generators to not move them. This isn't a problem for use cases such as awaiting on a future in asynchronous code or iterating over a structure, since the generator would be stored in the stack frame (which is immovable). + +# Detailed design +[design]: #detailed-design + +A new marker trait `Move` is introduced in `core::marker`. All type parameters (including `Self` for traits) and associated types implement `Move` by default. + +If you want to allow types which may not implement `Move`, you would use the `?Move` trait bound which means that the type may or may not implement `Move`. + +All primitive types implement `Move`. Composite types implement `Move` if all their fields do. The trait implementations for `Move` are provided by the compiler and users cannot implement `Move` themselves. These are the rules for dynamically sized types: +- `[T]` is `Move` if `T` is +- `str` is `Move` +- Trait objects are not `Move` by default + +A new marker struct `ImmovableData` is also introduced in `core::marker`. This struct does not implement `Move` and allows users to make composite immovable types. `PhantomData` should be extended to accept `?Move` types, but `PhantomData` itself should always implement `Move`. + +You can freely move values which do not implement `Move` as long as you don't borrow them. Once we borrow such a value, we'd know it's address and code should be able to rely on the address not moving. + +Static variables allow types which do not implement `Move`. + +## Borrowing immovable types + +Borrowing values of types which do not implement `Move` is only allowed if the borrows lasts for the entire lifetime of the values, including the drop of the value, since `drop` takes a reference to it. Reborrows of such borrows follow existing rules. + +This means that the following borrow would not be allowed: +```rust +let mutex = StaticMutex::new(0); +{ + *mutex.lock() += 1; +} +let moved = mutex; +*moved.lock() += 1; +``` +Here `lock` borrows the `mutex` variable. The borrow only last until the end of the same statement. That means we'd be allowed to move `mutex` into a new variable `moved` and call `lock` on it, this time with an different address for `&self`! + +We rely on the fact that borrows prevent moves. We cannot change the lifetime of the borrow to encompass the moving statement using the current borrow checker. This can be changed once we get non-lexical lifetime and we'd get an error on the move instead. + +Conceptually we can think of borrows of `?Move` values as introducing 2 borrows: +- one borrow with as short lifetime as possible with normal restrictions +- one borrow which must match the lifetime of the borrowed value. The only restriction placed is that the value must not be moved out of. + +This RFC suggests an approach where we use only the shorter borrow and require it to match the lifetime of the value. This is less flexible, but results in minimal implementation changes. A more flexible solution can be introduced with non-lexical lifetimes. + +We can easily work around issues with this in code by using a single borrow of the immovable value and just reborrow. + +Illegal: +```rust +let mutex = StaticMutex::new(0); +*mutex.lock() += 1; +*mutex.lock() += 1; +``` +Workaround using reborrows: +```rust +let mutex = &StaticMutex::new(0); +*mutex.lock() += 1; +*mutex.lock() += 1; +``` + +A borrow such as `&var.field` where `field` is immovable will last as long as the lifetime of `var` to ensure it matches the lifetime of the field. + +We need to prevent assignment to immovable types once they have been borrowed. This is because assignment actually moves the l-value before calling `Drop::drop`. If there are any restrictions on the l-value or if the l-value has a dereference operation, assignment to immovable types is not allowed. + +Types which implement `Copy`, but not `Move` are allowed. You can still copy them around, but borrows follows the restrictions of `?Move` types. + +## Immovable types contained in movable types + +To allow immovable types to be contained in movable types, we introduce a `core::cell::MobileCell` wrapper which itself implements `Move`. It works similarly to `Cell` in that it disallows references to the value inside. +```rust +#[lang = "mobile_cell"] +pub struct MobileCell { + value: T, +} + +impl MobileCell { + pub const fn new(value: T) -> Movable { + Movable { + value: value, + } + } + + pub fn into_inner(self) -> T { + self.value + } + + pub fn replace(&mut self, new_value: T) -> T { + let mut result = MobileCell::new(new_value); + core::mem::replace(self, &mut result); + result.into_inner() + } +} +``` + +## Implications for language traits + +In order to allow functions to take immovable types and arguments and return them, we need to change `FnOnce`, `FnMut` and `Fn`. A `?Move` bound should be added for the `Args` type parameter to these traits. We also need a `?Move` bound on `FnOnce::Output`, which is backwards incompatible. `FnOnce::Output` was stabilized in 1.12, so hopefully there aren't any code relying on it yet. + +Having a `?Move` bound on `Deref::Target` would be nice. It would allow us to use the dereference operator on `Box`, `Rc`, and `Arc` containing immovable types. + +A `?Move` bound on `IntoIterator::IntoIter` and `Iterator::Self` would also be useful, since you could then use immovable iterators in for-loops. + +I suggest we do a `crater` run to investigate if these breakages are feasible. + +Changing these associated types will be insta-stable. You would be unable to write stable code which would conflict with this proposal. `?Move` bounds would also show up in documentation, although we would be able to filter those out if desired. + +# How We Teach This +[how-we-teach-this]: #how-we-teach-this + +Rust already has the concept of immovable values when a value is borrowed. This adds types where borrows always last until the value is dropped. + +The concept of immovable types is likely familiar to users of C, C++ and C FFIs. + +# Drawbacks +[drawbacks]: #drawbacks + +This adds a new builtin trait and more logic to the borrow checker. It also requires `?Move` bounds. It may also break existing programs. + +# Alternatives +[alternatives]: #alternatives + +- Instead of having a `Move` trait, we can add final reference types `&final` `&final mut`. Borrowing with these would correspond to borrows of `?Move` types in this RFC. This would require much move invasive changes to the language and may rule out the possiblity of self borrowing types with a `'self` lifetime. + +- Do nothing, but not having this makes generators interact rather poorly with references. + +# Unresolved questions +[unresolved]: #unresolved-questions + +Which associated types can we change in a backwards incompatible way? \ No newline at end of file From a57a8886f16d27b336beba0b2c1d4e4869661fb1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?John=20K=C3=A5re=20Alsaker?= Date: Sun, 5 Mar 2017 14:13:58 +0100 Subject: [PATCH 2/7] Allow immovable types in Box, Rc, Arc --- text/0000-immovable-types.md | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/text/0000-immovable-types.md b/text/0000-immovable-types.md index 58fe53f6a4c..71c483c6af8 100644 --- a/text/0000-immovable-types.md +++ b/text/0000-immovable-types.md @@ -11,7 +11,7 @@ This add an new built-in trait `Move` which all existing types will implement. T # Motivation [motivation]: #motivation -Interacting with C/C++ code often require data that cannot change its location in memory. To work around this we allocate such data on the heap. For example the standard library `Mutex` type allocates a platform specific mutex on the heap. This prevents the use of `Mutex` in global variables. If we add immovable types, we can have an alternative immovable mutex type `StaticMutex` which we could store in global variables. If the lifetime of the mutex is limited to a lexical scope, we could also have a `StaticMutex` in the stack frame and avoid the allocation. +Interacting with C/C++ code may require data that cannot change its location in memory. To work around this we allocate such data on the heap. For example the standard library `Mutex` type allocates a platform specific mutex on the heap. This prevents the use of `Mutex` in global variables. If we add immovable types, we can have an alternative immovable mutex type `StaticMutex` which we could store in global variables. If the lifetime of the mutex is limited to a lexical scope, we could also have a `StaticMutex` in the stack frame and avoid the allocation. The key motivation for this proposal is to allow generators to have "stack frames" which do not move in memory. The ability to take references to local variables rely on those variable being static in memory. If a generator is moved, the local variables contained inside also move, which invalidates references to them. So references to local variables stored inside the generator cannot be allowed. @@ -31,7 +31,7 @@ All primitive types implement `Move`. Composite types implement `Move` if all th A new marker struct `ImmovableData` is also introduced in `core::marker`. This struct does not implement `Move` and allows users to make composite immovable types. `PhantomData` should be extended to accept `?Move` types, but `PhantomData` itself should always implement `Move`. -You can freely move values which do not implement `Move` as long as you don't borrow them. Once we borrow such a value, we'd know it's address and code should be able to rely on the address not moving. +You can freely move values which do not implement `Move` as long as you don't borrow them. Once we borrow such a value, we'd know its address and code should be able to rely on the address not changing. This is sound since the only way to observe the address of a value is to borrow it. Before the first borrow nothing can observe the address and the value can be moved around. Static variables allow types which do not implement `Move`. @@ -119,6 +119,17 @@ I suggest we do a `crater` run to investigate if these breakages are feasible. Changing these associated types will be insta-stable. You would be unable to write stable code which would conflict with this proposal. `?Move` bounds would also show up in documentation, although we would be able to filter those out if desired. +## Allowing immovable types in container types + +`std::boxed::Box`, `std::rc::Rc`, `std::rc::Weak`, `std::sync::Arc`, `std::sync::Weak` will be changed to allow immovable types inside, but will themselves be movable. These can be used to overcome the limitations of immovable types at the cost of an allocation. + +For `Rc` and `Arc` , the function `try_unwrap` would only be allowed on movable types. + +In general, we can allow immovable types in an movable container if we either: +- disallow all methods of accessing the address of the contained immovable types, including references (possible for `Vec`, `HashMap`) +- prevent the type from actually moving once it's inside (the method suitable for `Box`, `Rc`, `Arc`) + + # How We Teach This [how-we-teach-this]: #how-we-teach-this From 7886d57b12463e98afd8b873dbfa39b8d9052109 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?John=20K=C3=A5re=20Alsaker?= Date: Thu, 9 Mar 2017 21:30:12 +0100 Subject: [PATCH 3/7] Make Move an auto trait --- text/0000-immovable-types.md | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/text/0000-immovable-types.md b/text/0000-immovable-types.md index 71c483c6af8..32fc11c2920 100644 --- a/text/0000-immovable-types.md +++ b/text/0000-immovable-types.md @@ -20,16 +20,11 @@ Since generators can only move during suspend points we can require that referen # Detailed design [design]: #detailed-design -A new marker trait `Move` is introduced in `core::marker`. All type parameters (including `Self` for traits) and associated types implement `Move` by default. +A new unsafe auto trait `Move` is introduced in `core::marker`. References, pointers, `core::ptr::Unique` and `core::ptr::Shared` implement this trait. -If you want to allow types which may not implement `Move`, you would use the `?Move` trait bound which means that the type may or may not implement `Move`. - -All primitive types implement `Move`. Composite types implement `Move` if all their fields do. The trait implementations for `Move` are provided by the compiler and users cannot implement `Move` themselves. These are the rules for dynamically sized types: -- `[T]` is `Move` if `T` is -- `str` is `Move` -- Trait objects are not `Move` by default +All type parameters (including `Self` for traits), trait objects and associated types have a `Move` bound by default. -A new marker struct `ImmovableData` is also introduced in `core::marker`. This struct does not implement `Move` and allows users to make composite immovable types. `PhantomData` should be extended to accept `?Move` types, but `PhantomData` itself should always implement `Move`. +If you want to allow types which may not implement `Move`, you would use the `?Move` trait bound which means that the type may or may not implement `Move`. You can freely move values which do not implement `Move` as long as you don't borrow them. Once we borrow such a value, we'd know its address and code should be able to rely on the address not changing. This is sound since the only way to observe the address of a value is to borrow it. Before the first borrow nothing can observe the address and the value can be moved around. @@ -88,6 +83,8 @@ pub struct MobileCell { value: T, } +unsafe impl Move for MobileCell {} + impl MobileCell { pub const fn new(value: T) -> Movable { Movable { From 9a8240e6f42f8978e81fdb2e8aefe7b44da721e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?John=20K=C3=A5re=20Alsaker?= Date: Mon, 27 Mar 2017 15:40:18 +0200 Subject: [PATCH 4/7] Clarify some things --- text/0000-immovable-types.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/text/0000-immovable-types.md b/text/0000-immovable-types.md index 32fc11c2920..d4dfcafb856 100644 --- a/text/0000-immovable-types.md +++ b/text/0000-immovable-types.md @@ -20,13 +20,13 @@ Since generators can only move during suspend points we can require that referen # Detailed design [design]: #detailed-design -A new unsafe auto trait `Move` is introduced in `core::marker`. References, pointers, `core::ptr::Unique` and `core::ptr::Shared` implement this trait. +A new unsafe auto trait `Move` is introduced in `core::marker`. Auto traits are implemented for all primitive types and for composite types where the elements also implement the trait. Users can opt-out to this for custom types. References, pointers, `core::ptr::Unique` and `core::ptr::Shared` explicitly implement this trait, since pointers are movable even if they point to immovable types. All type parameters (including `Self` for traits), trait objects and associated types have a `Move` bound by default. If you want to allow types which may not implement `Move`, you would use the `?Move` trait bound which means that the type may or may not implement `Move`. -You can freely move values which do not implement `Move` as long as you don't borrow them. Once we borrow such a value, we'd know its address and code should be able to rely on the address not changing. This is sound since the only way to observe the address of a value is to borrow it. Before the first borrow nothing can observe the address and the value can be moved around. +You can freely move values which are known to implement `Move` after they are borrowed, however you cannot move types which aren't known to implement `Move` after they have been borrowed. Once we borrow an immovable type, we'd know its address and code should be able to rely on the address not changing. This is sound since the only way to observe the address of a value is to borrow it. Before the first borrow nothing can observe the address and the value can be moved around. Static variables allow types which do not implement `Move`. From c235cf218b63dd5fa3bbb4418a5a944719493630 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?John=20K=C3=A5re=20Alsaker?= Date: Thu, 8 Jun 2017 05:38:02 +0200 Subject: [PATCH 5/7] Use a MIR dataflow analysis pass to prevent moves instead of basing it on lifetime of borrows. --- text/0000-immovable-types.md | 44 ++---------------------------------- 1 file changed, 2 insertions(+), 42 deletions(-) diff --git a/text/0000-immovable-types.md b/text/0000-immovable-types.md index d4dfcafb856..36257d66b84 100644 --- a/text/0000-immovable-types.md +++ b/text/0000-immovable-types.md @@ -30,49 +30,9 @@ You can freely move values which are known to implement `Move` after they are bo Static variables allow types which do not implement `Move`. -## Borrowing immovable types +## Move checking -Borrowing values of types which do not implement `Move` is only allowed if the borrows lasts for the entire lifetime of the values, including the drop of the value, since `drop` takes a reference to it. Reborrows of such borrows follow existing rules. - -This means that the following borrow would not be allowed: -```rust -let mutex = StaticMutex::new(0); -{ - *mutex.lock() += 1; -} -let moved = mutex; -*moved.lock() += 1; -``` -Here `lock` borrows the `mutex` variable. The borrow only last until the end of the same statement. That means we'd be allowed to move `mutex` into a new variable `moved` and call `lock` on it, this time with an different address for `&self`! - -We rely on the fact that borrows prevent moves. We cannot change the lifetime of the borrow to encompass the moving statement using the current borrow checker. This can be changed once we get non-lexical lifetime and we'd get an error on the move instead. - -Conceptually we can think of borrows of `?Move` values as introducing 2 borrows: -- one borrow with as short lifetime as possible with normal restrictions -- one borrow which must match the lifetime of the borrowed value. The only restriction placed is that the value must not be moved out of. - -This RFC suggests an approach where we use only the shorter borrow and require it to match the lifetime of the value. This is less flexible, but results in minimal implementation changes. A more flexible solution can be introduced with non-lexical lifetimes. - -We can easily work around issues with this in code by using a single borrow of the immovable value and just reborrow. - -Illegal: -```rust -let mutex = StaticMutex::new(0); -*mutex.lock() += 1; -*mutex.lock() += 1; -``` -Workaround using reborrows: -```rust -let mutex = &StaticMutex::new(0); -*mutex.lock() += 1; -*mutex.lock() += 1; -``` - -A borrow such as `&var.field` where `field` is immovable will last as long as the lifetime of `var` to ensure it matches the lifetime of the field. - -We need to prevent assignment to immovable types once they have been borrowed. This is because assignment actually moves the l-value before calling `Drop::drop`. If there are any restrictions on the l-value or if the l-value has a dereference operation, assignment to immovable types is not allowed. - -Types which implement `Copy`, but not `Move` are allowed. You can still copy them around, but borrows follows the restrictions of `?Move` types. +To prevent values which may not implement `Move` that have been previously borrowed we introduce a MIR pass. We do a forward dataflow analysis on the MIR marking l-values that have been borrowed. For `a` we mark the path `a` as observed. For `a.b` we mark both `a.b` and the parent `a`, since if you observe `a.b` moving `a` will change the address of `a.b`. For `*a` we do nothing, as the address of `a` isn't observed. For slice indices `a[i]`, we mark `a` as observed. Finally we walk through every move in the MIR and give an error if the moved value could be observed at that point and the type of the value isn't known to implement `Move`. ## Immovable types contained in movable types From 1783835ed2711ba33ee5aef1933d03f5a57f2cc4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?John=20K=C3=A5re=20Alsaker?= Date: Thu, 8 Jun 2017 06:03:19 +0200 Subject: [PATCH 6/7] Update drawbacks --- text/0000-immovable-types.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/0000-immovable-types.md b/text/0000-immovable-types.md index 36257d66b84..984b81d216e 100644 --- a/text/0000-immovable-types.md +++ b/text/0000-immovable-types.md @@ -97,7 +97,7 @@ The concept of immovable types is likely familiar to users of C, C++ and C FFIs. # Drawbacks [drawbacks]: #drawbacks -This adds a new builtin trait and more logic to the borrow checker. It also requires `?Move` bounds. It may also break existing programs. +This adds a new builtin trait and another checker pass. It also requires `?Move` bounds. It may also break existing programs. # Alternatives [alternatives]: #alternatives From ef155ea2ea16a47533f8a54918206fe257671e02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?John=20K=C3=A5re=20Alsaker?= Date: Wed, 18 Oct 2017 11:13:38 +0200 Subject: [PATCH 7/7] Make Move a built in trait again and otherwise align the with PR --- text/0000-immovable-types.md | 86 +++++++++++++++++++++++++----------- 1 file changed, 61 insertions(+), 25 deletions(-) diff --git a/text/0000-immovable-types.md b/text/0000-immovable-types.md index 984b81d216e..e992ec6d351 100644 --- a/text/0000-immovable-types.md +++ b/text/0000-immovable-types.md @@ -20,47 +20,84 @@ Since generators can only move during suspend points we can require that referen # Detailed design [design]: #detailed-design -A new unsafe auto trait `Move` is introduced in `core::marker`. Auto traits are implemented for all primitive types and for composite types where the elements also implement the trait. Users can opt-out to this for custom types. References, pointers, `core::ptr::Unique` and `core::ptr::Shared` explicitly implement this trait, since pointers are movable even if they point to immovable types. - -All type parameters (including `Self` for traits), trait objects and associated types have a `Move` bound by default. +A new builtin marker trait `Move` is introduced in `core::marker`. All type parameters (including `Self` for traits) and associated types implement `Move` by default. If you want to allow types which may not implement `Move`, you would use the `?Move` trait bound which means that the type may or may not implement `Move`. +A new marker struct `Immovable` is also introduced in `core::marker`. This struct does not implement `Move` and allows users to make composite immovable types. + You can freely move values which are known to implement `Move` after they are borrowed, however you cannot move types which aren't known to implement `Move` after they have been borrowed. Once we borrow an immovable type, we'd know its address and code should be able to rely on the address not changing. This is sound since the only way to observe the address of a value is to borrow it. Before the first borrow nothing can observe the address and the value can be moved around. Static variables allow types which do not implement `Move`. +These are the rules to determine if a type implements `Move`: +- Integers, floats, `char` and `bool` are `Move` +- Function types and function pointers are `Move` +- Closures are `Move` if their captured variables are `Move` +- Movable generators are `Move` if all values (including capture variables) which are live during a suspension point are `Move` +- Immovable generators are never `Move` +- The `Immovable` type is never `Move` +- Trait objects are `Move` if they have an explicit `Move` bound +- Struct, enums and tuples are `Move` if all their elements are `Move` +- Existential types (`impl Trait`) are `Move` if their underlying type are `Move` +- `[T]` and `[T; n]` are `Move` if `T` is `Move` +- `str` is `Move` + ## Move checking -To prevent values which may not implement `Move` that have been previously borrowed we introduce a MIR pass. We do a forward dataflow analysis on the MIR marking l-values that have been borrowed. For `a` we mark the path `a` as observed. For `a.b` we mark both `a.b` and the parent `a`, since if you observe `a.b` moving `a` will change the address of `a.b`. For `*a` we do nothing, as the address of `a` isn't observed. For slice indices `a[i]`, we mark `a` as observed. Finally we walk through every move in the MIR and give an error if the moved value could be observed at that point and the type of the value isn't known to implement `Move`. +We need to ensure that values we have borrowed no longer can be moved. When we borrow a value we can find its address in memory. For example: +```rust +struct Foo { + field: bool, +} -## Immovable types contained in movable types +fn address(v: &Foo) -> usize { + v as *const _ as usize +} -To allow immovable types to be contained in movable types, we introduce a `core::cell::MobileCell` wrapper which itself implements `Move`. It works similarly to `Cell` in that it disallows references to the value inside. +let a = Foo { + field: true +}; +address(&a) +``` +We can also find the address of `a.field` in our `address` function using this code: ```rust -#[lang = "mobile_cell"] -pub struct MobileCell { - value: T, +fn address(v: &Foo) -> usize { + &v.field as *const _ as usize } +``` +Thus we say that our borrow `&a` observes both `a` and `a.field`. +In general, borrowing a value observes all other values stored inside except for values which are reached using an indirection (for example a reference or a `Box`). If any elements of an array is observed, the entire array is also observed. -unsafe impl Move for MobileCell {} +Whenever we are moving an value we emit an error if the type does not implement `Move` and the value could also have been observed. We do the same check for any values stored inside; again ignoring indirections. -impl MobileCell { - pub const fn new(value: T) -> Movable { - Movable { - value: value, - } - } +## Immovable types contained in movable types - pub fn into_inner(self) -> T { - self.value - } +To allow immovable types to be contained in movable types, we introduce a `core::cell::MovableCell` wrapper which itself implements `Move`. It works similarly to `Cell` in that it disallows references to the value inside. +```rust +#[lang = "movable_cell"] +#[derive(Default)] +pub struct MovableCell { + value: T, +} - pub fn replace(&mut self, new_value: T) -> T { - let mut result = MobileCell::new(new_value); - core::mem::replace(self, &mut result); - result.into_inner() - } +impl MovableCell { + /// Creates a new MovableCell. + pub const fn new(value: T) -> Self { + MovableCell { + value: value, + } + } + + /// Extracts the inner value. + pub fn into_inner(self) -> T { + self.value + } + + /// Replaces the inner value. + pub fn replace(&mut self, new_value: T) -> T { + mem::replace(self, MovableCell::new(new_value)).into_inner() + } } ``` @@ -86,7 +123,6 @@ In general, we can allow immovable types in an movable container if we either: - disallow all methods of accessing the address of the contained immovable types, including references (possible for `Vec`, `HashMap`) - prevent the type from actually moving once it's inside (the method suitable for `Box`, `Rc`, `Arc`) - # How We Teach This [how-we-teach-this]: #how-we-teach-this