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

jv-*_*dev 7 unsafe rust

我将复杂的数字数据填入Vec<f64>表格中的外部C库(不想改变),[i_0_real, i_0_imag, i_1_real, i_1_imag, ...]看起来它Vec<f64>的内存布局Vec<num_complex::Complex<f64>>与长度的一半相同,因为num_complex::Complex<f64>数据结构是内存 -布局与此处[f64; 2]记录的兼容.我想这样使用它而不需要重新分配潜在的大缓冲区.

我假设它是有效的使用from_raw_parts()std::vec::Vec假一新Vec,是以旧的所有权Vec的记忆(由不忘旧Vec),并使用size / 2capacity / 2,但是这需要不安全的代码.是否有一种"安全"的方式来进行这种数据重新解释?

Vec在Rust中作为a分配,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])
    );
}
Run Code Online (Sandbox Code Playgroud)

She*_*ter 13

是否有一种"安全"的方式来进行这种数据重新解释?

不.至少,这是因为你需要知道的信息不是在Rust类型系统中表达,而是通过散文(也就是文档)来表达:

Complex<T>是与数组兼容的内存布局[T; 2].

- Complex文档

如果a Vec已经分配了内存,那么它的指针len按顺序指向初始化的连续元素(如果你将它强制转换为切片,你会看到什么),

- Vec文档

数组强制切片([T])

- 数组文档

由于a Complex与数组存储器兼容,因此数组的数据与切片存储器兼容,并且Vec数据与切片存储器兼容,这种转换应该是安全的,即使编译器无法说明这一点.

此信息应附加(通过注释)到您的不安全区块.

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

  • 让两个Vecs同时指向相同的数据让我非常紧张.通过引入一些变量并在创建另一个变量之前忘记一个变量,可以简单地避免这种情况.

  • 删除return关键字更加惯用

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

  • 正如罗德里戈指出的那样,容量可能很容易变成奇数.为了避免这种情况,我们打电话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)
    }
}
Run Code Online (Sandbox Code Playgroud)

为了避免所有对容量的担忧,您可以将切片转换为Vec.这也没有任何额外的内存分配.它更简单,因为我们可以"丢失"任何奇怪的尾随值,因为它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)
    }
}
Run Code Online (Sandbox Code Playgroud)