从回调中返回引用

时间:2017-03-20 21:59:41

标签: rust

我试图从回调内部返回对数据数组的引用。由于生命周期,下面的代码段是不可能的,但无论如何我都添加它以提供更好的背景。

我想实现某种虚拟文件系统。我想使用返回类型&[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]>) {}

2 个答案:

答案 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可能没问题。 :)