-
Notifications
You must be signed in to change notification settings - Fork 12.7k
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
Untagged unions (tracking issue for RFC 1444) #32836
Comments
I may have missed it in the discussion on the RFC, but am I correct in thinking that destructors of union variants are never run? Would the destructor for the
|
@sfackler My current understanding is that |
So an assignment to a variant is like an assertion that the field was previously "valid"? |
@sfackler For |
Should a &mut union with Drop variants be a lint? On Friday, 8 April 2016, Scott Olson notifications@github.com wrote:
|
On April 8, 2016 3:36:22 PM PDT, Scott Olson notifications@github.com wrote:
I should have covered that case explicitly. I think both behaviors are defensible, but I think it'd be far less surprising to never implicitly drop a field. The RFC already recommends a lint for union fields with types that implement Drop. I don't think assigning to a field implies that field was previously valid. |
Yeah, that approach seems a bit less dangerous to me as well. |
Not dropping when assigning to a union field would make It's not a new problem, either; |
I personally don't plan to use Drop types with unions at all. So I'll defer entirely to people who have worked with analogous unsafe code on the semantics of doing so. |
I also don't intend to use Drop types in unions so either way doesn't matter to me as long as it is consistent. |
I don't intend to use mutable references to unions, and probably On Friday, 8 April 2016, Peter Atashian notifications@github.com wrote:
|
Seems like this is a good issue to raise up as an unresolved question. I'm not sure yet which approach I prefer. |
@nikomatsakis As much as I find it awkward for assigning to a union field of a type with Drop to require previous validity of that field, the reference case @tsion mentioned seems almost unavoidable. I think this might just be a gotcha associated with code that intentionally disables the lint for putting a type with Drop in a union. (And a short explanation of it should be in the explanatory text for that lint.) |
And I'd like to reiterate that (NB: the drop doesn't happen when But I support having a default warning against |
@tsion this is not true for fn main() {
let mut x: (i32, i32);
x.0 = 2;
x.1 = 3;
} (though trying to print |
@nikomatsakis That example is new to me. I guess I would have considered it a bug that that example compiles, given my previous experience. But I'm not sure I see the relevance of that example. Why is what I said not true for Say, if fn main() {
let mut x: (Box<i32>, i32);
x.0 = Box::new(2); // x.0 statically know to be uninit, destructor not called
x.0 = Box::new(3); // x.0 destructor is called before writing new value
} |
Maybe just lint against that kind of write? |
My point is only that On Tue, Apr 12, 2016 at 04:10:39PM -0700, Scott Olson wrote:
|
It runs the destructor if the drop flag is set. But I think that kind of write is confusing anyway, so why not just forbid it? You can always do |
@nikomatsakis I already mentioned that:
But I didn't account for dynamic checking of drop flags, so this is definitely more complicated than I considered. |
Drop flags are only semi-dynamic - after zeroing drop is gone, they are a part of codegen. I say we forbid that kind of write because it does more confusion than good. |
Should |
There is a valid use case for using a union to implement a |
As well as invoking such code manually via |
To me dropping a field value while writing to it is definitely wrong because the previous option type is undefined. Would it be possible to prohibit field setters but require full union replacement? In this case if the union implements Drop full union drop would be called for the value replaced as expected. |
I don't think it makes sense to prohibit field setters; most uses of unions should have no problem using those, and fields without a Drop implementation will likely remain the common case. Unions with fields that implement Drop will produce a warning by default, making it even less likely to hit this case accidentally. |
It's not obvious to me at all why this is the primary use case. |
@petrochenkov I didn't say "break the primary use case", I said "break primary use cases". FFI is one of the primary use cases of unions. |
There's certainly an attractive obviousness to a statement that "the possible values of a union are the union of the possible values of all its possible variants"... |
True. However, that's not the proposal -- we all agree that the following should be legal: union F {
x: (u8, bool),
y: (bool, u8),
}
fn foo() -> F {
let mut f = F { x: (5, false) };
unsafe { f.y.1 = 17; }
f
} Actually I think it is a bug that this even requires So, the union has to be taken bytewise, at least. |
I don't know about the new MIR-based unsafety-checker implementation, but in the old HIR-based one it was certainly a checker limitation/simplification - only expressions of the form |
Answering the comment in #52786 (comment): So the idea is that compiler still doesn't know anything about the I'm not sure though how exactly the part of First of all, regardless of fields being private or public, unexpected values cannot be written directly through those fields. You need something like a raw pointer, or code on the other side of FFI to do it, and it can be done without any field access, just by having a pointer to the whole union. So we need to approach this from some other direction than access to a field being restricted. As I interpret you comment, the approach is to say that a private field (in union or a struct, doesn't matter) implies an arbitrary invariant unknown to user, so any operations changing that field (directly or through wild pointers, doesn't matter) result in UB because they can potentially break that unspecified invariant. This means that if a union has a single private field, then its implementer (but not compiler) can assume that no third party will write an unexpected value into that union. If some union wants to prohibit unexpected values while still providing @RalfJung How scenarios like this are treated? mod m {
union MyPrivateUnion { /* private fields */ }
extern {
fn my_private_ffi_function() -> MyPrivateUnion; // Can return garbage (?)
}
} |
No, that is not what I meant. There are multiple invariants. I do not know how many we will need, but there will be at least two (and I don't have great names for them):
It would be nice to align these two concepts, but I do not think it is practical. First of all, for some types (function pointers, dyn traits), the definition of the custom, semantic invariant actually uses the definition of UB in the language. This definition would be circular if we wanted to say that it is UB to ever violate the custom, semantic invariant. Secondly, I'd prefer if the definition of our language, and whether a certain execution trace exhibits UB, was a decidable property. Semantic, custom invariants are frequently not decidable.
Essentially, when a type chooses its custom invariant, it has to make sure that anything that safe code can do preserves the invariant. After all, the promise is that just using this type's safe API can never lead to UB. This is applies to both structs and unions. One of the things safe code can do is access public fields, which is where this connection comes from. For example, a public field of a struct cannot have a custom invariant that is different from the custom invariant of the field type: After all, any safe user could write arbitrary data into that field, or read form the field and expect "good" data. A struct where all fields are public can be safely constructed, placing further restrictions on the field. A union with a public field... well that's somewhat interesting. Reading union fields is unsafe anyway, so nothing changes there. Writing union fields is safe, so a union with a public field has to be able to handle arbitrary data which satisfies that field's type's custom invariant being put into the field. I doubt this will be very useful... So, to recap, when you choose a custom invariant, it is your responsibility to make sure that foreign safe code cannot break this invariant (and you have tools like private fields to help you achieve this). It is the responsibility of foreign unafe code to not violate your invariant when that code does something safe code could not do.
Correct. (panic-safety is a concern here but you are probably aware). This is just like, in let sz = self.size;
self.size = 1337;
self.size = sz; and there is no UB. mod m {
union MyPrivateUnion { /* private fields */ }
extern {
fn my_private_ffi_function() -> MyPrivateUnion; // Can return garbage (?)
}
} In terms of the syntactic layout invariant, |
I finally wrote that blog post about whether and when |
Is there anything left to track here that’s not already covered by #55149, or should we close? |
E0658 still points here:
|
This currently plays terribly with atomics, since they do not implement |
When #55149 is implemented, you’ll be able to use |
With that implemented, you shouldn't even need |
Assigning myself to switch the tracking issue to the new one. |
…plett unions: test move behavior of non-Copy fields This test ensures the behaviors suggested by @petrochenkov [here](rust-lang#32836 (comment)).
Tracking issue for rust-lang/rfcs#1444.
Unresolved questions:
Copy
for a union? For example, what if some variants are of non-Copy type? All variants?Open issues of high import:
The text was updated successfully, but these errors were encountered: