fn main() {
let mut one = One {_f1: false, _f2: 0};
let ptr: *mut Two = &mut one as *mut One as *mut Two;
unsafe {
ptr.write(Two {_f1: true, _f2: 1});
}
println!("{:?}", one);
}
#[derive(Debug)]
struct One {
_f1: bool,
_f2: u32
}
#[derive(Debug)]
struct Two {
_f1: bool,
_f2: u32
}
You're right. I didn't mark my structs as repr(C) or repr(transparent) in my sample code, so let's remove the incorrect assumption the layout is the same for both.
So how do you know if the layouts are the same? For repr(C) types and repr(transparent) types, layout is precisely defined. But for your run-of-the-mill repr(Rust), it is not. Even different instances of the same generic type can have wildly different layout.
Just to be sure, run it a few times with randomized layout. Note that Miri doesn't have false positives, but it can have false negatives.
I don't think this is sound. Altough Rust defines bool as u8 and 0/1 values (which is more specific than in C), this is still not enough. The compiler reserves the right to reorder fields, and you're assuming that bool with padding will match layout of u32, but compiler could put padding either before or after the bool, and the 1 byte of u32 will be in a different place depending on endian.
(Same thing as what @kornel just said with more words.)
The potentially different layouts means it's unsound:
If they have different sizes or alignments or padding locations, say
If you end up writing a non-boolean value to the bool field byte (consider platform endianness too)
If you end up writing an uninitialized padding byte to any part of the u32 field
Even if you add some asserts about the size and alignment (which should compile out if your assumptions hold), the example (wherein the u32 only has 0 or 1 bytes regardless of endianness) is still unsound because these are all valid layouts...
bool [pad] [pad] [pad] four-u32-bytes
[pad] bool [pad] [pad] four-u32-bytes
[pad] [pad] bool [pad] four-u32-bytes
[pad] [pad] [pad] bool four-u32-bytes
// Same thing but with u32 on the front
...and so you could write a padding byte with an uninitialized value into one of the field bytes in the other struct.
error: Undefined Behavior: using uninitialized data, but this operation requires initialized memory
--> /playground/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/fmt/num.rs:466:5
|
466 | / impl_Display!(
467 | | i8, u8, i16, u16, i32, u32, i64, u64, usize, isize
468 | | as u64 via to_u64 named fmt_u64
469 | | );
| |_____^ using uninitialized data, but this operation requires initialized memory
|
= help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
= help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
= note: BACKTRACE:
= note: inside `core::fmt::num::imp::<impl std::fmt::Display for u32>::fmt` at /playground/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/fmt/num.rs:284:38: 284:43
= note: inside `core::fmt::num::<impl std::fmt::Debug for u32>::fmt` at /playground/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/fmt/num.rs:195:21: 195:47
= note: inside closure at /playground/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/fmt/builders.rs:150:17: 150:36
= note: inside `std::result::Result::<(), std::fmt::Error>::and_then::<(), {closure@std::fmt::DebugStruct<'_, '_>::field::{closure#0}}>` at /playground/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/result.rs:1320:22: 1320:27
= note: inside `std::fmt::DebugStruct::<'_, '_>::field` at /playground/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/fmt/builders.rs:133:23: 152:11
= note: inside `std::fmt::Formatter::<'_>::debug_struct_field2_finish` at /playground/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/fmt/mod.rs:1926:9: 1926:37
note: inside `<One as std::fmt::Debug>::fmt`
--> src/main.rs:13:10
|
13 | #[derive(Debug)]
| ^^^^^
= note: inside `core::fmt::rt::Argument::<'_>::fmt` at /playground/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/fmt/rt.rs:142:9: 142:40
= note: inside `std::fmt::write` at /playground/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/fmt/mod.rs:1117:17: 1117:40
= note: inside `<std::io::StdoutLock<'_> as std::io::Write>::write_fmt` at /playground/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/io/mod.rs:1762:15: 1762:43
= note: inside `<&std::io::Stdout as std::io::Write>::write_fmt` at /playground/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/io/stdio.rs:727:9: 727:36
= note: inside `<std::io::Stdout as std::io::Write>::write_fmt` at /playground/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/io/stdio.rs:701:9: 701:33
= note: inside `std::io::stdio::print_to::<std::io::Stdout>` at /playground/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/io/stdio.rs:1020:21: 1020:47
= note: inside `std::io::_print` at /playground/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/io/stdio.rs:1097:5: 1097:37
note: inside `main`
--> src/main.rs:10:5
|
10 | println!("{:?}", one);
| ^^^^^^^^^^^^^^^^^^^^^
= note: this error originates in the macro `impl_Display` which comes from the expansion of the macro `println` (in Nightly builds, run with -Z macro-backtrace for more info)