简体   繁体   English

trait 对象如何将带有泛型方法的 trait 作为参数?

[英]How can a trait object take a trait with generic methods as an argument?

So trait objects can't have methods with generics - that looks fine.所以 trait 对象不能有带有泛型的方法——这看起来不错。 But in this language the only ways to use abstraction mechanism are available through generics and trait objects.但是在这种语言中,使用抽象机制的唯一方法是通过泛型和特征对象。 Which means that for each trait I have to decide beforehand if it can be used as an object at all and use dyn in there everywhere instead of impl.这意味着对于每个特征,我必须事先决定它是否可以用作对象并在那里到处使用 dyn 而不是 impl。 And all taken traits inside it must be made same way to support this.并且必须以相同的方式制作其中的所有特征以支持这一点。 This feel very ugly.这种感觉非常难看。 Can you suggest anything or tell me why it's designed this way?你能提出什么建议或告诉我为什么它是这样设计的吗?

fn main() {}

// some abstracted thing
trait Required {
    fn f(&mut self, simple: i32);
}

// this trait doesn't know that it's going to be used by DynTrait
// it just takes Required as an argument
// nothing special
trait UsedByDyn {
    // this generic method doesn't allow this trait to be dyn itself
    // no dyn here: we don't know about DynTrait in this scope
    fn f(&mut self, another: impl Required);
}

// this trait needs to use UsedByDyn as a function argument
trait DynTrait {
    // since UsedByDyn uses generic methods it can't be dyn itself
    // the trait `UsedByDyn` cannot be made into an object
    //fn f(&mut self, used: Box<dyn UsedByDyn>);

    // we can't use UsedByDyn without dyn either otherwise Holder can't use us as dyn
    // the trait `DynTrait` cannot be made into an object
    // fn f(&mut self, used: impl UsedByDyn);

    // how to use UsedByDyn here?
}

struct Holder {
    CanBeDyn: Box<dyn DynTrait>,
}

Which means that for each trait I have to decide beforehand if it can be used as an object at all and use dyn in there everywhere instead of impl.这意味着对于每个特征,我必须事先决定它是否可以用作对象并在那里到处使用 dyn 而不是 impl。

You can do that, but fortunately it's not the only option.您可以这样做,但幸运的是,这不是唯一的选择。

You can also write your traits as you normally would, using generics where appropriate.你也可以像往常一样编写你的特征,在适当的地方使用泛型。 If/when you need trait objects, define a new object-safe trait that you use locally, and that exposes the subset of the API you actually need in that place.如果/当您需要 trait 对象时,定义一个您在本地使用的新的对象安全 trait,并在该位置公开您实际需要的 API 子集。

For example, let's say you have or use a non-object-safe trait:例如,假设您拥有或使用非对象安全特性:

trait Serialize {
    /// Serialize self to the given IO sink
    fn serialize(&self, sink: &mut impl io::Write);
}

That trait is not usable as a trait object because it (presumably to ensure maximum efficiency) has a generic method.该 trait 不能用作 trait 对象,因为它(大概是为了确保最大效率)有一个通用方法。 But that needn't stop your code from using trait objects to access the functionality of the trait.但这并不需要阻止您的代码使用 trait 对象来访问 trait 的功能。 Say you need to box Serialize values in order to hold them in a vector, which you will save into a file en masse :假设您需要向框Serialize ,以保持他们在一个载体,你会保存到一个文件中集体值:

// won't compile
struct Pool {
    objs: Vec<Box<dyn Serialize>>,
}

impl Pool {
    fn add(&mut self, obj: impl Serialize + 'static) {
        self.objs.push(Box::new(obj) as Box<dyn Serialize>);
    }

    fn save(&self, file: &Path) -> io::Result<()> {
        let mut file = io::BufWriter::new(std::fs::File::create(file)?);
        for obj in self.objs.iter() {
            obj.serialize(&mut file);
        }
        Ok(())
    }
}

The above doesn't compile because Serialize is not object safe.上面的代码无法编译,因为Serialize不是对象安全的。 But - you can easily define a new object-safe trait that fulfills the needs of Pool :但是 - 您可以轻松定义满足Pool需求的新对象安全特性:

// object-safe trait, Pool's implementation detail
trait SerializeFile {
    fn serialize(&self, sink: &mut io::BufWriter<std::fs::File>);
}

// Implement `SerializeFile` for any T that implements Serialize
impl<T> SerializeFile for T
where
    T: Serialize,
{
    fn serialize(&self, sink: &mut io::BufWriter<std::fs::File>) {
        // here we can access `Serialize` because `T` is a concrete type
        Serialize::serialize(self, sink);
    }
}

Now Pool pretty much just works, using dyn SerializeFile ( playground ):现在Pool几乎可以正常工作,使用dyn SerializeFile ( playground ):

struct Pool {
    objs: Vec<Box<dyn SerializeFile>>,
}

impl Pool {
    fn add(&mut self, obj: impl Serialize + 'static) {
        self.objs.push(Box::new(obj) as Box<dyn SerializeFile>);
    }

    // save() defined the same as before
    ...
}

Defining a separate object-safe trait may seem like unnecessary work - if the original trait is simple enough, you can certainly make it object-safe to begin with.定义一个单独的对象安全特性似乎是不必要的工作——如果原始特性足够简单,你当然可以从一开始就让它成为对象安全的。 But some traits are either too general or too performance-oriented to be made object-safe from the get-go, and in that case it's good to remember that it's ok to keep them generic.但是有些特征要么太笼统,要么太注重性能,以至于不能从一开始就成为对象安全的,在这种情况下,最好记住保持它们的通用性是可以的。 When you do need an object-safe version, it will typically be for a concrete task where a custom object-safe trait implemented in terms of the original trait will do the job.当您确实需要一个对象安全版本时,它通常用于具体任务,其中根据原始特征实现的自定义对象安全特征将完成这项工作。

I used the @user4815162342's answer but made my own version that doesn't require replacing a non-object-friendly trait with a concrete type.我使用了@user4815162342 的答案,但制作了我自己的版本,不需要用具体类型替换非对象友好特性。

struct Holder {
    dyn_traits: Vec<Box<dyn DynTrait>>,
}

// this trait doesn't know that it's going to be used by DynTrait
// it just takes ObjectFriendly as an argument
// nothing special
trait ObjectUnfriendly {
    // this generic method doesn't allow this trait to be dyn itself
    // no dyn here: we don't know about DynTrait in this scope
    fn f(&mut self, another: &impl ObjectFriendly);
    fn f2(&mut self, another: &mut impl ObjectFriendly);
    fn f3(&mut self, another: impl ObjectFriendly);
}

trait ObjectFriendly {
    fn f(&mut self, simple: i32);
    fn f2(&self, simple: i32);
}

// this trait needs to use the trait above as a function argument
trait DynTrait {
    // since that trait uses generic methods it can't be dyn itself
    // the trait cannot be made into an object
    //fn f(&mut self, used: Box<dyn ObjectUnfriendly>);

    // we can't use that trait without dyn either otherwise Holder can't use us as dyn
    // the trait `DynTrait` cannot be made into an object
    // fn f(&mut self, used: impl ObjectUnfriendly);

    // how to use ObjectUnfriendly here?
    // we use our own extension trait that is object-friendly
    fn f(&mut self, used: dyn NowObjectFriendly);
}

// our own object-friendly version
trait NowObjectFriendly {
    // if arguments are ObjectFriendly - we are lucky
    fn f(&mut self, another: &dyn ObjectFriendly);
    fn f2(&mut self, another: &mut dyn ObjectFriendly);
    fn f3(&mut self, another: Box<dyn ObjectFriendly>);

    // if not - we can just accept the specific struct we need
    // fn f3(&mut self, another: SomeImpl);

    // or do the same thing by making an extension trait
    // fn f3(&mut self, another: Box<dyn ObjectFriendly2Ex>);
}

// delegate implementation
impl<T: ObjectUnfriendly> NowObjectFriendly for T {
    fn f(&mut self, another: &dyn ObjectFriendly) {
        self.f(&ObjectFriendly2AsImpl(another));
    }

    fn f2(&mut self, another: &mut dyn ObjectFriendly) {
        self.f2(&mut ObjectFriendly2AsImpl(another));
    }

    fn f3(&mut self, another: Box<dyn ObjectFriendly>) {
        self.f3(ObjectFriendly2AsImpl(another));
    }

    // if not object friendly - we can just accept the specific struct we need
    // fn f3(&mut self, another: SomeImpl) {
    //     SomeImpl::f3(self, another);
    // }

    // or do the same thing for that trait by making another extension trait
    // fn f3(&mut self, another: Box<dyn ObjectFriendly2Ex>) {
    //     self.f3(another);
    // }
}

// for this delegation to work
// we need to make it convertible to impl

// can't implement foreign traits on foreign types
struct ObjectFriendly2AsImpl<T>(T);

impl ObjectFriendly for ObjectFriendly2AsImpl<&dyn ObjectFriendly> {
    fn f(&mut self, simple: i32) {
        unreachable!()
    }

    fn f2(&self, simple: i32) {
        (*self.0).f2(simple)
    }
}

impl ObjectFriendly for ObjectFriendly2AsImpl<&mut dyn ObjectFriendly> {
    fn f(&mut self, simple: i32) {
        (*self.0).f(simple)
    }

    fn f2(&self, simple: i32) {
        (*self.0).f2(simple)
    }
}

impl ObjectFriendly for ObjectFriendly2AsImpl<Box<dyn ObjectFriendly>> {
    fn f(&mut self, simple: i32) {
        (*self.0).f(simple)
    }

    fn f2(&self, simple: i32) {
        (*self.0).f2(simple)
    }
}

If there is a macro for this or more lightweight implementation please comment.如果有针对此或更轻量级实现的宏,请发表评论。

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

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