- Start Date: 2015-02-18
- RFC PR: (leave this empty)
- Rust Issue: (leave this empty)
Summary
Allow partial moves from drop-implementing types before forget
.
Motivation
There is a class of APIs that can transform the shell surrounding a
piece of data, while leaving the data itself untouched. If the shell
implements Drop
, then it is impossible to retain the contained data
by move while disposing of the container. For example, a generalized
String class that supports small string optimizations may be
implemented as an enum:
enum GeneralizedString {
SmallString([char; 24], usize),
BigString(Vec<char>),
}
impl GeneralizedString {
pub fn into_vec(self) -> Vec<char> {
match self {
SmallString(ref chars, size) => unsafe {
Vec::from_raw_buf(chars as *const char, size)
},
BigString(vec) => vec,
}
}
}
Of course, if GeneralizedString
implemented Drop
, then this
implementation would be impossible, since current-Rust forbids partial
moves on types that implement Drop
. This RFC addresses this issue,
by changing the restriction from "disallowing partial moves on types
that implement Drop
" to “disallowing partial moves as the value is
moved into the drop glue”. Since forget
prevents a type from being
consumed by drop glue, this would make a small transformation of the
above code enough to make compilation succeed, even if Drop
were
implemented for GeneralizedString
:
impl GeneralizedString {
pub fn into_vec(self) -> Vec<char> {
let rval = match self {
SmallString(ref chars, size) => unsafe {
Vec::from_raw_buf(chars as *const char, size)
},
BigString(vec) => vec,
};
std::mem::forget(self);
rval
}
}
Detailed design
This design has the following components:
-
Remove the restriction against moves from types that implement Drop.
-
Enforce the restriction against partial moves at the point the drop glue is inserted.
-
Allow std::mem::forget() to be called against partially-moved values.
In practice, the first two points should mean that this error:
error: cannot move out of type `GeneralizedString`, which defines the `Drop` trait
Would be replaced with this error:
error: use of partially moved value `self` while calling destructor for `GeneralizedString`.
While the last point allows the user to prevent the error from occurring.
Drawbacks
As pointed out by @eddyb and @pnkfelix, this proposal makes forget
a
special case in the language: it would be the only function that can
ever be defined that can receive a partially-moved value. As such, it
would be impossible to write routines that forward their
implementation to forget
, while accepting partially-moved inputs.
This could be addressed by reifying the concept of a partially-moved
value (perhaps a PartiallyMoved<T>
, which can be implicitly coerced
from T
), and making this be the argument to forget
, perhaps this
idea should be folded into the detailed-design properly.
I’m having a failure of imagination, and can’t think of other reasons this
would be undesirable. It would take some implementation effort, and
because it is backwards-compatible, it would not be a priority for a
Rust 1.0 release. Are there perhaps some cases that I don’t see that
the current restriction (against partial moves from types that
implement Drop) would not result in identical behavior under the new
restriction, when forget
is not called?
Alternatives
@eddyb suggested an Internal<T>
,
to capture the idea of an “internal” type that provides access to the
same data as T
, but with insertion of drop glue prevented. I
personally have a parsing challenge when reading the Internal<T>
type, where any type declaration Type<T>
reads like a decoration
around T
, but what is happening in actuality is that the drop-shell
surrounding T
is being removed: an implicit decorator is, in
essence, being removed. I also suspect that the approach described
here will be easier to implement, and is certainly less of a “new
concept” to understand than Internal<T>
. This design also does not
preclude the Internal<T>
design, though it does, I think, address
many of Internal<T>
's use-cases.
Another way of achieving the same ends may be to allow destructuring
on a type that implements Drop
to prevent drop-glue insertion. I
have a hard time understanding how this approach would work when
applied to enums that implement Drop, if only some of the variants
have associated data.
Unresolved questions
There are implementation details that require knowing how the language intends to allow function-pointers for intrinsics.