在Rust 1.29.0中,我的一个测试开始失败.我设法把这个奇怪的错误归结到这个例子:

#[derive(Clone, Debug)]
struct CountDrop<'a>(&'a std::cell::RefCell<usize>);

struct MayContainValue<T> {
    value: std::mem::ManuallyDrop<T>,
    has_value: u32,
}

impl<T: Clone> Clone for MayContainValue<T> {
    fn clone(&self) -> Self {
        Self {
            value: if self.has_value > 0 {
                self.value.clone()
            } else {
                unsafe { std::mem::uninitialized() }
            },
            has_value: self.has_value,
        }
    }
}

impl<T> Drop for MayContainValue<T> {
    fn drop(&mut self) {
        if self.has_value > 0 {
            unsafe {
                std::mem::ManuallyDrop::drop(&mut self.value);
            }
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    #[test]
    fn check_drops() {
        let n = 2000;
        let drops = std::cell::RefCell::new(0usize);

        let mut slots = Vec::new();
        for _ in 0..n {
            slots.push(MayContainValue {
                value: std::mem::ManuallyDrop::new(CountDrop(&drops)),
                has_value: 1,
            });
        }

        unsafe { std::mem::ManuallyDrop::drop(&mut slots[0].value); }
        slots[0].has_value = 0;

        assert_eq!(slots.len(), slots.clone().len());
    }
}

我知道代码看起来很奇怪;这完全是断章取义.我在Rust 1.29.0的64位Ubuntu上用cargo test重现了这个问题.一位朋友无法在同样Rust 版本的Windows上复制.

其他阻止繁殖的因素:

  • n降至900以下.
  • 不在cargo test以内运行示例.
  • CountDrop的成员替换为u64.
  • 使用1.29.0之前的Rust 版本.

这是怎么回事?是的,MayContainValue可以有一个未初始化的成员,但这永远不会以任何方式使用.

我还成功地在play.rust-lang.org上重现了这一点.


我对涉及以某种安全方式重新设计Optionenum的"解决方案"不感兴趣,我使用手动存储和占用/空闲区分是有充分理由的.

推荐答案

TL;DR:是的,创建未初始化的引用始终是未定义的行为.你不能将mem::uninitialized安全地用于泛型.对于你的具体 case ,目前还没有一个好的解决办法.


在valgrind中运行代码会报告3个错误,每个错误都具有相同的堆栈跟踪:

==741== Conditional jump or move depends on uninitialised value(s)
==741==    at 0x11907F: <alloc::vec::Vec<T> as alloc::vec::SpecExtend<T, I>>::spec_extend (vec.rs:1892)
==741==    by 0x11861C: <alloc::vec::Vec<T> as alloc::vec::SpecExtend<&'a T, I>>::spec_extend (vec.rs:1942)
==741==    by 0x11895C: <alloc::vec::Vec<T>>::extend_from_slice (vec.rs:1396)
==741==    by 0x11C1A2: alloc::slice::hack::to_vec (slice.rs:168)
==741==    by 0x11C643: alloc::slice::<impl [T]>::to_vec (slice.rs:369)
==741==    by 0x118C1E: <alloc::vec::Vec<T> as core::clone::Clone>::clone (vec.rs:1676)
==741==    by 0x11AF89: md::tests::check_drops (main.rs:51)
==741==    by 0x119D39: md::__test::TESTS::{{closure}} (main.rs:36)
==741==    by 0x11935D: core::ops::function::FnOnce::call_once (function.rs:223)
==741==    by 0x11F09E: {{closure}} (lib.rs:1451)
==741==    by 0x11F09E: call_once<closure,()> (function.rs:223)
==741==    by 0x11F09E: <F as alloc::boxed::FnBox<A>>::call_box (boxed.rs:642)
==741==    by 0x17B469: __rust_maybe_catch_panic (lib.rs:105)
==741==    by 0x14044F: try<(),std::panic::AssertUnwindSafe<alloc::boxed::Box<FnBox<()>>>> (panicking.rs:289)
==741==    by 0x14044F: catch_unwind<std::panic::AssertUnwindSafe<alloc::boxed::Box<FnBox<()>>>,()> (panic.rs:392)
==741==    by 0x14044F: {{closure}} (lib.rs:1406)
==741==    by 0x14044F: std::sys_common::backtrace::__rust_begin_short_backtrace (backtrace.rs:136)

在保持Valgrind误差(或一个极其相似的误差)的同时减少误差会导致

use std::{iter, mem};

fn main() {
    let a = unsafe { mem::uninitialized::<&()>() };
    let mut b = iter::once(a);
    let c = b.next();
    let _d = match c {
        Some(_) => 1,
        None => 2,
    };
}

在playground 的Miri中运行这个较小的复制会导致以下错误:

error[E0080]: constant evaluation error: attempted to read undefined bytes
 --> src/main.rs:7:20
  |
7 |     let _d = match c {
  |                    ^ attempted to read undefined bytes
  |
note: inside call to `main`
 --> /root/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/src/libstd/rt.rs:74:34
  |
74|     lang_start_internal(&move || main().report(), argc, argv)
  |                                  ^^^^^^
note: inside call to `closure`
 --> /root/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/src/libstd/rt.rs:59:75
  |
59|             ::sys_common::backtrace::__rust_begin_short_backtrace(move || main())
  |                                                                           ^^^^^^
note: inside call to `closure`
 --> /root/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/src/libstd/sys_common/backtrace.rs:136:5
  |
13|     f()
  |     ^^^
note: inside call to `std::sys_common::backtrace::__rust_begin_short_backtrace::<[closure@DefId(1/1:1823 ~ std[82ff]::rt[0]::lang_start_internal[0]::{{closure}}[0]::{{closure}}[0]) 0:&dyn std::ops::Fn() -> i32 + std::marker::Sync + std::panic::RefUnwindSafe], i32>`
 --> /root/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/src/libstd/rt.rs:59:13
  |
59|             ::sys_common::backtrace::__rust_begin_short_backtrace(move || main())
  |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
note: inside call to `closure`
 --> /root/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/src/libstd/panicking.rs:310:40
  |
31|             ptr::write(&mut (*data).r, f());
  |                                        ^^^
note: inside call to `std::panicking::try::do_call::<[closure@DefId(1/1:1822 ~ std[82ff]::rt[0]::lang_start_internal[0]::{{closure}}[0]) 0:&&dyn std::ops::Fn() -> i32 + std::marker::Sync + std::panic::RefUnwindSafe], i32>`
 --> /root/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/src/libstd/panicking.rs:306:5
  |
30| /     fn do_call<F: FnOnce() -> R, R>(data: *mut u8) {
30| |         unsafe {
30| |             let data = data as *mut Data<F, R>;
30| |             let f = ptr::read(&mut (*data).f);
31| |             ptr::write(&mut (*data).r, f());
31| |         }
31| |     }
  | |_____^
note: inside call to `std::panicking::try::<i32, [closure@DefId(1/1:1822 ~ std[82ff]::rt[0]::lang_start_internal[0]::{{closure}}[0]) 0:&&dyn std::ops::Fn() -> i32 + std::marker::Sync + std::panic::RefUnwindSafe]>`
 --> /root/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/src/libstd/panic.rs:392:9
  |
39|         panicking::try(f)
  |         ^^^^^^^^^^^^^^^^^
note: inside call to `std::panic::catch_unwind::<[closure@DefId(1/1:1822 ~ std[82ff]::rt[0]::lang_start_internal[0]::{{closure}}[0]) 0:&&dyn std::ops::Fn() -> i32 + std::marker::Sync + std::panic::RefUnwindSafe], i32>`
 --> /root/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/src/libstd/rt.rs:58:25
  |
58|           let exit_code = panic::catch_unwind(|| {
  |  _________________________^
59| |             ::sys_common::backtrace::__rust_begin_short_backtrace(move || main())
60| |         });
  | |__________^
note: inside call to `std::rt::lang_start_internal`
 --> /root/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/src/libstd/rt.rs:74:5
  |
74|     lang_start_internal(&move || main().report(), argc, argv)
  |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

简短的版本是mem::uninitialized创建一个空指针,它被视为一个引用.这就是未定义的行为.

在原始代码中,Vec::clone是通过迭代一个迭代器来实现的.Iterator::next返回Option<T>,因此您可以 Select 引用,这会导致null pointer optimization开始.这算作一个None,它提前终止迭代,导致第二个向量为空.

事实证明,拥有mem::uninitialized,一段给你类似C的语义的代码,是一把巨大的手枪,经常被误用(令人惊讶!),所以这里不只你一个人.作为替代品,您应该遵循的主要事项是:

Rust相关问答推荐

如何访问Rust存储值的内存地址

下载压缩文件

无法理解铁 rust &S错误处理

如何获取Serde struct 的默认实例

有没有可能让泛型Rust T总是堆分配的?

在文件链实施中绕过borrow 判断器

你能在Rust中弃用一个属性吗?

为什么RefCell没有与常规引用相同的作用域?

使用 serde::from_value 反序列化为泛型类型

通过写入 std::io::stdout() 输出不可见

decltype、dyn、impl traits,重构时如何声明函数的返回类型

Rust 中的自动取消引用是如何工作的?

没有得到无法返回引用局部变量`queues`的值返回引用当前函数拥有的数据的值的重复逻辑

为什么可以在迭代器引用上调用 into_iter?

如何存储返回 Future 的闭包列表并在 Rust 中的线程之间共享它?

Rustlings 切片原语

从函数返回 u32 的数组/切片

如何为返回正确类型的枚举实现 get 方法?

Rust 内联 asm 中的向量寄存器:不能将 `Simd` 类型的值用于内联汇编

在使用大型表达式时(8k 行需要一小时编译),是否可以避免 Rust 中的二次编译时间?