简体   繁体   English

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

[英]Patterns for proxying and implementation hiding in Rust?

So I'm an experienced developer, pretty new to Rust, expert in Java - but started out in assembly language, so I get memory and allocation, and have written enough compiler-y things to fathom the borrow-checker pretty well.所以我是一位经验丰富的开发人员,对 Rust 非常陌生,在 Java 方面的专家 - 但从汇编语言开始,所以我得到了 memory 和分配器,并且已经编写了足够好的编译器和借用检查器

I decided to port a very useful, high-performance bitset-based graph library I wrote in Java, both to use it in a larger eventual project, and because it's darned useful.我决定移植一个我在 Java 中编写的非常有用的、基于高性能位集的图形库,既可以在更大的最终项目中使用它,也因为它非常有用。 Since it's all integer positions in bitsets, and if you want an object graph you map indices into an array or whatever - I'm not tangled up in building a giant tree of objects that reference each other, which would be a mess to try to do in Rust.因为它是位集中的所有 integer 位置,并且如果你想要一个 object 图表你 map 到一个数组或任何其他索引尝试尝试将对象,这将是构建一个巨大的树在 Rust 中执行。 The problem I'm trying to solve is much simpler - so simple I feel like I must be missing an obvious pattern for how to do it:我要解决的问题要简单得多 - 如此简单,我觉得我必须错过一个明显的模式来解决它:

Looking over the various bit-set libraries available for Rust, FixedBitSet seemed like a good fit.查看可用于 Rust 的各种位集库, FixedBitSet似乎很合适。 However , I would rather not expose it via my API and tie every consumer of my library irrevocably to FixedBitSet (it is nice, but, it can also be useful to swap in, say, an implementation backed by atomics; and being married to usize may not be ideal, but FixedBitSet is).但是,我宁愿不通过我的 API 公开它,并将我的库的每个使用者不可撤销地绑定到usize (这很好,但是,它也可以用于交换,例如,由原子支持的实现;并嫁给使用可能不理想,但 FixedBitSet 是)。

In Java, you'd just create an interface that wraps an instance of the concrete type, and exposes the functionality you want, hiding the implementation type.在 Java 中,您只需创建一个包装具体类型实例的接口,并公开您想要的功能,隐藏实现类型。 In Rust, you have traits, so it's easy enough to implement:在 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)
    }
...

this gets a little ugly in that, if I don't want to expose FixedBitSet , everything has to return Box<dyn Bits<'a, usize> + 'a> , but so be it for now - though that creates its own problems with the compiler not knowing the size of dyn Bits... .这有点难看,如果我不想公开FixedBitSet ,一切都必须返回Box<dyn Bits<'a, usize> + 'a> ,但现在就这样 - 尽管这会产生自己的问题编译器不知道dyn Bits...

So far so good.到目前为止,一切都很好。 Where it gets interesting (did I say this was a weirdly simple problem?) is proxying an iterator .有趣的地方(我说这是一个非常简单的问题吗?)是代理一个迭代器 Rust iterators seem to be irrevocably tied to a concrete Trait type which has an associated type . Rust 迭代器似乎不可撤销地绑定到具有关联类型的具体 Trait 类型 So you can't really abstract it (well, sort of, via Box<dyn Iterator<Item=usize> + 'a> where... , and it looks like it might be possible to create a trait that extends iterator and also has a type Item on it, and implement it for u32 , u64 , usize and?? maybe the compiler coalesces the type Item members of the traits?).所以你不能真正抽象它(嗯,有点,通过Box<dyn Iterator<Item=usize> + 'a> where... ,看起来有可能创建一个扩展迭代器的特征,也上面有一个type Item ,并为u64 u32 usize和?? 实现它,也许编译器会合并特征的type Item成员?)。 And as far as I can tell, you can't narrow the return type in a trait implementation method to something other than the trait specifies, either.而且据我所知,您也不能将 trait 实现方法中的返回类型缩小到 trait 指定的其他类型。

This gets further complicated by the fact that the Ones type in FixedBitSet for iterating set bits has its own lifetime - but Rust's Iterator does not - so any generic Iterator implementation is going to need to return an iterator scoped to that lifetime, not '_ or there will be issues with how long the iterator lives vis-a-vis the thing that created it.由于 FixedBitSet 中用于迭代集合位的Ones类型有自己的生命周期,这变得更加复杂 - 但 Rust 的 Iterator 没有 - 所以任何通用的 Iterator 实现都需要返回一个范围为该生命周期的迭代器,而不是'_或迭代器与创建它的事物相比存在多长时间会存在问题。

The tidiest thing I could come up with - which is not tidy at all - after experimenting with various containers for an iterator (an implementation of Bits that adds an offset to the base value is also useful) that expose it, was something like:我能想出的最整洁的东西 - 这一点都不整洁 - 在尝试了各种容器的迭代器(向基值添加偏移量的 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;
}

which is implementable enough:这是足够可实施的:

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()
    }
}

but then, we know we're going to be dealing with Boxes.但是,我们知道我们将要处理 Boxes。 So, we're going to need an implementation for that.所以,我们需要一个实现。 Great!伟大的! A signature like impl<'a, 'b> Biterable<'a, 'b, usize, Ones<'b>> for Box<FixedBitSet> where 'a: 'b { is implementable.类似impl<'a, 'b> Biterable<'a, 'b, usize, Ones<'b>> for Box<FixedBitSet> where 'a: 'b {是可实现的。 BUUUUUUT , that's not what anything is going to return if we're not exposing FixedBitSet anywhere - we need it for Box<dyn Bits<...> +...> . BUUUUUUT ,如果我们不在任何地方公开FixedBitSet ,那将不会返回任何东西 - 我们需要Box<dyn Bits<...> +...> For that , we wind up in a hall of mirrors, scribbling out increasingly baroque and horrifying (and uncompilable) variants on为此我们最终进入了一个镜子大厅,在上面写下越来越巴洛克和可怕(且无法编译)的变体

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

in a vain search for something that compiles and works (this fails because while Bits and Biterable are traits, evidently Biterable + Bits is not a trait).徒劳地寻找可以编译和工作的东西(这失败了,因为虽然BitsBiterable是特征,但显然Biterable + Bits不是特征)。 Seriously - a stateless, no-allocation-needed wrapper for one call on this thing returning one call on that thing, just not exposing that thing's type to the caller.说真的 - 一个无状态的、不需要分配的包装器,用于对该事物的一次调用返回对该事物的一次调用,只是不将该事物的类型暴露给调用者。 That's it.而已。 The Java equivalent would be Supplier<T> a =...; return () -> a.get(); Java 等效项将是Supplier<T> a =...; return () -> a.get(); Supplier<T> a =...; return () -> a.get();

I have to be thinking about this problem wrong.我必须把这个问题想错了。 How?如何?

It does certainly seem like you're over-complicating things.看起来你确实把事情复杂化了。 You have a lot of lifetime annotations that don't seem necessary.您有很多似乎没有必要的生命周期注释。 Here 'sa straightforward implementation (ignoring generic S ): 是一个简单的实现(忽略通用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)
}

If you want the index type to be anonymous as well, make it an associated type and not define it when exposing your Bits :如果您希望索引类型也是匿名的,请将其设为关联类型,并且在公开您的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() {
            // ...
        }
    }
}

I highly encourage against this though for many reasons.尽管出于多种原因,我强烈反对这样做。 1) You'd need many more constraints than just Add for this to be remotely usable. 1)您需要更多的约束,而不仅仅是Add才能远程使用。 2) You are severely limited with impl Bits ; 2)您在impl Bits方面受到严重限制; its not fully defined so you can't have dyn Bits or store it in a struct.它没有完全定义,因此您不能拥有dyn Bits或将其存储在结构中。 3) I don't see much benefit in being generic in this regard. 3)我认为在这方面通用并没有多大好处。

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

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