-
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
Propose Interior<T>
data-type, to allow moves out of the dropped value during the drop hook.
#1180
Conversation
Define `Interior<T>` type and associated language item. `Interior<T>` is structurally identical to `T` (as in, same memory layout, same structure fields, same enum discriminant interpretation), but has no traits implemented. Define a new `DropValue` trait that takes an `Interior<T>` argument (instead of `&mut T`). This will allow fields to be moved out of a compound structure during the drop glue. The drop-glue will change to directly invoke the `DropValue` hook with the `Interior<T>` structure of the `T` structure being dropped.
## `impl DropValue for Interior<T>` | ||
|
||
We do not prohibit `impl DropValue for Interior<T>`: it should just | ||
work in the natural way. |
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.
Is this proposing to add a special case to the orphan rules? (Normally, you can't implement traits defined in another crate for types defined in another crate.)
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.
The idea on this line is that T
is defined in a local crate, so I think impl DropValue for Interior<T>
is valid per orphan rules, but I'll double-check.
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.
Drop
has special rules so you can't have partial impls for generic types:
struct Foo<T>(T);
// error: Implementations of Drop cannot be specialized [E0366]
impl Drop for Foo<()> {
fn drop(&mut self) {}
}
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.
Right, I've just read the discussion around that... You're right, I don't want to change this behavior. I'll amend the RFC.
Might be worth explicitly describing the shim the compiler has to generate for trait vtables. (Consider the code necessary to implement Overall, this seems like a solid proposal. |
cc me |
Thanks for proposing this @aidancully, especially making it backwards compatible! |
println!("drop_bar"); | ||
// "this" is consumed at function exit, so the compiler | ||
// will generate the following code: | ||
// DropValue::<Foo>::drop_value(into_interior(this.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.
Is it possible to do partial moves like this? Otherwise do:
let Bar(x, y) = this;
DropValue::<Foo>::drop_value(into_interior(x));
DropValue::<Foo>::drop_value(into_interior(y));
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.
In the proposal, it's possible to do this so long as DropValue
isn't defined for Interior<Foo>
: partial moves are allowed when type doesn't implement DropValue
. In the example, DropValue
is implemented for Bar
, but not for Interior<Bar>
; so a partial move out of Interior<Bar>
is allowed. That said, the proposal needs better discussion of partial moves.
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.
Interior<T>
cannot implement DropValue
if the same sanity checks we do for Drop
are kept (and I don't see why that should be possible).
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.
As I said like in our discussion long below, I still prefer tying destructuring and functional update to whether the type has private fields, rather than whether any trait is implemented.
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.
On the other hand, seeing that mem::forget is safe
, I don't know why we need to care about circumnavigating user-defined destructors at all.
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.
Well, just because mem::forget
is safe, doesn't mean we should be encouraging its use...
The point of manual drops it to override the default behavior (duh). So the final drop behavior and the default exist in a sort of self-super relationship. The point of IMO it is simpler/less magical just to expose a different function which is the default The vast majority of implementations will not need to worry about infinite recursion, as all interesting things to do in a |
I think that would be acceptable if infinite recursion were the only concern... But it doesn't solve the problem of allowing partial moves from the value during the drop-hook: partial moves are still disallowed for types implementing I know there's been discussion of using destructuring to allow consumption of the container type without consuming the fields, which would inhibit invocation of the drop-hook while continuing to provide access to fields. The difficulty I've had with this approach is that I haven't seen how it would work with some kinds of enums, which don't have moving destructures: enum Foo {
Bar,
// the way this enum gets destructured might depend on its discriminant:
//Baz(Box<u32>),
// but in this case, doesn't really...
Qux,
}
impl DropValue for Foo {
fn drop_value(self) {
// use `match` to find the discriminant:
match self {
when Bar => (),
when Qux => (),
}
// "self" is still accessible? argh, must use `forget`.
mem::forget(self)
}
} When this came up before, I suggested a |
@aidancully Thanks, I hadn't realized the problems with Copy before. Exposing a "default method" thing would help with the boilerplate, but not with Copy before. [Nor am I willing just say a type can't be DropValue and Copy, linear types that impl Copy might perhaps be useful].
|
@Ericson2314 You currently cannot impl |
} | ||
``` | ||
|
||
Define the following bare functions to convert between `T` and |
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.
Why aren't these static methods? Interior::of(T)
, for example.
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.
Because I wanted to avoid possibility of namespace collision. This is nonsensical, but demonstrates the problem I was trying to avoid:
struct Foo;
impl Foo {
fn of(&self) -> &Foo {
self
}
}
If we have impl Deref for Interior<T>
with Target == T
, then there's an ambiguity in how of
would be resolved, right?
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.
No, because resolving paths does not touch Deref
at all.
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.
OK, right - we can make these all static methods on Interior
. I played it a little too conservative.
@eddyb OK, but then we don't even need |
@Ericson2314 the problem isn't with a type that implements Copy and Drop; the problem is with a type that implements Drop, but all the members are Copy. There's also the issue that making an unmarked pattern match skip dropping a value might be a bit too easy to screw up. |
Ah, sorry I was confused. |
One of the reasons I haven't written up let x = 0u32;
let y = move x; // x is no longer accessible
fn blah(arg: u32) {}
blah(move y);
#[derive(Copy,Clone)]
struct Foo(u32, u32);
let z = Foo(0, 1);
let (a0, a1) = move z; Of course this likely conflicts with other uses of |
@aidancully I really don't see the utility of |
It seems to me that the move should actually be part of the pattern, as we are trying to overrides and "optimization" where patterns that would otherwise consume something don't. Also this allows |
Given that Infinite recursion can be avoided by either:
|
@Diggsey ... "sufficiently rare occurrence"? |
@eddyb It's rare if you continue to use old It seems like a fairly straightforward choice - either you special-case |
@Diggsey I thought the whole point of this proposal was to allow moving out of I should also mention that current |
What exactly is a partial move? I assume that if you move a non-copy value out of a non-copy value, it consumes the original. I agree with @Diggsey that the only Drop implementation that shouldn't easily consume
Certainly for backwards compatibility |
@Ericson2314 if you want to move out of |
@eddyb Sorry for the confusion, I am talking about the alternative of having The privacy-based rule would only allow one to "peel off" the destructor through destructuring when all fields are visible. |
OK, let me present an alternative which has
|
@Diggsey uh oh, all of those are pretty much hacks: I really can't see how that could be accepted without proper refinement types describing the partially-moved-out state, and those are even further out than |
@pnkfelix this RFC hasn't had any comments in a year, should we FCP? Could you summarise the status please? |
@nrc if we got |
Given that #1444 was accepted, this isn't very important. SmallVec can be implemented in a straightforward manner on top of a union. And it's possible to write a wrapper type which allows zero-overhead safe consume-on-drop:
|
@eefriedman I think moving-drop is still valuable because a) it accurately reflects the semantics of destruction and b) it confines all mandatory unsafety to |
Hear ye, hear ye! This RFC is now entering final comment period. The @rust-lang/lang team is inclined to close, since the addition of unsafe unions allows for things like the small vector use case to be handled that way, and in general there doesn't seem to be a great amount of urgency for this change. |
Could we close with the understanding that it could be revisited if we get |
For some definitions of "safe". I still support something like this RFC. Looking at current alternatives: I find neither the Option version nor the union version terribly ergonomic, in particular because they require |
My main motivation is, well, a belief that |
@Ericson2314 implementing drop glue by yourself sucks :( |
@ubsan hmm? I may have personally mused on a no-auto-drop-glue world :), but this RFC doesn't make it manual, especially that in the presence of partial moves. |
Is this a safe public interface? pub union NoDrop<T> {
inner: T,
}
impl<T> NoDrop<T> {
fn into_inner(self) -> T { /*...*/ }
}
impl<T> From<T> for NoDrop<T> { /*...*/ }
impl<T> Deref for NoDrop<T> {
type Target = T;
//...
}
impl<T> DerefMut for NoDrop<T> { /*...*/ } I think so, and I think it would fix the ergonomics issues I was talking about before. |
@Ericson2314 Afaict, any person that implements |
@ubsan hmm |
@Ericson2314 I guess I see it. Still seems... overcomplicated. |
I'm out of practice with Rust, but I'm not sure why @eefriedman's union is used? Is it just to prevent the interior type from implementing I think the trait DropConsumer: !DropGlue {
fn drop_consume(self);
}
struct ConsumeOnDrop<T: DropConsumer> {
t: T,
}
impl<T: DropConsumer> ConsumeOnDrop<T> {
fn new(t: T) -> Self {
ConsumeOnDrop { t: t }
}
}
impl<T: DropConsumer> Drop for ConsumeOnDrop<T> {
fn drop(&mut self) {
unsafe {
ptr::read(&self.t).drop_consume()
}
}
} I don't see this as that different than the RFC... There are still two types, but instead of being Either way, I won't have time in the foreseeable future to put serious effort into this or other RFCs, so I can't object to closing it. |
@aidancully union types don't have any drop glue. union Forget<T> { t: T }
Forget { t: ... }; is equivalent to std::mem::forget(...); |
@ubsan, Thanks for pointing that out, I didn't read the union RFC carefully enough and missed that. Using I think my point that consuming drop, while possible, would never become as ergonomic as |
I'm aware you feel this way, but I continue to think this is an exaggeration. From the point of view of the Rust type system guarantees alone, Certainly it's true that Drop marks a state transition, and if you consider an extended type, the extended permissions on drop are thus different from other methods that use None of this is to say that I think |
In that case I think that |
Agree we should close - we might want to consider something like this, but I think we should re-evaluate with unions, and after considering |
@Diggsey I think it's exactly equivalent. class This {
...
public:
...
~This() {
...
}
// not
~This() && {
...
}
}; |
Thanks everyone for the discussion. The @rust-lang/lang team has decided to close this RFC, as previously proposed. As @Ericson2314 noted, this proposal could be revisited in the future when we have more experience with |
rendered