在特征对象上使用回调

时间:2015-03-09 11:19:26

标签: rust

我正在尝试在特征对象上使用回调函数。我将我的问题减少到以下代码(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);
}

1 个答案:

答案 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代码一样。