Question about using traits again (c++ virtual methods)

Hello,

I'm again back with a question about traits and if I'm using them correctly.
(I want to rewrite my C++ ast.h/c++ in Rust.)

In C++ the Int, Plus and MultExp classes would inherit from Base class Exp.
But in Rust inheritance goes only by traits. In my previous question people answered that traits should only be used (wit mentioned exceptions) for interfaces. But could they work in the case of virtual methods and overloading too?
Since traits only have methods and no fields the Base class I had in C++ should be unnecessary.
But then each class would need the beenThere variable, no?

/*
struct Exp{         
    beenThere: bool, 
}
 */


struct IntExp{      
  beenThere: bool,
  val: int
}

struct PlusExp{
  beenThere: bool, 
  e1: Rc<Exp>,    
  e2: Rc<Exp>
}

struct MultExp{
  beenThere: bool, 
  e1: Rc<Exp>,
  e2: Rc<Exp>
}

In my commented pretty_eval I put all methods together at first because I thought like in C++ when I implement this trait for each struct the set_beenThere method would have the same behaviour and only eval and pretty would need to be changed via overloading.

/* 
trait pretty_eval{
  
  fn set_beenThere(&mut self, beenThere: bool){
    self.beenThere = beenThere;
  }
  

  fn eval(&mut self)->int;
  fn pretty(&mut self)->string;
}
*/

Would that be wrong or should it be used like the following so the set method is in a separate trait and another trait inherits from it?

trait Exp{  
  fn set_beenThere(&mut self, beenThere: bool){
    self.beenThere = beenThere;
  }
}


trait ExpAdv : Exp{  
  fn eval(&mut self)->int;
  fn pretty(&mut self)->string;
}

Since the variables change over time it needs to be mutable but is it also correct to use a reference here? If I would use mut self the ownership would need to be given back when leaving the function, right?

impl ExpAdv for IntExp{       
  fn eval(&mut self)->int{   
    return self.val;
  }

  fn pretty(&mut self)->string{
    return to_string(self.val); 
  }
}

In the following I have the trait impl for Plus/MultExp. A general question for the self parameter:
In other examples the self parameter was always in the signature. Is it necessary to include it when calling the function? For example calling it like: e1.eval() or e1.eval(self)?

impl ExpAdv for PlusExp{  

  fn eval(&mut self)->int{
    return e1.eval() + e2.eval();
  }

  fn pretty(&mut self)->string{
    
    let s = "";

    if(self.beenThere){
      s.append("("); 
      s.append(e1.pretty());
      s.append("+");
      s.append(e2.pretty());
      s.append(")");
  }
      
  else{ 
      s.append(""); 
      s.append(e1.pretty());
      s.append("+");
      s.append(e2.pretty());
      s.append(""); 
  }

  return s;
  }
}


impl ExpAdv for MultExp{

  fn eval(&mut self)->int{
    return e1.eval() *  e2.eval();
  }

  fn pretty(&mut self)->string{
    e1.set_beenThere(true);
    e2.set_beenThere(true);
    
    let s = "";
    s.append(e1.pretty());
    s.append("*");
    s.append(e2.pretty());
    s.append("");
    
    return s;
} 
}

And at last in C++ I had shortcuts for making new objects for each class. I read that in Rust there is no constructor (some do it anyway because of convention). But in this case one wouldn't need shortcuts for it then either?


/* 
struct EXP{
  exp: Rc<Exp>
}

impl EXP{

  fn newInt(&mut self, i: int)->Rc<IntExp>{
    return Rc::new(IntExp{exp:i});
  }

  fn newPlus(&mut self, a: EXP, b:EXP)->Rc<PlusExp>{
    return Rc::new(PlusExp{e1: a, e2: b});
  }

  fn newMult(&mut self, a: EXP, b:EXP)->Rc<MultExp>{
    return Rc::new(MultExp{e1: a, e2: b});
  }

}
*/

Here the c++ header (left) and source file (right) of my project.

You likely need enum dispatch in order to build your expression tree.

struct ExpOuter {
  visited: Cell<bool>;
  inner: ExpInner;
}

enum ExpInner {
  Immediate(i32),
  Plus(ExpPlus),
  Sub(ExpSub),
  Mul(ExpMul),
  // More variants...
}

pub type Exp = Rc<ExpOuter>; 

struct ExpSub {
  left: Exp;
  right: Exp;
}

// More variants...

There are crates like enum_dispatch that can help with doing this and routing the function calls. enum_dispatch - Rust

3 Likes

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.