How to define a common behaviour for some “subclasses” of a trait?

There is this pattern where some functionality of an abstract class is provided by its abstract subclass, like this (TypeScript):

abstract class Foo {  // could also be an interface
    abstract foo(): number;
}

abstract class FooFromBar {
    foo() { return this.bar() + 1; }
    abstract bar(): number;
}

class Data1 extends FooFromBar {
    bar() { return 5; }
}

alert(new Data1().foo());  // 6

Now I can have more classes extending FooFromBar that use the foo=bar+1 functionality, plus more classes extending Foo directly providing custom foo.

Now I want the same in Rust. My intuition is that the abstract classes become traits, and concrete classes become e.g. structs implementing them - but that might be wrong, I'm quite new to Rust. And the problem is I don't know how to get foo implemented automatically for types implementing FooFromBar. The best I got is this:

trait Foo{
    fn foo(&self)->i32;
}

trait FooFromBar:Foo{
    fn bar(&self)->i32;
}
impl<S:FooFromBar> Foo for S{
    fn foo(&self)->i32{self.bar()+1}
}

struct DataBar;
impl FooFromBar for DataBar{
    fn bar(&self)->i32{5}
}

fn main() {
    println!("{}",DataBar.foo());  // 6
}

It works, but there's a serious problem with this approach. If I add another "sub-trait", e.g. FooFromBaz implementing foo=baz+2 like this:

trait FooFromBaz:Foo{
    fn baz(&self)->i32;
}
impl<S:FooFromBaz> Foo for S{
    fn foo(&self)->i32{self.baz()+2}
}

then I get an error that there are conflicting implementations:

error[E0119]: conflicting implementations of trait `Foo`:
  --> src/main.rs:19:1
   |
8  | impl<S:FooFromBar> Foo for S{
   | ---------------------------- first implementation here
...
19 | impl<S:FooFromBaz> Foo for S{
   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ conflicting implementation

It looks to me that they are only conflicting if someone actually creates a type that implements both FooFromBar and FooFromBaz - that would really mean they did an unreasonable thing. But instead it fails even without any structs implementing any of them.

So the question is how to obtain a similar behaviour correctly? Is there some better generics magic to obtain that, or is it just something that should be solved completely differently in Rust and the problem is just that I'm stuck with thinking about classes?

(Question posted originally on StackOverflow: link)

You simply can't have overlapping impls like that, except using the nightly-only feature known as specialization.

I'd really like to know how to implement the pattern I mentioned correctly and "the Rust way".

I'm not writing this to complain about not being able to have overlapping impls. I can live with this, as long as there is another sensible way to achieve the goal. And for now I'd like to explore the options in the stable build.

I’m not entirely clear what your goal is. The original pattern you posted isn’t a good fit for Rust, and there’s not an obvious mapping to how Rust does things. If you can give a more concrete example, where the goal is some program behavior instead of an architecture pattern, there’s probably an idiomatic way to do it in Rust.

In particular, for future-compatibility reasons, the Rust compiler always treats the types that implement a trait as an open set, and there’s no way to tell it that two traits will always be exclusive of each other.

1 Like

Well I honestly don't see where the conflict is, but rust isn't really fully OOP... So it wasn't really meant to be used like that. It wasn't meant to deal with OOP concepts like inheritance. traits are more like interfaces. You would do better to just do it differently I guess. Like the others said, give an example of what you want to do instead of structure.

OK, I'm not convinced this will make things easier, but here's some TypeScript draft of a tool for calculating analytically derivative of an expression:

interface Expr{
    /** Derivative of this expression over x. */
    ddx(): Expr;
}

class Const implements Expr{
    constructor(private value:number) {}
    ddx() {return new Const(0);}
}

const X: Expr={
    ddx:()=>new Const(1),
}

/** Subclass for expressions like "-arg", "sin arg" or "e^arg" that have only one argument. */
abstract class SingleArgExpr implements Expr{
    constructor(protected arg: Expr) { }
    ddx() {
        return new MultExpr(this.ddarg(), this.arg.ddx());
    }
    /** Derivative of this expression over the arg. */
    protected abstract ddarg(): Expr;
}

class UnaryMinus extends SingleArgExpr{
    ddarg() {return new Const(-1);}
}

/** Subclass for expressions like "arg1+arg2" or "arg1^arg2" with two arguments. */
abstract class TwoArgExpr implements Expr{
    constructor(protected arg1: Expr, protected arg2: Expr) { }
    ddx() {
        return new AddExpr(
            new MultExpr(this.ddarg1(), this.arg1.ddx()),
            new MultExpr(this.ddarg2(),this.arg2.ddx()),
        )
    }
    /** Derivative of this expression over arg1 */
    protected abstract ddarg1(): Expr;
    /** Derivative of this expression over arg2 */
    protected abstract ddarg2(): Expr;
}

class AddExpr extends TwoArgExpr{
    ddarg1() { return new Const(1); }
    ddarg2() { return new Const(1);}
}
class MultExpr extends TwoArgExpr{
    ddarg1() { return this.arg2; }
    ddarg2() { return this.arg1;}
}

So in general Expr has just a method to return an expression representing its derivative over X. Implementation for constants and for x itself is trivial, and then there are one-argument expressions like logarithms, trigonometric functions and stuff, and two-argument expressions like addition, power etc. (Let's pretend this distinction cannot be removed, even though it could in fact be generalised to n-argument expressions. Let's say it cannot.) And then there's the piece of code that implements ddx for TwoArgExpr that I want reused in all the two-arg expressions. With object oriented languages that have classes I just implement this in an abstract class which is between Expr and AddExpr. With Rust, I don't know how to do that.

In Rust, I'm starting like this:

trait Expr{
    /** Derivative of this expression over x. */
    fn ddx(&self)-> Box<dyn Expr>;
}

struct Const(f64);
impl Expr for Const{
    fn ddx(&self)->Box<dyn Expr> {Box::new(Const(0.0))}
}

struct X;
impl Expr for X{
    fn ddx(&self)->Box<dyn Expr>{Box::new(Const(1.0))}
}

And now I don't know where to put the implementation of e.g. ddx for two-argument expressions, and how to express the requirement for all two-argument expressions to know their derivatives over their arguments. Could you please help me and propose some solution?

(By the way, this is not a homework or anything like that :slight_smile: just an example I'm trying to explore. I just want my brain to understand some Rust.)

You could go for the messy composition approach, but it seems cumbersome

you'll probably need a DataFoo struct, an impl Foo for DataFoo
and DataBar would need an impl: Foo field to carry a DataFoo or something along those lines.

Honestly I'd avoid all of these and try to implement Foo and FooFromBar separately for DataBar

For something like this, where all of the options are provided together, I’d reach for an enum (untested):

enum Expr {
    Const(f64),
    X,
    Add(Box<Expr>, Box<Expr>),
    // ...
}

impl Expr {
    pub fn ddx(&self)->Expr {
        use Self::*;
        match self {
            Const(_) => Const(0.0),
            X => Const(1.0),
            Add(a,b) => a.ddx() + b.ddx(),
            // ...
        }
    }
}

impl std::ops::Add for Expr {
    type Output = Self;
    fn add(self, rhs:Self)=>Self {
        Self::Add(Box::new(self), Box::new(rhs))
    }
}

This is the direction I would start looking in as well. I ported a Java project to Rust recently and while many parts were straightforward, parts involving more complicated inheritance required looking at the problem the original code was built to solve and "solving it again" in Rust, rather than reimplementing the same solution in Rust.

1 Like

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.