我正在尝试在Rust中实现一个访问者模式。我无法找到一种方法来支持两个返回不同返回值类型的访问者。
Playground link
trait Visited<R> {
fn accept (self: &Self, v: &dyn Visitor<R>) -> R;
}
trait Visitor<R> {
fn visit_a(&self, a: &A<R>) -> R;
fn visit_b(&self, b: &B) -> R;
}
我实现了两个可以访问的数据结构。
// ---- A ----
struct A<R> {
value: String,
b: Box<dyn Visited<R>>,
}
impl<R> Visited<R> for A<R> {
fn accept (&self, v: &dyn Visitor<R>) -> R {
v.visit_a(self)
}
}
// ---- B ----
struct B {
value: i32,
}
impl<R> Visited<R> for B {
fn accept(&self, v: &dyn Visitor<R>) -> R {
v.visit_b(self)
}
}
当我刚有一个混凝土访客的时候,这个工作还不错。
struct Visitor1 {}
impl Visitor<String> for Visitor1 {
fn visit_a(&self, a: &A<String>) -> String {
let b = a.b.accept(self);
format!("visitor1.visit_a(): {} {}", a.value, b)
}
fn visit_b(&self, b: &B) -> String {
format!("visitor1.visit_b(): {}", b.value)
}
}
然而,访问者模式的全部意义在于允许对数据结构应用多种算法。
当我想添加另一个访问者时,我不知道如何让它工作。
struct Visitor2 {}
impl Visitor<i32> for Visitor2 {
fn visit_a(&self, a: &A<i32>) -> i32 {
123
}
fn visit_b(&self, b: &B) -> i32 {
456
}
}
fn main() {
let a = A {
value: "HELLO".to_string(),
b: Box::new(B{ value: 32 })
};
let v1 = Visitor1{};
let s: String = a.accept(&v1);
println!("{}", s);
let v2 = Visitor2{};
let v: i32 = a.accept(&v2);
println!("{}", v);
}
a
的类型被推断为A<String>
,而a.accept(&v2)
导致了类型不匹配错误。
我想告诉a
被Visitor1
和Visitor2
访问,我该怎么做?
2条答案
按热度按时间niknxzdl1#
如果您希望
Visited
trait支持多个返回类型,则不能将返回类型绑定到对象本身。当前,返回类型是泛型,因此每个对象都有硬编码的返回类型。要解决这个问题,可以从
Visited
trait中删除返回类型泛型,并将其附加到accept
函数。但这有一个缺点:你不能再用这个trait创建trait对象了,这是有意义的,因为一旦强制转换为
dyn Visited
,Rust就不知道它是哪种类型了,这使得编译器不可能为所有需要的类型编译accept
函数,它失去了编译后的函数和调用它的类型之间的知识。通常情况下,这是可以的,但在您的情况下,
A
持有Box<dyn Visited>
,由于上述原因,这是不可能的。因此,如果我们将
Box<dyn Visited>
更改为B
(并对泛型进行一些重构),我们可以让它工作:我们可以让这个更方便一点,让实际的类型成为
A
的泛型,与dyn Visited
相反,这允许Rust在编译时解析它的确切类型,使编译成为可能。一个二个一个一个
jjhzyzn02#
如果你只返回
'static
类型,你可以使用type erasure。这个想法是创建一个trait,ErasedVisitor
,它不是泛型的,而是返回Box<dyn Any>
,并为所有Visitor
实现这个trait,并在内部使用它。这意味着,你不能使用泛型参数,只能使用关联的类型(否则你会得到“unconstrained type parameter”:Playground .
但是如果可以的话,最好的方法是使用静态多态(泛型)而不是动态调度。