Rust 1.29.0 изменил определение ManuallyDrop
. Раньше это был union
(с одним участником), но теперь это struct
и предмет lang. Роль элемента lang в компиляторе состоит в том, чтобы заставить тип не иметь деструктора, даже если он оборачивает тип, который имеет один раз.
Я попытался скопировать старое определение ManuallyDrop
(которое требует ночного режима, если не добавлена граница T: Copy
) и использовать его вместо значения из std
, и это позволило избежать проблемы (по крайней мере, для Игровая площадка * +1010 *). Я также попытался сбросить второй слот (slots[1]
) вместо первого (slots[0]
), и это тоже работает.
Несмотря на то, что я не смог воспроизвести проблему в своей системе (под управлением Arch Linux x86_64), я нашел кое-что интересное, используя 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`.
(Примечание: я могу получить ту же ошибку без использования Xargo, но тогда miri не показывает исходный код для стековых фреймов в std.)
Если я сделаю это снова с исходным определением ManuallyDrop
, то miri не сообщит ни о какой проблеме. Это подтверждает, что новое определение ManuallyDrop
приводит к тому, что ваша программа имеет неопределенное поведение .
Когда я изменяю std::mem::uninitialized()
на std::mem::zeroed()
, я могу надежно воспроизвести проблему. При исходной работе, если случается, что неинициализированная память имеет значение со всеми нулями, тогда вы получите проблему, в противном случае - нет.
Вызвав std::mem::zeroed()
, я заставил программу генерировать нулевые ссылки, которые задокументированы как неопределенное поведение в Rust . Когда вектор клонируется, используется итератор (как показано в выводе miri выше). Iterator::next
возвращает Option<T>
; что T
здесь имеет ссылку в нем (исходя из CountDrops
), что приводит к оптимизации макета памяти Option
: вместо дискретного дискриминанта он использует нулевую ссылку для представления значения None
, Поскольку я генерирую нулевые ссылки, итератор возвращает None
для первого элемента, и, таким образом, вектор оказывается пустым.
Интересно то, что когда ManuallyDrop
был определен как объединение, не оптимизировал макет памяти Option
.
оптимизирован.
println!("{}", std::mem::size_of::<Option<std::mem::ManuallyDrop<CountDrop<'static>>>>());
// prints 16 in Rust 1.28, but 8 in Rust 1.29
Об этой ситуации идет обсуждение в # 52898 .