【问题标题】:How can a trait object take a trait with generic methods as an argument?特征对象如何将具有泛型方法的特征作为参数?
【发布时间】:2020-11-21 11:56:09
【问题描述】:

所以 trait 对象不能有带有泛型的方法 - 看起来不错。但是在这种语言中,使用抽象机制的唯一方法是通过泛型和特征对象。这意味着对于每个特征,我必须事先决定它是否可以用作对象,并在任何地方使用 dyn 而不是 impl。并且它内部的所有特征都必须以相同的方式来支持这一点。这种感觉非常难看。您能提出什么建议或告诉我为什么要这样设计吗?

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>,
}

【问题讨论】:

    标签: generics rust traits abstraction trait-objects


    【解决方案1】:

    这意味着对于每个特征,我必须事先决定它是否可以用作对象,并在任何地方使用 dyn 而不是 impl。

    你可以这样做,但幸运的是这不是唯一的选择。

    您也可以像往常一样编写您的特征,在适当的情况下使用泛型。如果/当您需要 trait 对象时,请定义一个您在本地使用的新的对象安全 trait,并在该位置公开您实际需要的 API 子集。

    例如,假设您拥有或使用了非对象安全的特征:

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

    该特征不能用作特征对象,因为它(可能是为了确保最大效率)具有通用方法。但这并不需要阻止您的代码使用特征对象来访问特征的功能。假设您需要将 Serialize 值装箱,以便将它们保存在一个向量中,您将把它们保存到一个文件中en masse

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

    上面的代码不能编译,因为Serialize 不是对象安全的。但是 - 您可以轻松定义一个新的对象安全 trait 来满足 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);
        }
    }
    

    现在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
        ...
    }
    

    定义一个单独的对象安全特征似乎是不必要的工作 - 如果原始特征足够简单,您当然可以从一开始就使其成为对象安全。但是有些特征要么太通用,要么太面向性能,从一开始就无法做到对象安全,在这种情况下,最好记住保持通用性是可以的。当您确实需要对象安全版本时,它通常用于具体任务,其中根据原始特征实现的自定义对象安全特征将完成这项工作。

    【讨论】:

    • SerializeFile 特征中,您使用具体结构 BufWriter 但原始特征 (Serialize) 参数是 Write,这是特征。这是骗人的。如果你不知道具体类型是BufWriter怎么办?
    • 另外,如果您需要在某个时候返回 Serialize 实例怎么办?你如何将SerializeFile 转换回Serialize
    • @Vlad 我理解为什么它会让人觉得作弊,但它说明了不同的观点。 SerializeFile 是应用于文件的Serialize,是针对Pool 的需求量身定制的本地 和更狭窄的特征。当然,您可以更进一步,使SerializeFile::serializeaccept &amp;mut dyn io::Write,但这也可能被视为作弊,因为io::Write 恰好是对象安全的,而在您的原始示例中,接受为参数的特征不是。
    • 答案认为创建一个具有 不同 接口的新特征是可以的,它牺牲了原始特征的一些通用性以实现对象安全,并实现它(和这是最重要的部分)不修改原始特征。正因为如此,你不需要担心一个 trait 提前 不是对象安全的,因为你可以在以后使用它来创建一个对象安全的。
    • @Vlad 另请参阅this answer 以获取应用对象不安全标准特征 (std::hash::Hash) 来创建实现 Hash 并可用作哈希的类型擦除包装器的实际示例表键。它还定义了一个 is 对象安全的自定义特征,并在其总括 impl 中计算哈希,我们确实有具体类型并且可以调用 &lt;T as Hash&gt;::hash(),即使 Hash 不是对象-安全的。也许这是对这个概念的更好展示。
    【解决方案2】:

    我使用了@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)
        }
    }
    

    如果有此或更轻量级实现的宏,请发表评论。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-10-05
      • 2012-05-17
      • 2019-10-23
      • 1970-01-01
      • 2022-12-03
      • 2023-01-12
      相关资源
      最近更新 更多