如何在Rust中支持两个返回不同返回值类型的访问者?

x33g5p2x  于 2022-12-19  发布在  其他
关注(0)|答案(2)|浏览(193)

我正在尝试在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)导致了类型不匹配错误。
我想告诉aVisitor1Visitor2访问,我该怎么做?

niknxzdl

niknxzdl1#

如果您希望Visited trait支持多个返回类型,则不能将返回类型绑定到对象本身。当前,返回类型是泛型,因此每个对象都有硬编码的返回类型。
要解决这个问题,可以从Visited trait中删除返回类型泛型,并将其附加到accept函数。
但这有一个缺点:你不能再用这个trait创建trait对象了,这是有意义的,因为一旦强制转换为dyn Visited,Rust就不知道它是哪种类型了,这使得编译器不可能为所有需要的类型编译accept函数,它失去了编译后的函数和调用它的类型之间的知识。
通常情况下,这是可以的,但在您的情况下,A持有Box<dyn Visited>,由于上述原因,这是不可能的。
因此,如果我们将Box<dyn Visited>更改为B(并对泛型进行一些重构),我们可以让它工作:

trait Visited {
    fn accept<V: Visitor>(&self, v: &V) -> V::Ret;
}

trait Visitor {
    type Ret;
    fn visit_a(&self, a: &A) -> Self::Ret;
    fn visit_b(&self, b: &B) -> Self::Ret;
}

// ---- A ----
struct A {
    value: String,
    b: B,
}

impl Visited for A {
    fn accept<V: Visitor>(&self, v: &V) -> V::Ret {
        v.visit_a(self)
    }
}

// ---- B ----
struct B {
    value: i32,
}
impl Visited for B {
    fn accept<V: Visitor>(&self, v: &V) -> V::Ret {
        v.visit_b(self)
    }
}

struct Visitor1 {}
impl Visitor for Visitor1 {
    type Ret = String;
    fn visit_a(&self, a: &A) -> 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 for Visitor2 {
    type Ret = i32;
    fn visit_a(&self, _a: &A) -> i32 {
        123
    }
    fn visit_b(&self, _b: &B) -> i32 {
        456
    }
}

fn main() {
    let a = A {
        value: "HELLO".to_string(),
        b: 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);
}
visitor1.visit_a(): HELLO visitor1.visit_b(): 32
123

我们可以让这个更方便一点,让实际的类型成为A的泛型,与dyn Visited相反,这允许Rust在编译时解析它的确切类型,使编译成为可能。
一个二个一个一个

jjhzyzn0

jjhzyzn02#

如果你只返回'static类型,你可以使用type erasure。这个想法是创建一个trait,ErasedVisitor,它不是泛型的,而是返回Box<dyn Any>,并为所有Visitor实现这个trait,并在内部使用它。这意味着,你不能使用泛型参数,只能使用关联的类型(否则你会得到“unconstrained type parameter”:

trait Visited {
    fn accept_dyn(&self, v: &dyn ErasedVisitor) -> Box<dyn Any>;
    fn accept<V: Visitor>(&self, v: &V) -> V::Result
    where
        Self: Sized,
    {
        *self.accept_dyn(v).downcast().unwrap()
    }
}

impl<T: ?Sized + Visited> Visited for &'_ T {
    fn accept_dyn(&self, v: &dyn ErasedVisitor) -> Box<dyn Any> {
        T::accept_dyn(&**self, v)
    }
}

impl<T: ?Sized + Visited> Visited for &'_ mut T {
    fn accept_dyn(&self, v: &dyn ErasedVisitor) -> Box<dyn Any> {
        T::accept_dyn(&**self, v)
    }
}

impl<T: ?Sized + Visited> Visited for Box<T> {
    fn accept_dyn(&self, v: &dyn ErasedVisitor) -> Box<dyn Any> {
        T::accept_dyn(&**self, v)
    }
}

trait Visitor {
    type Result: 'static;
    fn visit_a(&self, a: &A) -> Self::Result;
    fn visit_b(&self, b: &B) -> Self::Result;
}

trait ErasedVisitor {
    fn visit_a(&self, a: &A) -> Box<dyn Any>;
    fn visit_b(&self, b: &B) -> Box<dyn Any>;
}

impl<V: ?Sized + Visitor> ErasedVisitor for V {
    fn visit_a(&self, a: &A) -> Box<dyn Any> {
        Box::new(<Self as Visitor>::visit_a(self, a))
    }
    fn visit_b(&self, b: &B) -> Box<dyn Any> {
        Box::new(<Self as Visitor>::visit_b(self, b))
    }
}

struct A {
    value: String,
    b: Box<dyn Visited>,
}

impl Visited for A {
    fn accept_dyn(&self, v: &dyn ErasedVisitor) -> Box<dyn Any> {
        v.visit_a(self)
    }
}

Playground .
但是如果可以的话,最好的方法是使用静态多态(泛型)而不是动态调度。

相关问题