Chr*_*isB 0 lifetime rust borrow-checker
我在 Rust 中遇到的一个常见模式是这样的:
struct Foo { /*...*/ }
struct FooProcessor {
foos: Vec<&'??? mut Foo>, // this lifetime is the issue, see explanation below
// other data structures needed for processing Foos ....
}
impl FooProcessor {
// just to make it clear, `self` may outlive `input` and it's `Foo`s
pub fn process(&mut self, input: &mut [Foo]) {
// some example operation that requires random access to a subset
self.foos.extend(input.iter().filter(|f| f.some_property()));
self.foos.sort();
// work some more on the Foos...
// remove all references to the processed Foos
self.foos.clear();
}
}
Run Code Online (Sandbox Code Playgroud)
问题在于 的生命周期FooProcessor::foos,正如上面的问号所强调的那样。所有对Foos 的引用都会在 的末尾被清除process(),但借用检查器当然不知道这一点。
FooProcessor通常是一个大的、寿命长的对象(具体来说,它可能比某些Foos 寿命更长),并且我不想Vec<&Foo>在每次process()调用时重新分配(甚至不使用某些SmallVec东西)。
有没有一种好方法可以解决这个问题,并且每次我有这种模式时不需要使用不安全代码(生命周期变换/指针)?(如果解决方案涉及使用某个板条箱,那么如果该板条箱内部使用不安全当然没问题)。
转变Vec<T>为Vec<U>通常是不健全的。但是,我们可以使用Vec::into_raw_parts将向量分解为数据指针、长度和容量,转换数据指针,然后使用Vec::from_raw_parts新类型重新创建向量,只要我们能够满足 的不变量即可Vec::from_raw_parts。
嗯,我们可以...但Vec::into_raw_parts不稳定。然而,它是根据公共Vec方法实现的,因此复制和粘贴实现很简单:
fn vec_into_raw_parts<T>(v: Vec<T>) -> (*mut T, usize, usize) {
let mut v = std::mem::ManuallyDrop::new(v);
(v.as_mut_ptr(), v.len(), v.capacity())
}
Run Code Online (Sandbox Code Playgroud)
好的,现在让我们来看看 ' 不变量列表Vec::from_raw_parts:
ptr必须使用全局分配器进行分配,例如通过函数alloc::alloc。
Vec我们从使用全局分配器的a 中获取指针。
T需要与ptr分配的对齐方式相同。(T对齐不太严格是不够的,对齐确实需要相等才能满足dealloc必须使用相同布局分配和释放内存的要求。)
Vec将确保分配对齐T,因此我们可以让我们的函数断言T并U具有相同的对齐方式。由于类型在编译时已知,因此如果通过,编译器将忽略此检查。
T时间的大小capacity(即以字节为单位分配的大小)需要与分配指针的大小相同。(因为与对齐类似,dealloc必须使用相同的布局来调用size。)
我们还将断言 和 的大小T相等U,这满足这个不变量。
length需要小于或等于容量。
capacity需要是分配给指针的容量。分配的大小(以字节为单位)不得大于 isize::MAX。请参阅pointer::offset 的安全文档。
我们将从现有向量中获取长度、容量和指针,它必须满足这些不变量,除非我们已经在某个地方遇到了 UB。
第一个
length值必须是正确初始化的类型值T。
我们将在分解向量之前清除它,因此length向量为零,空洞地满足这个不变量。
Vec现在我们可以用这些术语定义元素类型之间的安全转换:
fn safe_vec_transmute<T, U>(mut v: Vec<T>) -> Vec<U> {
assert_eq!(std::mem::size_of::<T>(), std::mem::size_of::<U>());
assert_eq!(std::mem::align_of::<T>(), std::mem::align_of::<U>());
v.clear();
let (ptr, len, cap) = vec_into_raw_parts(v);
// SAFETY:
//
// We assert T and U have the same size and alignment, and we clear the
// vector first. This satisfies several invariants of Vec:from_raw_parts.
// The remaining invariants are satisfied because we get ptr, len, and cap
// from an existing vector.
unsafe { Vec::from_raw_parts(ptr as *mut U, len, cap) }
}
Run Code Online (Sandbox Code Playgroud)
要利用它,您可以将其存储foos为Vec<&'static mut T>. 您process()可以std::mem::take(&mut self.foos)窃取分配,留下零容量向量,使用 转换生命周期safe_vec_transmute,使用向量进行工作,将生命周期转换回'static,并将其存储回self.foos。
它看起来像这样(未经测试的代码):
let foos = safe_vec_transmute(std::mem::take(&mut self.foos));
foos.extend(...);
foos.sort();
// More work
self.foos = safe_vec_transmute(foos);
Run Code Online (Sandbox Code Playgroud)
请注意,safe_vec_transmute 编译(经过优化)为与清除向量并返回向量的函数完全相同的指令,因此运行时开销为零!