-
Notifications
You must be signed in to change notification settings - Fork 15
Passing safe references to empty enums #2
Comments
I think it should definitely be possible to include such an enum in a struct, especially considering the fact that
|
Personally, I'd argue that we should follow Weeping Angels rules here :P
In particular, a reference's lifetime contract is that it can only exist for a lifetime strictly included in the referent's lifetime. As one cannot construct an instance of an empty type, no value of such a type can have a non-empty lifetime. Constructing a reference with an invalid lifetime is (I think?) UB. QED. I personally feel that generic parameters are a red herring here: In the absence of Creating such a reference with
Other options include creating a reference from a pointer with invalid pointee (UB), or transmuting a reference of another type (which I think is, or at least should be, UB). What I think should be trivially valid are raw pointers to empty-typed values (which can be used to refer to opaque FFI types), which can be meaningfully newtyped (for external use) or retyped and then reference-ized/dereferenced (for internal use). |
This is equivalent creating references to invalid enum variants, e.g. enum Foo {
X, Y
}
fn main() {
let data: *const u32 = &42;
let illegal = unsafe { &*(data as *const Foo) };
} I am still not sure whether to make this UB. dereferencing such a pointer is obviously UB, through. |
Can you say more about why you feel this should be UB? I am not persuaded. =) I agree that dereferencing of trying to load from such a pointer (at least without transmuting it back...) seems problematic, but I'm not sure that creating a pointer is bad. That said, if the original intention of this pattern is to make something that can't be created, it seems like privacy could do that more easily and somewhat less problematically. =) |
As the person who asked the question, I can clarify the original goal of this question. I want to have an object Foo which has a representation in C++, which is opaque to the Rust code. Consumers of my library would be defining The type I originally tried to implement this by creating a wrapper type around a private member: #[repr(C)]
pub struct Foo {
_prevent_construction: (),
} However, when consumers would put this type in extern "C" {
fn pass_a_foo(foo: *const Foo);
} the enum Impossible {}
#[repr(C)]
pub struct Foo {
_prevent_construction: Impossible,
} and simply make sure that within the module where this type was defined (which is littered with unsafe code anyways), references to |
That sounds like an issue with the |
I agree that this would be best accomplished by changing the improper_ctypes lint, I was just trying to explain the motivation. I still am curious as to whether or not it's safe, whether or not it is the best solution to the problem at hand. |
@nikomatsakis: I was referring to transmuting the value, not the pointer to the value, and so it runs afoul of "actually constructing an instance of an empty type". Converting the pointer and then making a reference from that runs afoul of the lifetime issue at the top of my post. @mystor: Out of curiousity, why use EDIT: Ah, nevermind, misunderstood @mystor's goals. |
Is it possible to invoke Undefined Behavior in this program without modifying the module |
Yes, it allows you to trigger UB from safe code, but this is fine since you need unsafe code to create such a reference in the first place. It is your responsibility to ensure that you do not leak this reference to safe code outside your module. With that said, I think simply manipulating the reference without dereferencing it (inside your unsafe module) should not be UB. |
But can you trigger Undefined Behavior from the outside module? The outside module cannot see the empty enum due to privacy, and thus cannot match on it. I'm handing the reference to safe code, but the safe code can't do anything with the reference, so I think it might be okay. |
Hmm I think that would allow UB since you can do |
I don't think that's possible. Bar is not Copy or Clone, so it can't be copied out. |
What that says to me (modulo @nikomatsakis' point about Tootsie-Pop models) is that type violations probably should not be permitted to exit the Anything else pushes us quite far towards Tootsie-Pop models, which have very nontrivial effects on the compiler's ability to optimize safe code ("the unchecked get use case"). |
I agree with @eternaleye's original comment. References ( (Requiring (or hypothesizing) a value of such a type, that is |
Is this UB? (if so, we might want to fix it...) https://doc.rust-lang.org/src/core/up/src/libcore/fmt/mod.rs.html#185 |
According to many of the arguments here, the answer is yes. It is currently not optimized as UB afaik, so it should be OK for now. |
@mystor I would agree. Just pointing out that it's used in real code. |
That use seems like another use case for my opaque struct idea (https://internals.rust-lang.org/t/pre-rfc-opaque-structs/4005). It could also just use raw pointers and a PhantomData however. |
cc rust-lang/rust#36976 - the plain old "reference to undef" case is also interesting. |
Revisiting my comment from earlier....
I'm struck anew by what treacherous ground this question of "should it be legal, or should it be undefined behavior" really is. When other people express the sentiment that some particular For what it's worth, I think my feelings on the subject attach more strongly to uninhabited types than to reference types. A live value of an uninhabited type is the very definition of undefined behavior (like GCC's __builtin_unreachable), what sound type systems everywhere exist to preclude, and the clarity of this really should not be muddied. If this isn't undefined behavior, then nothing is. On the other hand, if we say that in the presence of |
Merging rust-lang/rust#36449 has kind of answered the question raised here, didn't it? Everything |
No. We don't use |
This is now basically being discussed at rust-lang/unsafe-code-guidelines#77: does a reference always have to have a pointee that is valid for its type, or does it "just" have to be aligned and dereferencable? |
On IRC, @mystor asked me whether it would be illegal to pass around
&SafeType
or&mut SafeType
given this definition:In particular, is it ok to have an empty enum "by-value" in a struct in this fashion?
The text was updated successfully, but these errors were encountered: