Skip to content
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

Union initialization and Drop #2514

Merged
merged 12 commits into from
Oct 17, 2018

Conversation

RalfJung
Copy link
Member

@RalfJung RalfJung commented Aug 3, 2018

This RFC realizes the second part of https://internals.rust-lang.org/t/pre-rfc-unions-drop-types-and-manuallydrop/8025: Changing unions to no longer allow fields with drop glue, and otherwise describing what we need to settle before unions can be fully stabilized. Unfortunately this got somewhat more complicated than I thought.

As usual I had trouble separating things between the guide-level and the reference-level explanation; I hope this makes sense!

Rendered
Tracking issue


// We can write into uninitialized inner fields:
u.f2.1 = S(42);
let _ = &u.f2.1; // This field is initialized now.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let _ is a bit of a footgun in terms of testing initialization.
_ pattern doesn't access the right side at all, so even something like this

    let x: u8;
    let _ = x;

successfully compiles.
You need let _y = x; or just x; to test for x being initialized.

Copy link
Member Author

@RalfJung RalfJung Aug 3, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oops thanks!

EDIT: And fixed.

let _ = &u.f2.0;

// Equivalently, we can assign the entire union:
u = U { f2: S(42) };
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't type-check; f2 has type (S, S).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, I also aligned the unions in the two examples.

@scottmcm scottmcm added the T-lang Relevant to the language team, which will review and decide on the RFC. label Aug 3, 2018
# Summary
[summary]: #summary

Unions do not allow fields of types that require drop glue, but they may still
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: "drop glue" is such a rustc-specific jargon, perhaps "trivial destructor drop", or "drop not running any code", or something like this could be better.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think "(doesn't) need(s) drop" or "noop drop" could also work as terms.

Copy link
Member Author

@RalfJung RalfJung Aug 6, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't we have a term for "the code that runs when something is dropped"? I feel that is not such a strange rustc-specific concept, all Rust implementations and also e.g. C++ have it.

Personally, I find "noop drop" much less clear than "does not have drop glue".^^

(which this RFC adapts from @petrochenkov's proposal) can sometimes be a little
surprising when looking at individual fields: Whether `u.f2 = ...;` drops
depends on whether `u.f1` has been previously initialized. @petrochenkov hence
proposes a lint to warn people that unions with drop-glue fields are not always
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The lint was inherited from the original RFC rather than proposed by me :)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, sorry. Fixed :)

the rules that are currently only applied to unions that `impl Drop`. However,
that does not actually help with the pitfall described above. The more complex
rules allow more code that many will reasonably expect to work, and do not seem
to introduce any additional pitfalls.
Copy link
Contributor

@petrochenkov petrochenkov Aug 3, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Independent nested fields borrowed independently is probably the most natural expectation:

union U { a: (u8, u8), b: u16 }

let x = &u.a.0;
let y = &u.a.1;

so it's borrow checker that needs to work in per-field fashion first of all.
Move checker just mirrors what borrow checker does (for consistency and also because they share common infrastructure in the compiler).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, I hadn't thought about borrowck here. So with the rules as stated, once a field is (partially) borrowed, its siblings all become completely blocked from borrowing?

Borrowing is unsafe, so this would not be necessary, but it still seems useful.

@petrochenkov
Copy link
Contributor

LGTM.
It would be nice to see some practical code ported from Drop fields to ManuallyDrop though to estimate impact on ergonomics and readability.

This RFC doesn't seem to prevent reintroducing Drop fields in uncertain future if the need arises, so we don't lose anything by adopting it.

@dlight
Copy link

dlight commented Aug 4, 2018

The RFC summary should probably link to something that explains what a drop glue is.

(The Drop chapter of the Rust book doesn't contain the word "glue")

@RalfJung
Copy link
Member Author

RalfJung commented Aug 6, 2018

It would be nice to see some practical code ported from Drop fields to ManuallyDrop though to estimate impact on ergonomics and readability.

What would be a good example?

This RFC doesn't seem to prevent reintroducing Drop fields in uncertain future if the need arises, so we don't lose anything by adopting it.

Yes. (I avoided calling them "Drop fields" because a field can need drop glue even if it does not impl Drop -- e.g. if it is a struct and one of its fields has impl Drop.)

The RFC summary should probably link to something that explains what a drop glue is.

I added a brief explanation.

@joshtriplett
Copy link
Member

@rfcbot merge

Based on discussion in the lang team and with Ralf, I think this is ready to merge. Fields in unions that impl Drop have never been part of stable Rust, so this isn't a breaking change, and this fixes various concerns and painful corner cases that kept coming up.

@rfcbot
Copy link
Collaborator

rfcbot commented Aug 9, 2018

Team member @joshtriplett has proposed to merge this. The next step is review by the rest of the tagged teams:

Concerns:

Once a majority of reviewers approve (and none object), this will enter its final comment period. If you spot a major issue that hasn't been raised at any point in this process, please speak up!

See this document for info about what commands tagged team members can give me.

@rfcbot rfcbot added proposed-final-comment-period Currently awaiting signoff of all team members in order to enter the final comment period. disposition-merge This RFC is in PFCP or FCP with a disposition to merge it. labels Aug 9, 2018
}
{
let u = U { f1: ManuallyDrop::new(Vec::new()) };
foo(u.f2);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks like moving out of a union that implements Drop-- how is this different from the let v = u.f1; case above?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are right, I will fix the example.

The only way to deinitialize a union with drop is to move away the entire thing, just like with structs.

This requires `unsafe` because it desugars to `ManuallyDrop::deref_mut(&mut u.f).0`,
and while writing to a union field is safe, taking a reference is not.

For this reason, `DerefMut` auto-deref is not applied when working on a union or
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why ban just auto-deref rather than banning DerefMut entirely? Users could access the nested types using .as_ptr() or .as_mut_ptr() methods.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I felt that was too drastic, but sure.
Probably calling deref_mut manually is also still okay, just the sugar that lets you use * is not?

I also do not know what in technically feasible in this space.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I'm not sure what the right balance is. You want to allow the deref when the value has been initialized, but ban it as much as possible in cases where it creates an &mut T to a partially or wholly uninitialized T.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Uh, I think I'd rather avoid such stateful lints. They are not complete enough IMHO (they cannot know which field you are now allowed to create a reference to).

@pnkfelix
Copy link
Member

pnkfelix commented Sep 27, 2018

@rfcbot concern pnkfelix-wants-a-chance-to-read-this-before-he-checks-his-box

I've been swamped, but this is an area I sort of care about and I want to read this

@rfcbot rfcbot added proposed-final-comment-period Currently awaiting signoff of all team members in order to enter the final comment period. disposition-merge This RFC is in PFCP or FCP with a disposition to merge it. labels Sep 27, 2018
@rfcbot rfcbot added the final-comment-period Will be merged/postponed/closed in ~10 calendar days unless new substational objections are raised. label Oct 4, 2018
@rfcbot
Copy link
Collaborator

rfcbot commented Oct 4, 2018

🔔 This is now entering its final comment period, as per the review above. 🔔

@rfcbot rfcbot removed the proposed-final-comment-period Currently awaiting signoff of all team members in order to enter the final comment period. label Oct 4, 2018

// Rejected
union Example4<T> {
// `T` might have drop glue, and then `Cell<T>` would as well.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this mention RefCell<T>?

Copy link
Member Author

@RalfJung RalfJung Oct 11, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why that? I didn't intend to list every type with interior mutability.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Specifically because the line below is f1: RefCell<T>. I assumed this comment was here to explain why, given that line, this struct is rejected.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, I made a typo and didn't use the same type on both sides. Thanks for pointing that out! (GitHub's diff viw for comments embedded in the discussions is so bad...)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No problem - sorry I didn't point it out better initially! I'm still not entirely used to how github displays these things either.

@crlf0710
Copy link
Member

Sorry it's a bit late, but in prior art section: Just want to point out that actually C++ does have destructors for unions. (See https://en.cppreference.com/w/cpp/language/union). Maybe a small edit to the text will be good.

@RalfJung
Copy link
Member Author

@crlf0710 Thanks for pointing that out! I fixed the text. Do you agree it is correct now?

@crlf0710
Copy link
Member

@RalfJung Looks good!

@SimonSapin
Copy link
Contributor

Would this auto trait be an accurate expression of the “has no drop glue” concept?

pub auto trait NoDropGlue {}
impl<T> !NoDropGlue for T where T: Drop {}

If it is and we make it a lang item (in core::marker), generic unions could be extended to allow fields that are not necessarily Copy:

union Foo<T: NoDropGlue> {
    f: T,
}

@RalfJung
Copy link
Member Author

I think it would begin to express that concept if it worked, but AFAIK those kind of negative bounds don't work...at least I recall that was the result of someone checking last time.

We'd however also need

impl<T> NoDropGlue for ManuallyDrop<T>

and then what about unions themselves? Do they get auto traits the same way everything else does, or are auto traits just never implemented for them? Either way we'd want NoDropGlue implemented for all unions

@SimonSapin
Copy link
Contributor

Good points. So leaving aside the auto-trait definition and assuming it can be implemented with ~compiler magic~ instead, would it be desirable for this trait to exist to enable such generic unions?

@RalfJung
Copy link
Member Author

RalfJung commented Oct 13, 2018

I would think so, yes. Copy has always been a bad approximation. IIRC either @eddyb or @nikomatsakis had other uses for such a trait as well.

However, introducing such a trait is firmly out of scope for this PR.

@crlf0710
Copy link
Member

i think the “has no drop glue” approximately corresponds to std::mem::needs_drop which is implemented using a intrinsic. As the documentation says it's conservative, so... maybe it will be a matter of making it accurate and const fn... Maybe it's also a good idea to lift it to a trait(that's a common problem for all const fns, i think).

@rfcbot
Copy link
Collaborator

rfcbot commented Oct 14, 2018

The final comment period, with a disposition to merge, as per the review above, is now complete.

@rfcbot rfcbot added finished-final-comment-period The final comment period is finished for this RFC. and removed final-comment-period Will be merged/postponed/closed in ~10 calendar days unless new substational objections are raised. labels Oct 14, 2018
@Centril Centril merged commit e5276df into rust-lang:master Oct 17, 2018
@Centril
Copy link
Contributor

Centril commented Oct 17, 2018

Huzzah! This RFC has been merged!

Tracking issue: rust-lang/rust#55149

@Centril Centril added A-unions Proposals relating to union items A-drop Proposals relating to the Drop trait or drop semantics A-typesystem Type system related proposals & ideas A-expressions Term language related proposals & ideas A-data-types RFCs about data-types labels Nov 23, 2018
@RalfJung RalfJung deleted the union-initialization-and-drop branch September 16, 2019 18:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-data-types RFCs about data-types A-drop Proposals relating to the Drop trait or drop semantics A-expressions Term language related proposals & ideas A-typesystem Type system related proposals & ideas A-unions Proposals relating to union items disposition-merge This RFC is in PFCP or FCP with a disposition to merge it. finished-final-comment-period The final comment period is finished for this RFC. T-lang Relevant to the language team, which will review and decide on the RFC.
Projects
None yet
Development

Successfully merging this pull request may close these issues.