首页 > 解决方案 > Rust 中隐藏的代理和实现模式?

问题描述

所以我是一位经验丰富的开发人员,对 Rust 很陌生,是 Java 专家——但我是从汇编语言开始的,所以我得到了内存和分配,并且写了足够多的编译器东西来很好地理解借用检查器。

我决定移植一个我用 Java 编写的非常有用的、基于位集的高性能图形库,既是为了在更大的最终项目中使用它,也是因为它非常有用。因为它是位集中的所有整数位置,并且如果您想要一个对象图,您可以将索引映射到一个数组或其他任何东西 - 我不会纠结于构建一个相互引用的巨大对象树,这将是一团糟在 Rust 中做。我要解决的问题要简单得多 - 如此简单,我觉得我必须错过一个明显的模式来解决它:

查看可用于 Rust 的各种位集库,FixedBitSet似乎很合适。 但是,我宁愿不通过我的 API 公开它,并将我的库的每个使用者不可撤销地绑定到 FixedBitSet(这很好,但是,换入例如由原子支持的实现也很有用;并且与usize可能结婚不理想,但 FixedBitSet 是)。

在 Java 中,您只需创建一个封装具体类型实例的接口,并公开您想要的功能,隐藏实现类型。在 Rust 中,您具有特征,因此很容易实现:

pub trait Bits<'i, S: Sized + Add<S>> {
    fn size(&'i self) -> S;
    fn contains(&'i self, s: S) -> bool;
...
impl<'i, 'a> Bits<'i, usize> for FixedBitSet {
    fn size(&'i self) -> usize {
        self.len()
    }
    fn contains(&'i self, s: usize) -> bool {
        FixedBitSet::contains(self, s)
    }
...

这有点难看,如果我不想暴露FixedBitSet,一切都必须返回Box<dyn Bits<'a, usize> + 'a>,但现在就这样吧——尽管编译器不知道dyn Bits....

到目前为止,一切都很好。有趣的地方(我说这是一个非常简单的问题吗?)是代理一个迭代器。Rust 迭代器似乎不可撤销地绑定到具有关联类型的具体 Trait 类型。所以你不能真正抽象它(好吧,有点像 via Box<dyn Iterator<Item=usize> + 'a> where ...,看起来有可能创建一个扩展迭代器并在其上也有 a 的特征,并为 ,和??type Item实现它也许是编译器合并特征的成员?)。而且据我所知,您也不能将 trait 实现方法中的返回类型缩小到 trait 指定的其他类型。u32u64usizetype Item

由于FixedBitSet 中用于迭代集合位的Ones类型有其自己的生命周期,这变得更加复杂- 但 Rust 的 Iterator 没有 - 因此任何通用的 Iterator 实现都需要返回一个范围为该生命周期的迭代器,而不是'_或者会有迭代器与创建它的事物相比存在多长时间的问题。

我能想出的最整洁的东西 - 这一点都不整洁 - 在尝试了各种用于迭代器的容器(向基值添加偏移量的 Bits 的实现也很有用)暴露它之后,是这样的:

pub trait Biterable<'a, 'b, S: Sized + Add<S>, I: Iterator<Item = S> + 'b>  where 'a: 'b {
    fn set_bits<'c>(&'a mut self) -> I where I: 'c, 'b: 'c;
}

这是足够可实现的:

impl<'a, 'b> Biterable<'a, 'b, usize, Ones<'b>> for FixedBitSet where 'a: 'b {
    fn set_bits<'c>(&'a mut self) -> Ones<'b> where Ones<'b>: 'c, 'b: 'c {
        self.ones()
    }
}

但是,我们知道我们将要处理 Boxes。所以,我们需要一个实现。伟大的!类似的签名impl<'a, 'b> Biterable<'a, 'b, usize, Ones<'b>> for Box<FixedBitSet> where 'a: 'b {是可以实现的。 BUUUUUUT,如果我们不暴露FixedBitSet在任何地方,那将不会有任何回报——我们需要它Box<dyn Bits<...> + ...>。为此我们最终进入了一个镜子大厅,在上面写下越来越巴洛克和可怕(且无法编译)的变体

impl<'a, 'b, B> Biterable<'a, 'b, usize, &'b mut dyn Iterator<Item=usize>>
   for Box<dyn B + 'a> where 'a: 'b, B : Bits<'a, usize> + Biterable<'a, 'b, usize, Ones<'b>> {

徒劳地寻找可以编译和工作的东西(这失败了,因为 whileBitsBiterable是特征,显然Biterable + Bits不是特征)。说真的 - 一个无状态的、不需要分配的包装器,用于对该事物的一次调用返回对该事物的一次调用,只是不将该事物的类型暴露给调用者。而已。Java等价物是Supplier<T> a = ...; return () -> a.get();

我必须把这个问题想错了。如何?

标签: rust

解决方案


看起来你确实把事情复杂化了。您有很多似乎没有必要的生命周期注释。是一个简单的实现(忽略 generic S):

use fixedbitset::FixedBitSet; // 0.2.0

pub trait Bits {
    fn size(&self) -> usize;
    fn contains(&self, s: usize) -> bool;
    fn set_bits<'a>(&'a mut self) -> Box<dyn Iterator<Item = usize> + 'a>;
}

impl Bits for FixedBitSet {
    fn size(&self) -> usize {
        self.len()
    }
    fn contains(&self, s: usize) -> bool {
        self.contains(s)
    }
    fn set_bits<'a>(&'a mut self) -> Box<dyn Iterator<Item = usize> + 'a> {
        Box::new(self.ones())
    }
}

pub fn get_bits_from_api() -> impl Bits {
    FixedBitSet::with_capacity(64)
}

如果您希望索引类型也是匿名的,请将其设为关联类型,并且在公开您的 : 时不要定义它Bits

use fixedbitset::FixedBitSet; // 0.2.0

pub trait Bits {
    type Idx: std::ops::Add<Self::Idx>;

    fn size(&self) -> Self::Idx;
    fn contains(&self, s: Self::Idx) -> bool;
    fn set_bits<'a>(&'a self) -> Box<dyn Iterator<Item = Self::Idx> + 'a>;
}

impl Bits for FixedBitSet {
    type Idx = usize;

    fn size(&self) -> Self::Idx {
        self.len()
    }
    fn contains(&self, s: Self::Idx) -> bool {
        self.contains(s)
    }
    fn set_bits<'a>(&'a self) -> Box<dyn Iterator<Item = Self::Idx> + 'a> {
        Box::new(self.ones())
    }
}

pub fn get_bits_from_api() -> impl Bits {
                           // ^^^^^^^^^ doesn't have <Idx = usize>
    FixedBitSet::with_capacity(64)
}

fn main() {
    let bits = get_bits_from_api();

    // just a demonstration that it compiles
    let size = bits.size();
    if bits.contains(size) {
        for indexes in bits.set_bits() {
            // ...
        }
    }
}

尽管出于多种原因,我强烈反对这样做。1)您需要更多的约束,而不仅仅是Add远程可用。2)您受到严重限制impl Bits;它没有完全定义,因此您不能拥有dyn Bits或将其存储在结构中。3)我认为在这方面通用并没有多大好处。


推荐阅读