Help trying to understand generics/traits in Rust

I'm still very new to Rust and have been trying small exercises to see how they differ from other languages I'm more familiar with and just get more understanding of the language.

I've been reading the ebook and doing my own version of the learn by example and got up to the Generics and Traits which has got me quite confused.

In C# if I start with object of type A and want to project it out into type B and type C, I'd make a generic interface with the required method and then a class to inherit from the interface and then implement the method in the class to accept A as a parameter and return B, but in Rust I've been having some issues getting my head around it.

I followed the same approach trying to translate what I understand from docs and examples and my code works but not 100% sure if this is the most correct solution?

Any tips or advice would be appreciated

fn main() {
    println!("Hello, world!");

    let test: C = C { prop_A: String::from("8"), prop_B: String::from("Test description") };

    let a: A = C::convert(&test);
    let b: B = C::convert(&test);

    println!("{a:#?}");
    println!("{b:#?}");
}

#[derive(Debug)]
pub struct A {
    field_A: String,
    field_B: String
}

#[derive(Debug)]
pub struct B {
    prop_A: u8,
    prop_B: String
}

pub struct C {
    prop_A: String,
    prop_B: String
}

trait Convert<T> {
    fn convert(c: &C) -> T;
}

impl Convert<A> for C {
    fn convert(c: &C) -> A {
       A { field_A: c.prop_A.clone(), field_B: c.prop_B.clone() }
    }
}

impl Convert<B> for C {
    fn convert(c: &C) -> B {
        B { prop_A: c.prop_A.clone().parse().unwrap(), prop_B: c.prop_B.clone() }
    }
}

You might want to look at the From and Into traits from the standard library. They should be able to do everything your Convert trait is doing. You will notice a couple of differences.

  1. They take ownership of the value. This allows the caller of the trait method to decide if they need to clone or can pass ownership of the object.
  2. They take self instead of a specific type. This allows them to be called by writing a.into(), which is much nicer than C::convert(&test) This also means that they can be used for more than just C objects.

If you would do the second difference to you trait it could look like this:

trait Convert<T> {
    fn convert(&self) -> T;
}

impl Convert<A> for C {
    fn convert(&self) -> A {
       A { field_A: self.prop_A.clone(), field_B: self.prop_B.clone() }
    }
}

impl Convert<B> for C {
    fn convert(&self) -> B {
        B { prop_A: self.prop_A.clone().parse().unwrap(), prop_B: self.prop_B.clone() }
    }
}

and you would call it like this:

let a: A = test.convert();
let b: B = C::convert(&test); // still works
1 Like

I had overlooked From/Into entirely even though I have used them both on numerous occasions so far in my Rust journey. My approach was definitely more convoluted but I feel like I understand a little better now after updating my code and playing around with those traits and my convert trait after your suggestions so thankyou for your time

If you need to do a projection that doesn't create a new value, but only provides a view into an existing value, then take a look at the AsRef trait.

1 Like