在特征对象上使用回调
Using callbacks on trait objects
我正在尝试对特征对象使用回调函数。我将我的问题简化为以下代码 (playpen):
trait Caller {
fn call(&self, call: fn(&Caller)) where Self: Sized {
call(self)
}
}
struct Type;
impl Caller for Type {}
fn callme(_: &Caller) {}
fn main() {
let caller: Box<Caller> = Box::new(Type);
caller.call(callme); // does not work
//callme(&*caller); // works
}
这导致
<anon>:14:12: 14:24 error: the trait `core::marker::Sized` is not implemented for the type `Caller` [E0277]
<anon>:14 caller.call(callme); // does not work
^~~~~~~~~~~~
添加 Sized
绑定到 Caller
结果:
<anon>:3:14: 3:18 error: cannot convert to a trait object because trait `Caller` is not object-safe [E0038]
我真的不明白为什么我需要 Sized
绑定到特征上。有趣的是,如果我直接使用回调,它就可以工作。我如何让它工作?
编辑:多亏了答案,我现在想出了一个不错的解决方案
trait Caller {
fn borrow(&self) -> &Caller;
fn call(&self, call: fn(&Caller)) {
call(self.borrow())
}
}
struct Type;
impl Caller for Type {
fn borrow(&self) -> &Caller { self }
}
fn callme(_: &Caller) {}
fn main() {
let caller: Box<Caller> = Box::new(Type);
caller.call(callme);
}
fn call
中的参数 call
采用特征对象 &Caller
,因此调用它需要将 self
引用(类型 &Self
)强制为&Caller
特征对象。只有当 &Self
是一个瘦指针而不是像特征对象或 &[T]
切片这样的胖指针时,强制转换才有可能。 &Self
恰好在 Self: Sized
时是一个瘦指针。编译器在不为 Sized
的特征中默认为 Self
,因此需要额外的限制。 Sized
trait 表示该类型的大小在编译时已知,无需存储额外信息(在指针旁边,使其成为 "fat")以在运行时计算它。
不幸的是,这留下了一个漏洞:AFAIK,实际上不可能让这样的方法成为默认方法并且仍然能够在特征对象上调用它,因为特征对象 &Caller
有 Self = Caller
这不是 Sized
。但是,如果为每种类型手动实现该方法,它应该可以工作:
trait Caller {
fn call(&self, call: fn(&Caller));
}
struct Type;
impl Caller for Type {
fn call(&self, call: fn(&Caller)) {
call(self)
}
}
fn callme(_: &Caller) {}
fn main() {
let caller: Box<Caller> = Box::new(Type);
caller.call(callme);
}
trait 中的 call
方法声明不再需要 where Self: Sized
因为它不是试图自己进行 trait 对象强制转换,具体实现对&Caller
特征对象获取。对于 Sized
类型,它直接工作,就像原来的 where Self: Sized
代码一样。
我正在尝试对特征对象使用回调函数。我将我的问题简化为以下代码 (playpen):
trait Caller {
fn call(&self, call: fn(&Caller)) where Self: Sized {
call(self)
}
}
struct Type;
impl Caller for Type {}
fn callme(_: &Caller) {}
fn main() {
let caller: Box<Caller> = Box::new(Type);
caller.call(callme); // does not work
//callme(&*caller); // works
}
这导致
<anon>:14:12: 14:24 error: the trait `core::marker::Sized` is not implemented for the type `Caller` [E0277]
<anon>:14 caller.call(callme); // does not work
^~~~~~~~~~~~
添加 Sized
绑定到 Caller
结果:
<anon>:3:14: 3:18 error: cannot convert to a trait object because trait `Caller` is not object-safe [E0038]
我真的不明白为什么我需要 Sized
绑定到特征上。有趣的是,如果我直接使用回调,它就可以工作。我如何让它工作?
编辑:多亏了答案,我现在想出了一个不错的解决方案
trait Caller {
fn borrow(&self) -> &Caller;
fn call(&self, call: fn(&Caller)) {
call(self.borrow())
}
}
struct Type;
impl Caller for Type {
fn borrow(&self) -> &Caller { self }
}
fn callme(_: &Caller) {}
fn main() {
let caller: Box<Caller> = Box::new(Type);
caller.call(callme);
}
fn call
中的参数 call
采用特征对象 &Caller
,因此调用它需要将 self
引用(类型 &Self
)强制为&Caller
特征对象。只有当 &Self
是一个瘦指针而不是像特征对象或 &[T]
切片这样的胖指针时,强制转换才有可能。 &Self
恰好在 Self: Sized
时是一个瘦指针。编译器在不为 Sized
的特征中默认为 Self
,因此需要额外的限制。 Sized
trait 表示该类型的大小在编译时已知,无需存储额外信息(在指针旁边,使其成为 "fat")以在运行时计算它。
不幸的是,这留下了一个漏洞:AFAIK,实际上不可能让这样的方法成为默认方法并且仍然能够在特征对象上调用它,因为特征对象 &Caller
有 Self = Caller
这不是 Sized
。但是,如果为每种类型手动实现该方法,它应该可以工作:
trait Caller {
fn call(&self, call: fn(&Caller));
}
struct Type;
impl Caller for Type {
fn call(&self, call: fn(&Caller)) {
call(self)
}
}
fn callme(_: &Caller) {}
fn main() {
let caller: Box<Caller> = Box::new(Type);
caller.call(callme);
}
trait 中的 call
方法声明不再需要 where Self: Sized
因为它不是试图自己进行 trait 对象强制转换,具体实现对&Caller
特征对象获取。对于 Sized
类型,它直接工作,就像原来的 where Self: Sized
代码一样。