在特征对象上使用回调

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,实际上不可能让这样的方法成为默认方法并且仍然能够在特征对象上调用它,因为特征对象 &CallerSelf = 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 代码一样。