我正在尝试在特征对象上使用回调函数。我将我的问题减少到以下代码(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);
}
答案 0 :(得分:3)
call
中的参数fn call
采用特征对象&Caller
,因此调用它需要将self
引用(类型&Self
)强制转换为{ {1}}特质对象。只有当&Caller
是一个瘦指针而不是像特征对象或&Self
切片这样的胖指针时,强制才有可能。 &[T]
恰好是&Self
时的精简指针。在不是Self: Sized
的特征中,编译器默认为Self
,因此需要额外的限制。 Sized
trait表示该类型具有在编译时已知的大小,不需要存储额外信息(指针旁边,使其“胖”)以在运行时计算它。
不幸的是,这留下了一个漏洞:AFAIK,实际上不可能让这样的方法成为默认方法,并且仍然能够在特征对象上调用它,因为特征对象Sized
具有{{1}这不是&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);
}
,因为它本身并没有尝试执行特征对象强制,并且具体实现对{{{{{{{{ 1}}获得特质对象。对于call
类型,它可以直接使用,就像原始的where Self: Sized
代码一样。