-2

I want to take the behaviors of two traits and aggregate them together into some new thing which can be used to constrain other structures, enums, etc.

Here is an example: Combine the behavior of Any with some other trait. The intended purpose is to combine the downcast and is behaviors of Any with some other concept, in this case a function which returns the name of an object.

use std::any::Any;

trait Named {
    
    fn get_name(&self) -> String;
}

// Combine the behaviors of `Any` with the trait `Named`
trait DynamicDispatchTrait : Any + Send + Sync + 'static + Named {
    
}

struct MyType {
    
    my_name: String,
}

How can I mark the structure MyType as "something which behaves as trait DynamicDispatchTrait?

My first thought was that MyType should implement this trait. That doesn't seem to work:

impl DynamicDispatchTrait for MyType {
    
    fn get_name(&self) -> String {
        return format!("my_name_is:{}", self.my_name);
    }
}

Here's why:

method `get_name` is not a member of trait `DynamicDispatchTrait`

The ultimate objective is to be able to write a function like this:

fn dynamic_function(dynamic: Box<dyn DynamicDispatchTrait>) -> String {
    
    if let Some(thing) = dynamic.downcast_ref::<MyType>() {
        // ok
    }
    else {
        println!("error!");
        return String::from("error");
    }
    
    // ok ... operate dynamically
    let s = dynamic.get_name();
    return s;
}

Attempting to implement each trait independently reduces the number of compiler errors:

struct MyType {
    
    my_name: String,
}

// Implement trait Named separately
impl Named for MyType {
    
    fn get_name(&self) -> String {
        return format!("my_name_is:{}", self.my_name);
    }
}

// Implement trait DynamicDispatchTrait separately
impl DynamicDispatchTrait for MyType {  }

// Any not implemented?

This doesn't quite solve all the issues. The compiler now says:

no method named `downcast_ref` found for struct
`Box<(dyn DynamicDispatchTrait + 'static)>` in the current scope

I would have thought that occurs because there is no impl Any for MyType.

Attempting to add that doesn't quite work.

impl Any for MyType {  }
conflicting implementations of trait `Any` for type `MyType`

...

note: conflicting implementation in crate `core`:
 - impl<T> Any for T
 where T: 'static, T: ?Sized;

Is there a solution to the problem I am attempting to solve?


Trait Aggregation / Requirements Example:

This looks to behave in a way which is analogous to inheritance.

The point here being that MyTraitCombo, when used in the context of a "trait object" has a vtable which permits dispatch of both the functions from the two traits it "inherits" from. ("Inherits" = "satisfies the requirements" / "trait bounds" of.)

trait MyTrait1 {
    
    fn my_function_1(&self) -> String {
        
        String::from("function_1")
    }
}

trait MyTrait2 {

    fn my_function_2(&self) -> String {
        
        String::from("function-2")
    }
}

trait MyTraitCombo : MyTrait1 + MyTrait2 {
    

}

// note works without &
fn my_dynamic(argument: Box<&dyn MyTraitCombo>) -> String {
    
    let s1 = argument.my_function_1();
    let s2 = argument.my_function_2();
    
    format!("{}-{}", s1, s2)   
}

impl MyTraitCombo for i64 {
    
}

impl MyTrait1 for i64 {  }

impl MyTrait2 for i64 {  }

fn main() {
    
    let arg = 0i64;
    let s = my_dynamic(Box::new(&arg)); // note works without &
    println!("{}", s);
}
FreelanceConsultant
  • 13,167
  • 27
  • 115
  • 225
  • 1
    It feels like you are trying to force OOP patterns onto a non-OOP language. I'm afraid you will get frustrated pretty quickly with this approach. – Finomnis Jul 30 '23 at 13:04
  • 1
    *"demonstrates that Rust does support trait inheritance"* - This is not inheritance, nothing gets inherited. It is more like trait requirements. You cannot simply implement `MyTraitCombo` for something that doesn't implement `MyTrait1`, like inheritance would. It's more like trait dependency than inheritance. – Finomnis Jul 30 '23 at 13:05
  • *"because there is no `impl Any` for `MyType`"* - `Any` is [automatically implemented for all types that are `'static`](https://doc.rust-lang.org/std/any/trait.Any.html#impl-Any-for-T). – Finomnis Jul 30 '23 at 13:08
  • @Finomnis From your last comment, that suggests a requirement that the traits discussed above all be `'static` ? – FreelanceConsultant Jul 30 '23 at 13:13
  • @Finomnis I have re-worded the subsection title to make it clearer – FreelanceConsultant Jul 30 '23 at 13:15
  • I'd rather say the objects that implement the trait have to be `'static`, meaning, they do not contain references to things ouside of themselves. That's because lifetimes can't be downcast into, there's no way to abstract lifetimes away. Lifetimes have to stay explicit for the compiler to understand them. – Finomnis Jul 30 '23 at 13:18
  • @Finomnis yes, that makes sense. Does that require any additional explicit constraints, or is it enough for the objects implementing the traits to not contain any references? (Or be references themselves.) – FreelanceConsultant Jul 30 '23 at 13:26
  • In general, every struct is `'static` that doesn't contain lifetime annotations (like `struct MyStruct<'a>`). – Finomnis Jul 30 '23 at 13:41
  • @Finomnis Ok that's fine, it is as I thought in that case – FreelanceConsultant Jul 30 '23 at 13:46

1 Answers1

1

In your whole post you assume traits and supertraits work like inheritance in other languages would work, they don't Rust doesn't have inheritance.

You can't use downcast or downcast_ref because they're not methods of Any but inherent methods of Box<dyn Any + 'static> and dyn Any + 'static respectively, but dyn DynamicDispatchTrait is a different type from dyn Any + 'static.

Currently I think the only way to achieve what you want is to take Box<dyn Any + 'static> and cast it to the specific type you have:

fn dynamic_function(dynamic: Box<dyn Any + 'static>) -> String {
    let dynamic_named: &dyn Named = if let Some(thing) = dynamic.downcast_ref::<MyType>() {
        thing
    } else if let Some(thing) = dynamic.downcast_ref::<OtherTypeImplementingNamed>() {
        thing
    } else {
        println!("error!");
        return String::from("error");
    };
    
    let s = dynamic_named.get_name();
    return s;
}

Once upcasting is implemented you can use that to get a &dyn Any from a &dyn DynamicDispatchTrait, lets your code work like you want it to with a little tweak.

Any is automatically implemented for every applicable type so you can't and don't need to ever implement it.

cafce25
  • 15,907
  • 4
  • 25
  • 31
  • I feared it might be something like this. Can you explain, in the link you have posted related to `Box` where is the information? The link takes me to `pub fn downcast`, which is not the same function? – FreelanceConsultant Jul 30 '23 at 12:36
  • It *does* have the function `get_name`. We must be talking cross purposes here... – FreelanceConsultant Jul 30 '23 at 13:01
  • Perhaps more accurately: Trait objects derived from `DynamicDispatchTrait` have this function. That means that, somewhere, there is a vtable which contains `get_name`. – FreelanceConsultant Jul 30 '23 at 13:02
  • Since you can't create a `DynamicDispatchTrait` by itself, I don't see what practical difference that distinction makes. – FreelanceConsultant Jul 30 '23 at 13:03
  • Ahh, I see the point you are making. This is simply a difference as to what both of us are considering inheritance to be. Then I agree, it is more accurate to say "DynamicDispatchTrait trait objects" have this function, the trait itself, which cannot be created and is not really a meaningful thing in the concrete sense, does not permit this function to be defined within its scope. Traits are abstract... trait objects are concrete... – FreelanceConsultant Jul 30 '23 at 13:06
  • I don't think that's correct. The reason this doesn't work is more related to where `downcast` and `downcast_ref` are defined. I could be mistaken, but it seems the key point is that `downcast_ref`, for example, is defined as a templated (generic) function for `T: Any`. Which means it doesn't work for a trait `trait X: Any + ...`. I don't know, maybe you would define that as "inheritance" and from this conclude Rust does not support inheritance. – FreelanceConsultant Jul 30 '23 at 13:10
  • 1
    *Trait objects derived from `DynamicDispatchTrait` have this function*" - that is incorrect. Correct would be: *Trait objects can only implement `DynamicDispatchTrait` **if** they also implement `Named`. **Because** this is a requirement, all objects that implement `DynamicDispatchTrait` **also** have the function `get_name()`*. Note that those are two orthogonal things. – Finomnis Jul 30 '23 at 13:14