How to have a default trait implementation that refers to self

I have 2 traits

pub trait IBaseNode: AsAny {
    fn get_name(&self) -> String;
    fn add_child(&mut self, child: Box<dyn IBaseNode>);
    fn accept(&self, visitor: &dyn Visitor);
}

and

pub trait Visitor {
    fn visit_program(&self, node: &ProgramNode) where Self: Sized {
        println!("Visit Program");
        for child in &node.base.children {
            child.accept(self)
        }
    }
    fn visit_variable_declaration(&self, node: &Variable_declarationNode) where Self: Sized {
        println!("Visit Variable_declaration");
        for child in &node.base.children {
            child.accept(self)
        }
    }
..... lots more

child is &Box<dyn IBaseNode>

That code works fine if I implement visit_program in a concrete implementation of Visitor, but will not compile here. It complains about self being unsized. The compiler suggested adding the where Self :Sized, but that just produces a 'no can do ' error

error: the `visit_program` method cannot be invoked on a trait object
   --> horn_lox\src\loxvisitor.rs:203:17
    |
63  |     fn visit_program(&self, node: &ProgramNode) where Self: Sized {
    |                                                             ----- this has a `Sized` requirement
...
203 |         visitor.visit_program(self);

Sorry if a dup, I did hunt about trying to find pointers

The problem is that you can only coerce &self to &dyn Visitor if Self: Sized. For example if str: Visitor, you still can't coerce &str to &dyn Visitor.

You can instead provide an object-safe conversion trait for &dyn Visitor...

// The compiler provides an implementation for `dyn Visitor + '_`
pub trait AsVisitor {
    fn as_visitor(&self) -> &dyn Visitor;
}

...implement it for all Sized types that also implement Visistor so they won't have to...

impl<T: Visitor /* + Sized */> AsVisitor for T {
    fn as_visitor(&self) -> &dyn Visitor {
        self
    }
}

...make it a supertrait of Visitor...

pub trait Visitor: AsVisitor {

...and make use of it in your method.

    fn visit_program(&self, node: &ProgramNode) {
        println!("Visit Program");
        for child in &node.base.children {
            child.accept(self.as_visitor())
        }
    }

Er, clarification:

//                                                  vv
// The compiler provides an implementation for `dyn AsVisitor + '_`
pub trait AsVisitor {

// The compiler also provides `impl AsVisitor for dyn Visitor + '_`
// due to the supertrait bound
pub trait Visitor: AsVisitor {

Thanks you very much, works, need to contemplate it to internalize why it works (who said rust is complicated - hah). Having got this working I realized that I do not need any dynamic dispatch on the Visitor type. There will only every be one of them in any mesh of these objects, so I changed

pub trait IBaseNode: AsAny {
    fn get_name(&self) -> String;
    fn add_child(&mut self, child: Box<dyn IBaseNode>);
    fn accept(&self, visitor: &dyn Visitor);
}

to

pub trait IBaseNode: AsAny {
    fn get_name(&self) -> String;
    fn add_child(&mut self, child: Box<dyn IBaseNode>);
    fn accept(&self, visitor: impl Visitor);
}

But that doesnt work either, complaining that this trait is not object safe.Since I prefer to keep unneeded dynamic stuff minimized I wonder how that could be made to work.

It's basically the same approach as manual supertrait upcasting. You have to do a little dance to make the coercion available (as a method) without restricting it to Sized types (which would make the method non-dyn-dispatchable).

APIT is basically a generic type parameter:[1]

-    fn accept(&self, visitor: impl Visitor);
+    fn accept<V: Visitor>(&self, visitor: V);

(Note how this doesn't mean you accept "one specific Visitor", it means you accept "any Visitor"; the caller still chooses the type. There are also some subtleties around the fact you take by value now; for example, you can't take &dyn Visitor or Box<dyn Visitor> unless you implement Visitor for those types.)

Generic methods can't be object safe, hence the error.

What you had originally (&dyn Visitor) is a reasonable way to approximate a generic function in an object-safe way.

(Sometimes using an associated type is an option, depending on what you meant by "only ever be one of them", but in this case my intuition is that it infects your dyn IBaseNode too much.


  1. that you can't turbofish ↩ī¸Ž

once again , thank you. By 'one one of them' I meant that there will only be one type of Visitor, so I dont need to dynamically dispatch on it. That type will be decided by the implementor of the trait

I am sure I am going to be posting a follow up on associated types shortly, I need the visit_xxx methods to return an implementor decidable type and cannot quite work out how to do it (seems like it should be simple - hah ,always run into odd things trying to coerce rust into being an OO language). I'll be back

1 Like

This does not work

fn accept<V: Visitor>(&self, visitor: V);

because of this

fn add_child(&mut self, child: Box<dyn IBaseNode>);

and elsewhere I do dyn IBaseNode , producing

     children: Vec<Box<dyn IBaseNode>>,
   |                       ^^^^^^^^^^^^^ `IBaseNode` cannot be made into an object

Correct, if you need the method to be object safe, it can't have a generic type parameter (no <T: Visitor> and no arg: impl Visitor (which is basically the same thing)).

That's what I was trying to say in the second half of my reply above - they're the same thing, and make the method non-object-safe.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.