Rust中隐藏的代理和实现模式



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

我决定移植一个我用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类型,该类型有一个关联类型。因此,您不能真正抽象它(好吧,有点像是通过Box<dyn Iterator<Item=usize> + 'a> where ...,看起来可以创建一个扩展迭代器的trait,上面也有type Item,并为u32u64usize实现它,也许编译器合并了trait的type Item成员(。据我所知,你也不能把trait实现方法中的返回类型缩小到trait指定的以外的类型。

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>> {

徒劳地寻找可编译和工作的东西(这失败了,因为虽然BitsBiterable是特征,但显然Biterable + Bits不是特征(。说真的——一个无状态的、不需要分配的包装器,用于对这个东西的一个调用,返回对那个东西的一次调用,只是不向调用者公开那个东西的类型。就是这样。Java的等价物应该是Supplier<T> a = ...; return () -> a.get();

我一定是想错了这个问题。怎样

看起来确实过于复杂了。您有很多似乎没有必要的终身注释。这里有一个简单的实现(忽略通用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( 在这方面,我不认为通用有多大好处。

最新更新