繁体   English   中英

如何在 Rust 中没有特征对象的特征的多个实现者之间进行选择?

[英]How can I choose between multiple implementors of a trait without trait objects in Rust?

我有一个名为Dataset的特征,它有一个方法可以通过文件名和关联的Error类型检索文件的内容。 该特征针对不同的文件系统结构实现,例如用于基于目录访问的PathBuf或用于访问压缩 zip 存档的ZipArchive 出于测试目的,还存在HashMap的实现。

trait Dataset {
    type Error: Error + 'static;

    fn read(&mut self, name: &str) -> Result<String, Self::Error>;
}

现在,我想根据给定的文件名推断要使用哪个实现:如果路径是目录,则为目录;如果扩展名为.zip.bzip ,则为 zip 存档,等等。考虑 function fn run(dataset: impl Dataset)对数据集进行操作。 内联选择代码后,一切正常:(使用Option<PathBuf>仅用于此示例,实际代码会查看给定路径的属性,如上所述)

let optional = Some(PathBuf::from("/path/to/dataset/.txt"));
match optional.clone() {
    Some(dataset) => run(dataset)?,
    None => run(static_dataset())?,
}

但是,我更愿意将 (1)“为Dataset选择一个实现者”的功能与 (2)“将Dataset提供给run函数”分离。 我希望使这个 function 更具可重用性(因为不仅run function 可以利用 (1))并且更加独立于run的更改——考虑更改签名,例如添加更多参数。

我尝试了两种方法:

  1. 特征对象

    虽然我不太乐意仅仅出于解耦的原因而使用特征对象,但我不会在意识形态上拒绝这个选项。
     fn with_trait_object(optional: Option<PathBuf>) -> Box<dyn Dataset> { match optional { Some(dataset) => Box::new(dataset), None => Box::new(static_dataset), } }
    但这被错误消息拒绝:
     error[E0191]: the value of the associated type `Error` (from trait `Dataset`) must be specified --> src/main.rs:68:60 | 9 | type Error: Error + 'static; | ---------------------------- `Error` defined here... 68 | fn with_trait_object(optional: Option<PathBuf>) -> Box<dyn Dataset> { |
    这对我来说很有意义,因为Dataset::Error类型在返回值 position 中,我们在使用特征 object 时不知道它的确切大小。
  2. 闭包作为参数

    我的第二种方法是我更喜欢的想法:调用者没有将Dataset返回给我的调用者,而是给我一个将接受Dataset的闭包。 然后,可以使用闭包将额外的 arguments 传递给实际处理 function。我还可以使用不同的闭包,这样我就可以轻松地重用我的“选择正确的实现者”功能。 但是,我无法表达“这个 function 接受一个闭包FnOnce ,它本身在其第一个参数中的Dataset特征上是通用的”。 目前,我使用Result<(), Box<dyn Error>>作为闭包的返回类型,但也可以是通用的。 这里是错误的代码:
     fn with_consumer_function( optional: Option<PathBuf>, consumer: impl FnOnce(impl Dataset) -> Result<(), Box<dyn Error>>, ) -> Result<(), Box<dyn Error>> { match optional { Some(dataset) => consumer(dataset), None => consumer(static_dataset), } }

我很好奇,看起来我无法分解出这段代码,因为我无法描述消费者 function 的正确签名。是否有不同的方式来订购类型参数? 或者是否有可能采用完全不同的方法?

可以在Rust Playground上找到一个小例子。

你可以从这个开始:

fn with_consumer_function<DS: Dataset>(
    optional: Option<DS>,
    consumer: impl FnOnce(DS) -> Result<(), Box<dyn Error>>,
) -> Result<(), Box<dyn Error>> {
    match optional {
        Some(dataset) => consumer(dataset),
        None => todo!(), //consumer(static_dataset()),
    }
}

如果要更改None arm,则必须创建一个(通用)方法来生成数据集。 您可以通过使用fn static_dataset() -> Self扩展特征Dataset来实现。

None不适用于闭包的原因是 rust(至少到目前为止)将闭包视为接受固定参数。 如果consumer在第一个分支中接受DS ,则它不能在第二个分支中简单地接受HashMap - 它仍然必须接受相同的类型: DS

您可以通过定义自己的特征来解决这个问题:

trait DatasetConsumer {
    fn call<DS: Dataset>(self, ds: DS) -> Result<(), Box<dyn Error>>;
}

struct DatasetConsumerRun;
impl DatasetConsumer for DatasetConsumerRun {
    fn call<DS: Dataset>(self, mut dataset: DS) -> Result<(), Box<dyn Error>> {
        println!("{}", dataset.read("info.txt")?);
        Ok(())
    }
}
    
fn with_consumer_function<DS: Dataset>(
    optional: Option<DS>,
    consumer: impl DatasetConsumer,
) -> Result<(), Box<dyn Error>> {
    match optional {
        Some(dataset) => consumer.call(dataset),
        None => consumer.call(static_dataset()),
    }
}

fn main() -> Result<(), Box<dyn Error>> {
    let optional = Some(PathBuf::from("/path/to/dataset/.txt"));
    with_consumer_function(optional.clone(), DatasetConsumerRun)?;
    Ok(())
}

也就是说,使用DatasetConsumer比使用普通闭包更麻烦。

您也可以尝试将with_consumer_function实现为宏,但我不确定我是否会遵循这条路径。

另外,我有一个 bash 试图让编译器接受。 这里是Rust游乐场

但我认为@phimuemue 的回应非常简洁。 在我的例子中,为了方便我添加了一个枚举而不是关联类型。 并且只是在代码中通过在Option上映射一个正在运行的闭包来避免泛型。 我不确定它是否符合分解出匹配arm的标准,但感觉很方便并且避免了动态调度。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM