-
Notifications
You must be signed in to change notification settings - Fork 390
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
MaybeUninit
has an "uninit" variant, not an "undefined" one
#198
Comments
Maybe I misunderstand your point, but the use std::mem::MaybeUninit;
fn main() {
let x = MaybeUninit::<u8>::uninit();
unsafe {
if x.assume_init() == 0 {
dbg!("x is zero");
}
}
} gives on my machine with Rust 1.81-nightly
In C lingo, the result of accessing a |
Closing due to inactivity. |
Calling Your example is no different to this: fn main() {
let x = 0;
unsafe {
if std::num::NonZeroU8::new_unchecked(0) == 1 {
dbg!("x is one");
}
}
} Here
|
Let me start off by saying I think there is merit to the argument that the union variant However, pub const unsafe fn assume_init(self) -> T {
unsafe {
intrinsics::assert_inhabited::<T>();
ManuallyDrop::into_inner(self.value)
}
} Likewise, reading the uninitialized value by other means also gives UB: use std::mem::{transmute, MaybeUninit};
fn main() {
let x: u8 = {
let m = MaybeUninit::<u8>::uninit();
unsafe { transmute(m) }
};
if x == 0 {
dbg!(x); // Crash
}
} Now from the perspective of this cheat sheet and a condensed info graphic, I still think the current depiction is better than the alternative of showing it as merely any other variant: There is really only one way to get that variant |
I'm not exactly sure what you mean by that? Uninit is an invalid value for most types, similarly to how
Note that it's not just any access, it's a union field access. Accessing union fields is equivalent to a You, once again, can do exactly the same with use std::num::NonZeroU8;
#[repr(u8)]
#[derive(Copy, Clone)]
enum ZeroU8 { Zero = 0u8 }
union MaybeZeroU8 {
zero: ZeroU8,
non_zero: NonZeroU8,
}
impl MaybeZeroU8 {
fn zero() -> Self {
Self { zero: ZeroU8::Zero }
}
unsafe fn assume_non_zero(self) -> NonZeroU8 {
unsafe { self.non_zero }
}
}
fn main() {
let zero = MaybeZeroU8::zero();
unsafe { zero.assume_non_zero() }; // UB
} You can also make a copy of
Transmute and union access are quite literally equivalent, so this is expected.
Not really! You can get uninit memory by reading padding bytes in a structure, for example1: fn main() {
#[repr(C, align(2))]
struct Padded {
__: u8,
}
let p = Padded { __: 1 };
let mu = unsafe {
std::ptr::from_ref(&p)
.cast::<core::mem::MaybeUninit<u8>>()
.add(1)
.read()
};
eprintln!("until this point miri doesn't complain, this is sound!");
// (miri not complaining doesn't *guarantee* soundness, but I do believe this code is sound)
// ...and this is not sound at all,
// since it assumes uninit memory is init
unsafe { mu.assume_init() };
}
I'm not sure what you mean by "you can't really get uninit back" (you can certainly override an initialized Yet, I do not see how this makes it "cursed". You can't observe lots of stuff. Zero sized types for example. But I don't think zero sized types are cursed either? Handling Ig what I'm trying to say, Footnotes
|
I mean that
Yes, agreed.
I disagree with that view. I mean, you are right that in this particular case you can summon a I also disagree for ontological reasons. If I knew nothing of the inside of a
Yes! The target audience should have some familiarity with C-level concepts (memory, pointers) (compare FAQ). What I'm now trying to convey in that picture with 10 words of text or so:
As you say, if I had much more space I'd elaborate that undefined doesn't really exist anywhere as a property of space, but rather as a property of compilation, in that realistically (from an "machine code perspective") a program using a *) FWIW, I just realized that when I mean read I imply trying to do something meaningful with that value other than continuing treating it as an undefined value. While technically copying a struct with padding might-or-might-not involve copying (reading) some uninitialized bytes, that's an implementation detail in my book, not a read-access.
I agree that there are other things that either might cause UB, or are unobservable. The cursed visualization in this particular case comes from that
Yes, I agree with all of that, and I even agree that instead rendering it as I think the bottom line of my point is that in the scope of type visualizations it's a reasonably appropriate warning about a severe issue that many people get wrong, while being correct enough as to showing the type layout and its primary behavior. |
MaybeUninit
does not have an " Undefine̻̅ḓ̓ " variant (as a side note, this might be terrible for screen readers / accessibility anyway). It has an "uninit" variant.I would say that is an important distinction, "undefined" seems mysterious and possibly not reproducible (i.e. "undefined" value may have different value each time it is observed), but actually "uninit" is just a single value that exists in the Rust Abstract Machine ™️ (if I'm not mistaken a "byte" is defined as either
uninit
or a0..=255
integer value).So while you don't know how an
uninit
variant is represented on the hardware, this does not actually matter, since you can't introspect it from within the abstract machine.uninit
is just a value, which is invalid for most operations.So I think explaining it as "undefined" and "cursed" is not the best way.
(P.S. haven't done much with unsafe lately, so I'm not sure if terminology is correct here, but I believe the idea is)
The text was updated successfully, but these errors were encountered: