如何安全地将 Vec<f64> 重新解释为大小减半的 Vec<num_complex::Complex<f64>>?

How to safely reinterpret Vec<f64> as Vec<num_complex::Complex<f64>> with half the size?

我有一个外部 C 库(最好不要更改)以 [i_0_real, i_0_imag, i_1_real, i_1_imag, ...] 形式将复数数据填充到 Vec<f64> 中,看起来这个 Vec<f64> 具有相同的假设 num_complex::Complex<f64> 的数据结构与 [f64; 2] 的内存布局兼容,如 here 所述,内存布局将是长度的一半 Vec<num_complex::Complex<f64>>。我想这样使用它而不需要重新分配可能很大的缓冲区。

我假设在 std::vec::Vec 中使用 from_raw_parts() 伪造一个新的 Vec 是有效的,该 Vec 拥有旧的 Vec 的内存(通过忘记旧的 Vec) 并使用 size / 2capacity / 2,但这需要不安全的代码。有没有 "safe" 方法来重新解释这种数据?

Vec 在 Rust 中分配为 Vec<f64> 并由 C 函数使用 .as_mut_ptr() 填充 Vec<f64>.

我当前的编译不安全实现:

extern crate num_complex;

pub fn convert_to_complex_unsafe(mut buffer: Vec<f64>) -> Vec<num_complex::Complex<f64>> {
    let new_vec = unsafe {
        Vec::from_raw_parts(
            buffer.as_mut_ptr() as *mut num_complex::Complex<f64>,
            buffer.len() / 2,
            buffer.capacity() / 2,
        )
    };
    std::mem::forget(buffer);
    return new_vec;
}

fn main() {
    println!(
        "Converted vector: {:?}",
        convert_to_complex_unsafe(vec![3.0, 4.0, 5.0, 6.0])
    );
}

Is there a "safe" way to do this kind of data re-interpretation?

没有。至少,这是因为你需要知道的信息不是用 Rust 类型系统表达的,而是通过散文表达的(a.k.a.the docs):

Complex<T> is memory layout compatible with an array [T; 2].

Complex docs

If a Vec has allocated memory, then [...] its pointer points to len initialized, contiguous elements in order (what you would see if you coerced it to a slice),

Vec docs

Arrays coerce to slices ([T])

Array docs

由于 Complex 与数组内存兼容,数组的数据与切片内存兼容,而 Vec 的数据与切片内存兼容,这转换应该是安全的,即使编译器无法告诉这一点。

此信息应附加(通过评论)到您的不安全块。

对你的函数做一些小的调整:

  • 有两个 Vec 同时指向相同的数据让我 非常 紧张。这可以通过引入一些变量并在创建另一个之前忘记一个变量来避免。

  • 删除 return 关键字更加地道

  • 添加一些断言数据的起始长度是二的倍数

  • , the capacity could easily be an odd number. To attempt to avoid this, we call shrink_to_fit。这有一个缺点,即 Vec 可能 需要重新分配和复制内存,具体取决于实现。

  • 扩展 unsafe 块以涵盖确保维护安全不变量所需的所有相关代码。

pub fn convert_to_complex(mut buffer: Vec<f64>) -> Vec<num_complex::Complex<f64>> {
    // This is where I'd put the rationale for why this `unsafe` block
    // upholds the guarantees that I must ensure. Too bad I
    // copy-and-pasted from Stack Overflow without reading this comment!
    unsafe {
        buffer.shrink_to_fit();

        let ptr = buffer.as_mut_ptr() as *mut num_complex::Complex<f64>;
        let len = buffer.len();
        let cap = buffer.capacity();

        assert!(len % 2 == 0);
        assert!(cap % 2 == 0);

        std::mem::forget(buffer);

        Vec::from_raw_parts(ptr, len / 2, cap / 2)
    }
}

为了避免所有关于容量的担忧,您可以将切片转换为 Vec。这也没有任何额外的内存分配。它更简单,因为我们可以 "lose" 任何奇数尾随值,因为 Vec 仍然维护它们。

pub fn convert_to_complex(buffer: &[f64]) -> &[num_complex::Complex<f64>] {
    // This is where I'd put the rationale for why this `unsafe` block
    // upholds the guarantees that I must ensure. Too bad I
    // copy-and-pasted from Stack Overflow without reading this comment!
    unsafe {
        let ptr = buffer.as_ptr() as *mut num_complex::Complex<f64>;
        let len = buffer.len();

        std::slice::from_raw_parts(ptr, len / 2)
    }
}