特征对象如何将具有泛型方法的特征作为参数?
How can a trait object take a trait with generic methods as an argument?
所以 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>,
}
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.
你可以这样做,但幸运的是这不是唯一的选择。
您也可以像往常一样编写特征,在适当的地方使用泛型。 If/when 你需要特征对象,定义一个你在本地使用的新的对象安全特征,它公开了你在那个地方实际需要的 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
不是对象安全的。但是 - 您可以轻松定义一个新的对象安全特征来满足 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
...
}
定义一个单独的对象安全特征似乎是不必要的工作 - 如果原始特征足够简单,您当然可以从一开始就使其成为对象安全的。但是有些特征要么过于笼统,要么过于以性能为导向,无法从一开始就使其成为对象安全的,在这种情况下,最好记住保持它们的通用性是可以的。当您确实需要对象安全版本时,它通常用于具体任务,其中根据原始特征实现的自定义对象安全特征将完成这项工作。
我使用了@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)
}
}
如果有这个或更轻量级实现的宏,请发表评论。
所以 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>,
}
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.
你可以这样做,但幸运的是这不是唯一的选择。
您也可以像往常一样编写特征,在适当的地方使用泛型。 If/when 你需要特征对象,定义一个你在本地使用的新的对象安全特征,它公开了你在那个地方实际需要的 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
不是对象安全的。但是 - 您可以轻松定义一个新的对象安全特征来满足 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
...
}
定义一个单独的对象安全特征似乎是不必要的工作 - 如果原始特征足够简单,您当然可以从一开始就使其成为对象安全的。但是有些特征要么过于笼统,要么过于以性能为导向,无法从一开始就使其成为对象安全的,在这种情况下,最好记住保持它们的通用性是可以的。当您确实需要对象安全版本时,它通常用于具体任务,其中根据原始特征实现的自定义对象安全特征将完成这项工作。
我使用了@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)
}
}
如果有这个或更轻量级实现的宏,请发表评论。