我有一个外部 C 库(不想更改)以[i_0_real, i_0_imag, i_1_real, i_1_imag, ...]
的形式填充到Vec<f64>
中,并且看起来这个Vec<f64>
具有与一半长度的Vec<num_complex::Complex<f64>>
相同的内存布局,因为num_complex::Complex<f64>
的数据结构与此处记录的[f64; 2]
内存布局兼容。我想这样使用它,而不需要重新分配潜在的大缓冲区。
我假设在std::vec::Vec
中使用from_raw_parts()
来伪造一个新Vec
是有效的,该拥有旧Vec
的内存(通过忘记旧Vec
)并使用size / 2
和capacity / 2
,但这需要不安全的代码。有没有一种"安全"的方法来进行这种数据重新解释?
Vec
在 Rust 中作为Vec<f64>
分配,并由一个 C 函数填充,使用填充Vec<f64>
的.as_mut_ptr()
。
我目前的编译不安全实现:
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])
);
}
有没有一种"安全"的方法来进行这种数据重新解释?
不。至少,这是因为你需要知道的信息不是在 Rust 类型系统中表达的,而是通过散文(又名文档)表达的:
Complex<T>
是与阵列[T; 2]
兼容的内存布局。—
Complex
文档
如果一个
Vec
分配了内存,那么它的指针依次指向len
初始化的连续元素(如果你把它强制到一个切片上,你会看到什么),—
Vec
文档
阵列强制到切片 (
[T]
)— 数组文档
由于Complex
与数组的内存兼容,数组的数据与切片的内存兼容,而Vec
的数据与切片的内存兼容,因此这种转换应该是安全的,即使编译器无法分辨这一点。
此信息应(通过注释)附加到您的不安全块。
我会对您的函数进行一些小的调整:
-
同时有两个
Vec
指向相同的数据让我非常紧张。这可以通过引入一些变量并在创建另一个变量之前忘记一个变量来轻松避免。 -
删除
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)
}
}
为了避免所有对容量的担忧,您只需将切片转换为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 *const num_complex::Complex<f64>;
let len = buffer.len();
assert!(len % 2 == 0);
std::slice::from_raw_parts(ptr, len / 2)
}
}