Rust 1.29.0 changed the definition of ManuallyDrop
. It used to be a union
(with a single member), but now it's a struct
and a lang item. The role of the lang item in the compiler is to force the type to not have a destructor, even if it wraps a type that has once.
I tried copying the old definition of ManuallyDrop
(which requires nightly, unless a T: Copy
bound is added) and using that instead of the one from std
, and it avoids the issue (at least on the Playground). I also tried dropping the second slot (slots[1]
) instead of the first (slots[0]
) and that also happens to work.
Although I haven't been able to reproduce the problem natively on my system (running Arch Linux x86_64), I found something interesting by using miri:
francis@francis-arch /data/git/miri master
$ MIRI_SYSROOT=~/.xargo/HOST cargo run -- /data/src/rust/so-manually-drop-1_29/src/main.rs
Finished dev [unoptimized + debuginfo] target(s) in 0.03s
Running `target/debug/miri /data/src/rust/so-manually-drop-1_29/src/main.rs`
error[E0080]: constant evaluation error: attempted to read undefined bytes
--> /home/francis/.rustup/toolchains/nightly-2018-09-15-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/src/liballoc/vec.rs:1903:32
|
1903 | for element in iterator {
| ^^^^^^^^ attempted to read undefined bytes
|
note: inside call to `<std::vec::Vec<T> as std::vec::SpecExtend<T, I>><MayContainValue<CountDrop>, std::iter::Cloned<std::slice::Iter<MayContainValue<CountDrop>>>>::spec_extend`
--> /home/francis/.rustup/toolchains/nightly-2018-09-15-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/src/liballoc/vec.rs:1953:9
|
1953 | self.spec_extend(iterator.cloned())
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
note: inside call to `<std::vec::Vec<T> as std::vec::SpecExtend<&'a T, I>><MayContainValue<CountDrop>, std::slice::Iter<MayContainValue<CountDrop>>>::spec_extend`
--> /home/francis/.rustup/toolchains/nightly-2018-09-15-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/src/liballoc/vec.rs:1402:9
|
1402 | self.spec_extend(other.iter())
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
note: inside call to `<std::vec::Vec<T>><MayContainValue<CountDrop>>::extend_from_slice`
--> /home/francis/.rustup/toolchains/nightly-2018-09-15-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/src/liballoc/slice.rs:168:9
|
168 | vector.extend_from_slice(s);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^
note: inside call to `std::slice::hack::to_vec::<MayContainValue<CountDrop>>`
--> /home/francis/.rustup/toolchains/nightly-2018-09-15-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/src/liballoc/slice.rs:369:9
|
369 | hack::to_vec(self)
| ^^^^^^^^^^^^^^^^^^
note: inside call to `std::slice::<impl [T]><MayContainValue<CountDrop>>::to_vec`
--> /home/francis/.rustup/toolchains/nightly-2018-09-15-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/src/liballoc/vec.rs:1687:9
|
1687 | <[T]>::to_vec(&**self)
| ^^^^^^^^^^^^^^^^^^^^^^
note: inside call to `<std::vec::Vec<T> as std::clone::Clone><MayContainValue<CountDrop>>::clone`
--> /data/src/rust/so-manually-drop-1_29/src/main.rs:54:33
|
54 | assert_eq!(slots.len(), slots.clone().len());
| ^^^^^^^^^^^^^
note: inside call to `tests::check_drops`
--> /data/src/rust/so-manually-drop-1_29/src/main.rs:33:5
|
33 | tests::check_drops();
| ^^^^^^^^^^^^^^^^^^^^
error: aborting due to previous error
For more information about this error, try `rustc --explain E0080`.
(Note: I can get the same error without using Xargo, but then miri doesn't show the source code for the stack frames in std.)
If I do this again with the original definition of ManuallyDrop
, then miri doesn't report any issue. This confirms that the new definition of ManuallyDrop
causes your program to have undefined behavior.
When I change std::mem::uninitialized()
to std::mem::zeroed()
, I can reliably reproduce the issue. When running natively, if it happens that the uninitialized memory is all zeroes, then you'll get the issue, otherwise you won't.
By calling std::mem::zeroed()
, I've made the program generate null references, which are documented as undefined behavior in Rust. When the vector is cloned, an iterator is used (as shown in miri's output above). Iterator::next
returns an Option<T>
; that T
here has a reference in it (coming from CountDrops
), which causes Option
's memory layout to be optimized: instead of having a discrete discriminant, it uses a null reference to represent its None
value. Since I am generating null references, the iterator returns None
on the first item and thus the vector ends up empty.
What's interesting is that when ManuallyDrop
was defined as a union, Option
's memory layout was not optimized.
println!("{}", std::mem::size_of::<Option<std::mem::ManuallyDrop<CountDrop<'static>>>>());
// prints 16 in Rust 1.28, but 8 in Rust 1.29
There is a discussion about this situation in #52898.