借用一个对象两次作为不相关的顺序使用

Borrowing an object as mutable twice for unrelated, sequential uses

我正在尝试实现一个允许我从目录或 zip 文件中读取的抽象。我从实现这种东西开始:

pub trait FileOpener<'a> {
    type ReaderType: Read;

    fn open(&'a self, file_name: &str) -> Result<Self::ReaderType, Box<dyn Error>>;
}

pub struct DirectoryFileOpener<'a> {
    root: &'a Path
}

impl<'a> DirectoryFileOpener<'a> {
    pub fn new(root: &'a Path) -> Self {
        DirectoryFileOpener { root }
    }
}

impl<'a> FileOpener<'a> for DirectoryFileOpener<'a> {
    type ReaderType = File;

    fn open(&'a self, file_name: &str) -> Result<File, Box<dyn Error>> {
        Ok(File::open(self.root.join(file_name))?)
    }
}

但后来我意识到 zip-rs 包的 zip::ZipFile 是从对它所在的 zip::ZipArchive 的可变引用构造的,所以我最终得到以下代码:

use std::path::Path;
use std::error::Error;
use std::fs::File;
use std::io::prelude::*;
use zip::{ZipArchive, read::ZipFile};
use std::marker::PhantomData;

pub trait FileOpener<'a> {
    type ReaderType: Read;

    fn open(&'a mut self, file_name: &str) -> Result<Self::ReaderType, Box<dyn Error>>;
}

pub struct DirectoryFileOpener<'a> {
    root: &'a Path
}

impl<'a> DirectoryFileOpener<'a> {
    pub fn new(root: &'a Path) -> Self {
        DirectoryFileOpener { root }
    }
}

impl<'a> FileOpener<'a> for DirectoryFileOpener<'a> {
    type ReaderType = File;

    fn open(&'a mut self, file_name: &str) -> Result<File, Box<dyn Error>> {
        Ok(File::open(self.root.join(file_name))?)
    }
}

pub struct ZipFileOpener<'a, R: Read + Seek> {
    zip: ZipArchive<R>,
    phantom: PhantomData<&'a Self>
}

impl<'a, R: Read + Seek> ZipFileOpener<'a, R> {
    pub fn new(zip: ZipArchive<R>) -> Self {
        ZipFileOpener { zip, phantom: PhantomData }
    }
}

impl<'a, R: Read + Seek> FileOpener<'a> for ZipFileOpener<'a, R> {
    type ReaderType = ZipFile<'a>;

    fn open(&'a mut self, file_name: &str) -> Result<ZipFile<'a>, Box<dyn Error>> {
        Ok(self.zip.by_name(file_name)?)
    }
}

我不确定这是否是最佳的编写方式,但至少它可以编译。然后我尝试这样使用它:

fn load(root: &Path) -> Result<...> {
        let mut opener = io::DirectoryFileOpener::new(root);
        let a = Self::parse_a(opener.open("a.txt")?)?;
        let b = Self::parse_b(opener.open("b.txt")?, a)?;
}

我得到 cannot borrow 'opener' as mutable more than once at a time。这并没有让我感到惊讶,因为我确实使用了 open(),它借用了 opener 作为可变的,两次 - 尽管 a 只是一个 u64,而且从我的角度来看它与 [=30 的生命周期无关=](),从编译器的角度来看,它必须与它下面的行处于同一生命周期,因此我们尝试两次借用 opener 作为可变的。

但是,我随后查看了以下代码,它编译并运行良好,我通过尝试改进开始了整个事情:

fn load_zip(root: &Path) -> Result<...> {
    let file = File::open(root)?;
    let mut zip = ZipArchive::new(file)?;
    let a = Self::parse_a(zip.by_name("a.txt")?)?;
    let b = Self::parse_b(zip.by_name("b.txt")?, a)?;
}

这完全让我失望了,因为函数 by_name() 也借用了 zip 作为可变的,并且也被调用了两次!为什么在这里两次允许借用 zip 作为可变的,而在以前的情况下不允许?

在更深入地研究了这个问题和 Rust 的语义并在 trentcl 的注释之上构建之后,我开始意识到这个问题本质上归结为定义 FileOpener 特征,其中生命周期参数绑定到关联类型并且不是特征本身,例如

pub trait FileOpener {
    type ReaderType: Read;

    fn open(&'a mut self, file_name: &str) -> Result<Self::ReaderType, Box<dyn Error>>;
}


impl<'a, R: Read + Seek> FileOpener for ZipFileOpener<R> {
    type ReaderType = ZipFile<'a>;
    ...
}

然而,这被称为 generic associated types (GAT),并且在 Rust 中尚不支持。然而,GAT RFC 确实提到,在某些情况下,可以通过将生命周期绑定到特征本身并在接收函数中使用 higher-rank 特征边界(HRTB)来规避问题,这会产生以下针对该问题的有效解决方案:

pub trait FileOpener<'a> {
    type ReaderType: Read;

    fn open(&'a self, file_name: &str) -> Result<Self::ReaderType, Box<dyn Error>>;
}

...

fn load<T: for<'a> FileOpener<'a>>(opener: T) -> ... {
    let a = parse_a(opener.open("a.txt")?)?;
    let b = parse_b(opener.open("b.txt")?, a)?;
}

这是因为 HRTB 允许我们将 T 绑定到 FileOpener 而无需为其绑定特定的生命周期,这使得每次调用 opener.open()

时可以延迟绑定不同的生命周期