有没有办法用泛型函数实现特征对象?

Is there a way to implement trait objects with generic functions?

提问人:Adnan 提问时间:6/7/2023 更新时间:6/7/2023 访问量:65

问:

基本上,我正在尝试实现访问者编码范式,其中 Expr 特征需要通过二进制结构实现。我想使用 Expr 作为特征对象。任何想要与 Expr 交互的实体都需要实现 Visitors 特征。访客 trait 也应该是一个具有泛型函数的 trait 对象,以便 trait 内部的不同函数可以支持不同的类型。但这会使 Expr 和 Visitors 不是特征对象安全的。有没有办法实现我想要实现的目标?

 use crate::token_type::Token;

pub trait Expr {
    fn accept<T>(&self, visitor: &dyn Visitor) -> T;
}

pub trait Visitor {
    fn visit_binary_expr<T>(&self, expr: Binary) -> T;
}

impl Expr for Binary {
    fn accept<T>(self, visitor: &dyn Visitor) -> T {
        visitor.visit_binary_expr(self)
    }
}

pub struct Binary {
    left: Box<dyn Expr>,
    operator: Token,
    right: Box<dyn Expr>,
}

impl Binary {
    fn new(left: Box<dyn Expr>, operator: Token, right: Box<dyn Expr>) -> Self {
        Self {
            left,
            operator,
            right,
        }
    }
}

struct ASTPrinter {}
impl ASTPrinter {
    fn print(&self, expr: Box<dyn Expr>) -> &str {
        expr.accept(self)
    }
}
impl Visitor for ASTPrinter {
    fn visit_binary_expr(&self, expr: Binary) -> &str {
        "binary"
    }
}
锈蚀 访客模式 特征对象

评论

0赞 Chayim Friedman 6/7/2023
通常,对于 AST 来说,使用枚举比使用 trait 对象更好。
0赞 Adnan 6/7/2023
@ChayimFriedman有什么特别的原因吗?
0赞 Chayim Friedman 6/7/2023
是的:详尽检查,更容易访问,性能原因。

答:

3赞 Chayim Friedman 6/7/2023 #1

首先,重新考虑你是否真的想要特征对象而不是枚举。枚举是对一组封闭类型(如表达式)进行建模的更好方法。

如果您坚持使用特征对象,请重新考虑您的访问者是否真的需要能够返回某些东西。-回头客实施起来非常简单:()

pub trait Expr {
    fn accept(&self, visitor: &mut dyn Visitor);
}

pub trait Visitor {
    fn visit_binary_expr(&mut self, expr: &Binary);
}

impl Expr for Binary {
    fn accept(&self, visitor: &mut dyn Visitor) {
        visitor.visit_binary_expr(self);
    }
}

现在,如果你真的需要特征对象,并且你真的需要返回值,那么你需要一些样板。

这个想法是有一个结果类型擦除的访问者,它包装一个通用访问者,但总是返回,并将内部访问者的结果保留在一个字段中。然后,我们有一个 takes (即返回的访问者 )和一个使用 and 接受任何访问者并返回其结果的包装器:()accept_impl()&mut dyn Visitor<Result = ()>()accept()accept_impl()ErasedVisitor

pub trait Visitor {
    type Result;
    fn visit_binary_expr(&mut self, expr: &Binary) -> Self::Result;
}

struct ErasedVisitor<'a, V: Visitor> {
    visitor: &'a mut V,
    result: Option<V::Result>,
}

impl<V: Visitor> Visitor for ErasedVisitor<'_, V> {
    type Result = ();
    fn visit_binary_expr(&mut self, expr: &Binary) {
        self.result = Some(self.visitor.visit_binary_expr(expr));
    }
}

pub trait Expr {
    fn accept_impl(&self, visitor: &mut dyn Visitor<Result = ()>);
}

pub trait ExprExt: Expr {
    fn accept<V: Visitor>(&self, visitor: &mut V) -> V::Result {
        let mut visitor = ErasedVisitor {
            visitor,
            result: None,
        };
        self.accept_impl(&mut visitor);
        visitor.result.unwrap()
    }
}

impl<E: Expr + ?Sized> ExprExt for E {}

然后使用它就像:

struct ASTPrinter {}
impl ASTPrinter {
    fn print(&mut self, expr: &dyn Expr) -> &'static str {
        expr.accept(self)
    }
}
impl Visitor for ASTPrinter {
    type Result = &'static str;
    fn visit_binary_expr(&mut self, expr: &Binary) -> &'static str {
        "binary"
    }
}