我试图从回调内部返回对数据数组的引用。由于生命周期,下面的代码段是不可能的,但无论如何我都添加它以提供更好的背景。
我想实现某种虚拟文件系统。我想使用返回类型&[u8]
,因为我正在考虑使用mmap
以及看起来很有希望公开&[u8]
来访问数据的实现。
现在这样做太过分了,所以我想专注于回调来读取并返回传递给它的文件的内容。
这样做的惯用方法是什么?
use std::fs::File;
use std::io::prelude::*;
fn main() {
test(&|path| {
if false {
let mut data: Vec<u8> = Vec::new();
let mut file = File::open(path).unwrap();
file.read_to_end(&mut data).unwrap();
return Some(&data);
}
None
});
}
// loads various files. I do not care about them anymore once this function returns
pub fn test<'a>(loader: &Fn(&str) -> Option<&'a [u8]>) {}
答案 0 :(得分:3)
返回对堆栈分配数据的引用是不正确的,因为它将立即比它引用的对象寿命更长。唯一可以返回的引用,没有问题,是那些生命周期为'static
的引用 - Rust会仔细检查。对新分配数据的引用肯定不是'static
。
幸运的是,有一种解决方法:当Rust可以证明引用超过数据时,返回引用是安全的。例如:
// Memory backed by a Vec
struct VecMemory {
data: Vec<u8>
}
impl VecMemory {
fn as_slice(&self) -> &[u8] {
&self.data
}
}
as_slice()
可能会返回引用,因为该引用可证明比它所引用的对象更长。如果我们撤消生命周期省略,as_slice()
的签名将是:
fn as_slice<'a>(&'a self) -> &'a [u8]
接下来的问题是关闭应该返回什么?如果按照@ E_net4的建议返回Vec
,或者VecMemory
(再次只保留Vec
),那么使用向量作为底层存储将被烘焙到界面。为了支持不同的存储类型,闭包应返回其他语言称为接口的内容。最接近的Rust等效项是trait对象,在返回上下文中指定为Box<SomeTrait>
。
通过这种设计,闭包有效地堆分配资源管理对象并返回一个两指针大小的框,该框为堆分配的值提供所有权和统一接口。该框的用户仅通过框与实现进行通信,该框使用内部vtable与实现进行通信。 (指向vtable的指针是Box
本身占用两个指针而不是一个指针的原因。)换句话说,闭包的返回值是擦除返回的具体类型
use std::fs::File;
use std::io::prelude::*;
trait Memory {
fn as_slice(&self) -> &[u8];
// a real-life trait would likely also define
// as_slice(&mut self) -> &mut [u8]
}
// Memory backed by a Vec
struct VecMemory {
data: Vec<u8>
}
impl Memory for VecMemory {
fn as_slice(&self) -> &[u8] {
&self.data
}
}
fn main() {
test(&|path| {
if false {
let mut data: Vec<u8> = Vec::new();
let mut file = File::open(path).unwrap();
file.read_to_end(&mut data).unwrap();
return Some(Box::new(VecMemory { data: data }));
}
None
});
}
// loader returns a boxed trait object whose underlying memory
// can be accessed as long as the box is alive.
fn test<'a>(_loader: &Fn(&str) -> Option<Box<Memory>>) {}
要将mmap
用于存储,可以编写不同的Memory
实现,例如Mmap
。这个将存储原始指针和mmap()
返回的内存大小。它会调用mmap()
中的new()
和munmap()
中的Drop::drop
。最重要的是,Mmap
将使用不安全的块来实现Memory
,以根据存储的指针和长度构造切片。同样,这是安全的,因为参考的生命周期将与Mmap
的生命周期相关联。
答案 1 :(得分:2)
您不希望在此处返回引用,因为您的data
仅存在于闭包内。除非您希望将回调API更改为通过可变引用修改缓冲区的内容,否则更简单(并且仍然惯用)的方法将返回向量。
test(&|path| {
if false {
let mut data: Vec<u8> = Vec::new();
let mut file = File::open(path).unwrap();
file.read_to_end(&mut data).unwrap();
return Some(data);
}
None
});
然后,修改使用者函数test
以返回向量,从而保留所有权。如果需要,可以通过调用as_slice
获取对向量内数据的引用。
pub fn test<'a>(loader: &Fn(&str) -> Option<Vec<u8>>) {}
我想使用返回类型&amp; [u8],因为我正在考虑使用mmap和看起来很有希望的实现以及[u8]来访问数据
您可能意味着您希望自己的函数返回&[u8]
。即使在这种情况下,数据也必须归其他地方所有,这是您必须自己处理的事情。这可能涉及使用某种ResourceHandler
结构,它将提供与资源处理程序一样长的切片。
但是现在这样做太过分了,所以我想专注于回调来读取并返回传递给它的文件的内容。
在这种情况下,暂时返回Vec
可能没问题。 :)