(Taken from #69 (comment).)
How do we feel about this code -- UB or not?
fn main() { unsafe {
let mut x = Some(&0);
match x {
Some(ref mut b) => {
let u = b as *mut &i32 as *mut usize;
// Just writing into a *mut u8
*u = 0;
}
None => panic!(),
}
assert!(x.is_some());
} }
This "magically" changes the discriminant. OTOH, it is very similar to
fn main() { unsafe {
let mut x = Some(&0);
(&mut x as *mut _ as *mut usize) = 0;
assert!(x.is_some());
} }
which is definitely allowed (it makes assumptions about layout, but we do guarantee layout of Option<&T>.
Other example:
fn main() { unsafe {
let mut x = Some(true);
match x {
Some(ref mut b) => {
let u = b as *mut bool as *mut u8;
// Just writing into a *mut u8
*u = 2;
}
None => panic!(),
}
assert!(x.is_some());
} }
#![feature(rustc_attrs)]
#[rustc_layout_scalar_valid_range_start(1)]
#[repr(transparent)]
pub(crate) struct NonZero(u32);
fn main() { unsafe {
let mut x = Some(NonZero(1));
match x {
Some(NonZero(ref mut c)) => {
// Just writing 0 into an &mut u32
*c = 0;
}
None => panic!(),
}
assert!(x.is_some());
} }
Relevant questions:
- Is changing the discriminant through a pointer derived from an enum variant okay if there's never again an access with the outer pointer? In other words, does this act like a protector where "invalidating" the discriminant seen by parent pointers is immediate UB? If no, there are still two variants: one where the discriminant can be "repaired" again (i.e. we just check the discriminant again when the parent pointer is used), and one where it is permanently invalidated (when overwriting the discriminant the parent pointer becomes unusable but the child pointers remain usable). This was brought up here.
(Taken from #69 (comment).)
How do we feel about this code -- UB or not?
This "magically" changes the discriminant. OTOH, it is very similar to
which is definitely allowed (it makes assumptions about layout, but we do guarantee layout of
Option<&T>.Other example:
Relevant questions: