Rust自定义特质对象
Rust基于特质(trait)的多态机制是我认为很巧妙的设计。Rust用同一套机制解决编译期和运行期的多态,比C++那种模板和面向对象几乎分离的设计要简洁。然而,Rust的运行时多态的灵活性略低,有些特质不能创建特质对象(trait object),也不能自定义哪些方法在特质对象中是动态分派的。这个缺陷在我编写Rust递归函数库 RecurFn 中比较明显。针对这个缺陷,我研究出了一个work around,我称它为自定义特质对象(customized trait object).
举个例子,假设我们有这样的特质定义
trait Foo {
fn foo(&self, f: impl Fn() -> ());
fn foo_twice(&self, f: impl Fn() -> () + Copy) {
self.foo(f);
self.foo(f)
}
}
其中,foo
方法是多态的行为,而foo_twice
本应是函数,放在特质内是为了方便以方法的形式调用,实现不应当重写。
特质Foo
不能直接作为特质对象,因为它的两个方法都接收了一个impl Fn() -> ()
参数。但我们可以做一些转化使得它可以成为特质对象:对于foo
中的impl Fn() -> ()
,可以通过动态的&Fn() -> ()
代替,而且对于foo_twice
方法我们可以让它静态分派。
这样,我们可以得到Foo
特质的一个“动态化”版本。
trait DynFoo {
fn foo(&self, f: &Fn() -> ());
// 由于我们希望foo_twice是静态分派的,这里不出现。
}
这个特质的特质对象dyn DynFoo
就是自定义特质对象。现在我们希望dyn DynFoo
能当作一个Foo
的特质对象使用。也就是说,我们需要实现:
Foo
的实现的指针(包括裸指针、借用、Box
等),它能转化成对应的dyn DynFoo
的指针。
dyn DynFoo
实现了Foo
.
对于1,我们可以通过给所有Foo
的实现实现DynFoo
做到。
impl<F: Foo> DynFoo for F {
fn foo(&self, f: &Fn() -> ()) {
self.foo(f)
}
}
对于2,我们可以通过给dyn DynFoo
实现Foo
做到。
impl Foo for DynFoo {
fn foo(&self, f: impl Fn() -> ()) {
self.foo(&f)
}
}
这样,如下代码就能通过编译。
let foo: &DynFoo = &FooImpl::new(); // 此处FooImpl::new()返回一个实现Foo特质的值。
foo.foo(&|| {});
foo.foo_twice(&|| {});
问题似乎已经解决了。然而,Rust有一个迷之设定:在impl Foo for DynFoo
中,DynFoo
的默认生命周期为'static
,且DynFoo
不能带Send
或Sync
。也就是说,如下代码
fn test_complex<'a>() {
let a: Box<DynFoo + Send + 'a> = Box::new(FooImpl::new());
a.foo_twice(&|| {});
}
是编译不过的。
为了解决这个问题,我们需要将上面的实现代码复制四份,并分别将实现的开头修改成impl<'a> Foo for DynFoo + 'a
, impl<'a> Foo for DynFoo + 'a + Send
, impl<'a> Foo for DynFoo + 'a + Sync
, impl<'a> Foo for DynFoo + 'a + Send + Sync
。四个实现的其余代码完全一样。
为了消除这个代码重复,目前我能想到的方法是用宏。
macro_rules! impl_dyn_with_markers {
($($marker:ident),*) => {
impl<'a> Foo for DynFoo + 'a$( + $marker)* {
fn foo(&self, f: impl Fn() -> ()) {
self.foo(&f)
}
}
};
}
impl_dyn_with_markers! {}
impl_dyn_with_markers! {Send}
impl_dyn_with_markers! {Sync}
impl_dyn_with_markers! {Send, Sync}
这样就能一定程度上减少重复代码。但是用宏有个缺点:使用IDEA的Rust插件重命名方法时,宏内的代码不会被重命名。
这就是我针对Rust特质对象灵活度不足提出的一种解决方案。如果你有更好的方法,欢迎在评论中提出。
本文的示例代码放在这里,这个方法的实际应用案例可参考 RecurFn.
文章来源:
Author:Rust.cc
link:https://rust.cc/article?id=36a82b3e-c604-4919-a3e2-ead1ee8ea0b2