Using subtraits in functions

Hello,

I want to ask what is the most idiomatic solution for solving compilation errors of following code:

use std::sync::{Arc, Mutex};
struct Bulldog;
struct Ragdoll;

trait Walk {}
trait Bark {}
trait Meow {}

trait Dog: Walk + Bark {}
trait Cat: Walk + Meow {}

impl Walk for Bulldog {}
impl Bark for Bulldog {}
impl Dog for Bulldog {}

impl Walk for Ragdoll {}
impl Meow for Ragdoll {}
impl Cat for Ragdoll {}

fn walk_ref(item: &dyn Walk) {}
fn walk_arc(item: Arc<dyn Walk>) {}
fn walk_locked(item: Arc<Mutex<dyn Walk>>) {}

fn main() {
    let dog: &dyn Dog = &Bulldog;
    let cat: &dyn Cat = &Ragdoll;
    walk_ref(dog);
    walk_ref(cat);

    let dog: Arc<dyn Dog> = Arc::new(Bulldog);
    let cat: Arc<dyn Cat> = Arc::new(Ragdoll);
    walk_arc(dog);
    walk_arc(cat);

    let dog: Arc<Mutex<dyn Dog>> = Arc::new(Mutex::new(Bulldog));
    let cat: Arc<Mutex<dyn Cat>> = Arc::new(Mutex::new(Ragdoll));
    walk_locked(cat);
    walk_locked(dog);
}
error[E0308]: mismatched types
  --> src/main.rs:27:14
   |
27 |     walk_ref(dog);
   |              ^^^ expected trait `Walk`, found trait `Dog`
   |
   = note: expected type `&dyn Walk`
              found type `&dyn Dog`
...

To the best of my knowledge, you can't do this, as the representations of the VTables don't allow for it.

I'd really recommend against trying to use traits to model inheritence relationships if at all possible. trait A: B doesn't mean that A inherits from B, it means that any type that implements A must also implement B.

2 Likes

This conversion can't be done. When Rust casts something to dyn it erases all other type information. Traits aren't real inheritance, using them to emulate OOP patterns may fail like that.

You will have to make the Dog trait have as_walk() method that casts its concrete type.

1 Like

Is that right? Doesn't trait A : B mean that if C provides and implementation of A it must also implement B

1 Like

Whoops, yeah - will edit my reply.

Hi, thanks for the reply. I'm just trying to figure out whether it is possible to do this in some kind of nice way.

Thanks for the reply. I guess I know how to implement as_walks() for &dyn Dog/Cat.

However I'm not sure how to implement Arc<dyn Dog/Cat> to Arc<dyn Walk> and Arc<Mutex<dyn Dog/Cat>> to Arc<Mutex<dyn Walk>> conversion.

I think the suggestion to have the Dog trait provide a method, as_walk/as_bark would solve the issue fairly well.

Well, not entirely. Now I have:

use std::sync::{Arc, Mutex};
struct Bulldog;
struct Ragdoll;

trait Walk {
    fn as_walk(&self) -> &dyn Walk;
}
trait Bark {}
trait Meow {}

trait Dog: Walk + Bark {}
trait Cat: Walk + Meow {}

impl Walk for Bulldog {
    fn as_walk(&self) -> &dyn Walk {
        self
    }
}
impl Bark for Bulldog {}
impl Dog for Bulldog {}

impl Walk for Ragdoll {
    fn as_walk(&self) -> &dyn Walk {
        self
    }
}
impl Meow for Ragdoll {}
impl Cat for Ragdoll {}

fn walk_ref(item: &dyn Walk) {}
fn walk_arc(item: Arc<dyn Walk>) {}
fn walk_locked(item: Arc<Mutex<dyn Walk>>) {}

fn main() {
    let dog: &dyn Dog = &Bulldog;
    let cat: &dyn Cat = &Ragdoll;
    walk_ref(dog.as_walk());
    walk_ref(cat.as_walk());

    let dog: Arc<dyn Dog> = Arc::new(Bulldog);
    let cat: Arc<dyn Cat> = Arc::new(Ragdoll);
    walk_arc(dog);
    walk_arc(cat);

    let dog: Arc<Mutex<dyn Dog>> = Arc::new(Mutex::new(Bulldog));
    let cat: Arc<Mutex<dyn Cat>> = Arc::new(Mutex::new(Ragdoll));
    walk_locked(cat);
    walk_locked(dog);
}

First conversion walk_ref is fine, but I'm not sure how to deal with walk_arc and walk_locked.

as_walk should be on the Dog trait

Sorry, I'm starting to be a bit confused here. Let me to rephrase the question.
Assume, that I have a trait definions like this.

trait A {}
trait B: A {}

struct X;
impl A for X {};
impl B for X {};

And I have a vector.

let my_vec = Vec<Arc<Mutex<dyn B>> = vec![X, X, X, X];

And I have a function.

fn do_something(a: Arc<Mutex<dyn A>>) {}

Which I'd like to perform on every element of the vector. And I don't know how to do that or whether it is possible.

This is not something that you can do

1 Like

I'm going to take a bit of a leap and say to avoid dynamic traits unless necessary.

Here's a few things you can say in C#, but in more idiomatic rust:

public static void Foo(MyInterface obj) {/**/}
// Naively
pub fn foo(obj: &dyn MyTrait) {/**/}
// Idiomatically
pub fn foo<T: ?Sized>(obj: &T) {/**/}

interface Bar: A, B, C {}

public static void Foo(Bar obj) {
    Baz(obj);
}
public static void Baz(A obj) {/**/}
pub trait Foo: A + B + C {}

pub fn foo<T: Foo>(obj: &T) { 
    baz(obj);
}
pub fn baz<T: A>(obj: &T) {/**/}

public class Bar {
    MyInterface Foo;
    
    public Bar() {/**/}
}
// Naively
pub struct Bar {
    foo: Box<dyn MyTrait>,
}
impl Bar {
   pub fn new() -> Self {/**/}
}
// Idiomatically
pub struct Bar<T: MyTrait + ?Sized> {
    foo: T
}
impl<T: MyTrait> Bar<T> {
    pub fn new() -> Self {/**/}
}
impl<T: MyTrait + ?Sized> Bar<T> {/**/}

interface Bar<T> {
    virtual T BarVal { get; set; }
}
trait Bar<T> {
    pub fn get_val(&self) -> &T;
    pub fn get_val_mut(&mut self) -> &mut T;
    pub fn set_val(&mut self, val: T);
}

Here are some scenarios where dynamics are needed:

var myList = new List<MyInterface>();
// Unless you can use an enum!
let my_list = Vec::<Box<dyn MyTrait>>::new();

aaand... I actually fail to find other examples where you'd need to most definitely use dynamics due to language constraints, disregarding ugly cases.

3 Likes

One example I see often is where you want to return two different objects which implement the same trait. For example, if you want to accept input from either a file or STDIN you need runtime dispatch:

struct Config {
  input_file: Option<PathBuf>,
}

impl Config {
  fn input(&self) -> Result<Box<dyn Read>, io::Error> {
    match self.input_file {
      Some(ref path) => {
        let f = File:open(path)?;
        Ok(Box::new(f))
      }
      None => Ok(Box::new(io::stdin())),
    }
  }
}

Well... you don't need it, but using something like Either or a custom enum doesn't scale very well.

2 Likes

dyn B is a type that implements A, so making do_something generic (applying @OptimisticPeach's post to your example)

fn do_something<T: ?Sized + A>(_: Arc<Mutex<T>>) {}

is sufficient to make it compile. However, I concur with the other voices here suggesting this might not be the best way to go about things.

Thank you all for you time and suggestions. Right now I don't see any nice way how to solve this problem.

So I guess I need to:

  • re-think the entire design
  • use some kind of enum wrappers
  • generate do_something functions via generics (can't be used if do_something is inside a trait which is used with dynamic dispatch)
  • generate do_something functions via macros (to keep DRY principle)

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.