Call Trait function's without the type that implement the trait

#1

Hi,

I am using a lib and a runtime parameter select the type of instance to create. I encapsulate all supported implementation in enum and to call functions I do a match.

How can improve my code by not using a match (as all instances implement the trait) ?

I try to illustrate the case with the following playground.

Thanks for your advice

#![allow(unused)]

use std::fmt;
use std::any::Any;


#[derive(Debug, Clone)]
struct XStruct{
    a: i32,
    b: String,
}

impl XStruct {
    fn new() -> XStruct {
        XStruct{
            a : -1,
            b : String::from("XStruct"),
        }
    }
}

#[derive(Debug, Clone)]
struct YStruct{
    a: u64,
    b: u8,
}

impl YStruct {
    fn new() -> YStruct {
        YStruct{
            a : 2,
            b : 255,
        }
    }
}
#[derive(Debug, Clone)]
enum Container {
    X(XStruct),
    Y(YStruct),
}

trait Concatenate {
    fn foo(&mut self) -> String {
        unimplemented!()
    }
    fn bar(&mut self) -> String {
        unimplemented!()
    }
    fn fuu(&mut self) -> String {
        unimplemented!()
    }
}

impl Concatenate for YStruct {
    fn foo(&mut self) -> String {
        format!("foo:{}{}",self.a,self.b)
    }
    fn bar(&mut self) -> String {
        format!("bar:{}{}",self.a,self.b)
    }
    fn fuu(&mut self) -> String {
        format!("fuu:{}{}",self.a,self.b)
    }
}

impl Concatenate for XStruct {
    fn foo(&mut self) -> String {
        format!("foo:{}{}",self.a,self.b)
    }
    fn bar(&mut self) -> String {
        format!("bar:{}{}",self.a,self.b)
    }
    fn fuu(&mut self) -> String {
        format!("fuu:{}{}",self.a,self.b)
    }
}

fn main() {
    
    // Build an instance
    let container = build(false);
    println!("container {:?}",container);
    
    match container {
        Container::X(mut x) => {
                println!("{}",x.foo());
                println!("{}",x.bar());
                println!("{}",x.fuu());
            },
        Container::Y(mut y) => {
                println!("{}",y.foo());
                println!("{}",y.bar());
                println!("{}",y.fuu());
            }
    }
    
    // Question ? As I am calling Trait function can I do it without match ?
    /*
    println!("{}",container.foo());
    println!("{}",container.bar());
    println!("{}",container.fuu());
    */
    
}

fn build(selector : bool) -> Container {
    if selector {
        Container::X(XStruct::new())
    } else {
        Container::Y(YStruct::new())
    }
}




(Playground)

Output:

container Y(YStruct { a: 2, b: 255 })
foo:2255
bar:2255
fuu:2255

Errors:

   Compiling playground v0.0.1 (/playground)
    Finished dev [unoptimized + debuginfo] target(s) in 1.91s
     Running `target/debug/playground`

#2

You’ll need to implement a function on Container that gives you a trait object. This function could be Deref::deref, which will get called automatically when you use the method call operator (.).

If you change your trait as follows:

trait Concatenate: 'static { ... }

You can do this:

use std::ops::{Deref, DerefMut};

impl Deref for Container {
    type Target = dyn Concatenate;
    fn deref(&self) -> &dyn Concatenate {
        match self {
            Container::X(x) => x,
            Container::Y(y) => y,
        }
    }
}

impl DerefMut for Container {
    fn deref_mut(&mut self) -> &mut dyn Concatenate {
        match self {
            Container::X(x) => x,
            Container::Y(y) => y,
        }
    }
}
2 Likes
#3

Thanks for your answer it works fine !
I just try it in playground. Note a small typo of the answer is corrected in playground.

https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=f911aeb93be3aa4af00327a9f1608a5d

#4

You didn’t change the trait as I suggested. This way is fine too.

#5

Hi jethrogb;
No, I did the change, but on the same playground as the initial post and it was overwritten. I do it again using a new playground and change the link in my answer.
Thanks for your answer