我有一个复杂的数字数据,它由外部C库以Vec<f64>
的形式填充到[i_0_real, i_0_imag, i_1_real, i_1_imag, ...]
中,并且似乎Vec<f64>
具有相同的内存布局假设Vec<num_complex::Complex<f64>>
的数据结构与here所述的num_complex::Complex<f64>
的存储布局与[f64; 2]
兼容,则长度为from_raw_parts()
的一半。我想按原样使用它,而无需重新分配可能很大的缓冲区。
我假设在std::vec::Vec
中使用Vec
来伪造一个新Vec
并拥有旧Vec
的所有权(通过忘记size / 2
的旧版本,并分别使用capacity / 2
和Vec
,但这需要不安全的代码。有没有一种“安全”的方式来进行这种数据重新解释?
Vec<f64>
在Rust中作为.as_mut_ptr()
分配,并由C函数使用填充Vec<f64>
的{{1}}填充。
我当前的编译不安全实现:
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])
);
}
答案 0 :(得分:13)
是否存在“安全”的方式来进行这种数据重新解释?
不。至少,这是因为您需要知道的信息不是在Rust类型系统中表达的,而是通过散文(也称为docs)表达的:
Complex<T>
是与数组[T; 2]
兼容的内存布局。
如果
Vec
分配了内存,则其指针按顺序指向len
初始化的连续元素(将其强制为片状后会看到什么),< / p>—
Vec
docs
数组强制转换为切片(
[T]
)
由于Complex
与数组内存兼容,数组的数据与slice内存兼容,并且Vec
的数据与slice内存兼容,因此此转换应是安全的,即使编译器无法告知这一点。
此信息应(通过注释)附加到您的不安全封锁中。
我会对您的功能进行一些小的调整:
同时拥有两个Vec
指向相同的数据使我非常紧张。可以通过引入一些变量并在创建另一个变量之前忘记一个变量来避免这种情况。
删除return
关键字以使其更加惯用
添加一些断言,表明数据的起始长度是2的倍数。
与rodrigo points out一样,容量很容易是奇数。为了避免这种情况,我们调用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
。这也没有任何额外的内存分配。这很简单,因为我们可以“丢失”任何奇数尾随的值,因为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)
}
}