【问题标题】:Why do Read::read and Read::read_exact require that the buffers passed to them be initialized?为什么 Read::read 和 Read::read_exact 要求初始化传递给它们的缓冲区?
【发布时间】:2021-12-03 21:31:45
【问题描述】:

我有一个阅读器,其中包含有关 51*51 网格的信息,其中网格上的每个点都由 f32 表示。我想将这些数据读入一个向量,以便我可以轻松处理它:

pub fn from_reader<R: Read + Seek>(reader: &mut R) -> Arena {
    let arena_size = 51 * 51;
    let arena_byte_size = arena_size * size_of::<f32>();
    let mut arena = vec![0.0f32; arena_size];

    unsafe {
        let mut arena_slice =
            std::slice::from_raw_parts_mut(arena.as_mut_ptr() as *mut u8, arena_byte_size);
        let _ = reader.read(&mut arena_slice);
    };
    //...
}

此方法不方便且不必要地缓慢,因为它强制将向量的所有元素初始化为 0 值。我最初想简单地分配一个缓冲区,而不是初始化它,将数据读入其中,然后使用from_raw_parts 从中创建一个向量。但是我被告知这是未定义的行为,因为出于某种深不可测的原因,readread_exact 要求调用者在调用它们之前初始化传递给它们的数据。

为什么会这样?有什么解决方法吗? Rust 团队是否正在制定任何解决方案?

【问题讨论】:

    标签: rust


    【解决方案1】:

    为什么会这样?

    因为Read 的实现者首先读取传入的缓冲区是有效的。如果您传入未初始化的数据并且Read 的实现者查看了缓冲区,那么在纯安全代码中将存在未定义的行为。静态地禁止这一点是 Rust 的一大卖点。

    use std::io::{self, Read};
    
    struct Dummy;
    
    impl Read for Dummy {
        fn read(&mut self, buffer: &mut [u8]) -> io::Result<usize> {
            let v: u8 = buffer.iter().sum(); // Reading from the buffer
            buffer[0] = v;
            Ok(1)
        }
    }
    
    fn main() {
        let mut data = [0, 1, 2];
        Dummy.read(&mut data).unwrap();
        println!("{:?}", data);
    }
    
    • 为什么Read::read 不阻止从缓冲区读取

      没有一种语言结构可以用来施加这种限制。与其他一些语言不同,Rust 没有“输出参数”。即使是这样,我也可以看到Read 的实现者希望能够读取它刚刚写入的数据。例如,计算通过它的换行符数量的阅读器。

    • 为什么Read::read 不接受MaybeUninit

      MaybeUninit 在 Rust 1.0 中不存在——它仅在 Rust 1.36 中稳定。我们希望能够在 Rust 1.0 中读取文件。由于 Rust 的向后兼容性保证,该方法的签名现在无法更改。

    • 为什么Read::read 不是unsafe

      这将是支持未初始化数据的主要(唯一?)技术,但成本很高。 unsafe 不是经验丰富的 Rust 程序员轻易选择的工具。当我们确实使用它时,我们通常会非常努力尽量减少它的范围。

      如果Read::read 不安全,那么每个实施者都必须考虑如何正确满足不安全标准。这对“简单”适配器来说是一个沉重的负担。

    有什么解决方法吗? Rust 团队是否正在制定任何解决方案?

    不稳定的Read::initializer 方法是一种建议的解决方案,但它可能不是首选路线。

    RFC 2930 提供了更新的尝试,并讨论了许多背景故事和挑战。

    另见:


    对于您的具体情况,您可能可以使用Read::takeRead::read_to_end 将您想要的所有字节读入一个空的(未初始化的!)Vec 然后convert a Vec&lt;T&gt; to a Vec&lt;U&gt; without copying the vector。您需要以某种方式确保您已将 Vecf32 正确对齐,因为它开始时仅与 u8 对齐。

    【讨论】:

    • 这是一个很好的答案,而且大多令人满意,但我想说它在解释为什么会出现这种情况方面缺乏,因为虽然所说的一切在技术上都是正确的,它没有解释为什么 rust 团队选择这样设置它。在 read 和 read_exact 的文档中明确指出,rust 团队积极阻止实现缓冲区中的内容,但仍然允许它。在什么情况下需要从缓冲区读取?为什么 rust 团队会允许它?这似乎使情况不必要地复杂化了。
    • @zee 为什么 rust 团队会允许它——你会使用什么 Rust 语言技术来禁止它?没有。
    • 这是不可取的,并且实现不应该从输入缓冲区中读取。问题是没有办法防止它。库设计者不希望正​​确性取决于遵守规则的未知实现者。那将是 C/C++ 的答案:强加不可执行的要求并声明如果不遵循它们就是 UB。 Safe Rust 有更高的标准。
    • @zee 请注意,我特别说过“导致内存不安全”。 Vec::len 返回错误的值会很糟糕,我同意,但如果这样做,就不会发生内存不安全。如果Read 的实现者使用了一些“其他”数据,那也是错误的,但不会导致内存不安全。请务必查看 RFC 中的 "But how bad are undefined values really?" section 和 Rustonomicon 中的 Working With Uninitialized Memory
    • @zee 我相信我在这个讨论中看到了一个可能的误解。当 Shepmaster 谈到“实现者”时,他并不是指 Rust 的替代实现,这确实是正确实现该语言所必需的。他指的是可能实现Read 特征的用户定义类型。这种类型不受以与Vec::len()File::read() 相同的方式符合语言的限制。将未初始化的切片传递给 read() 的任意实现将使安全代码能够表现出未定义的行为,这是 Rust 不允许的。
    猜你喜欢
    • 1970-01-01
    • 2016-07-10
    • 2021-05-02
    • 1970-01-01
    • 1970-01-01
    • 2016-07-18
    • 2010-10-09
    • 1970-01-01
    相关资源
    最近更新 更多