Beginner question : how to pass different structs into a function

I want to pass in a variable into a function that can be either two types. I thought by creating an enum. It would work. But not sure what to do next.

struct A {
    id: String,
    units: i32,
}

struct B {
    id: String,
    max: i32,
}

enum MyContainer {
    A,
    B,
}

pub fn check_id(obj: MyContainer) {
   
    match obj{
        A=>{},
        B=>{},
    }
}

fn main(){
      let a = A{
             id:"ok".to_String(),
             units:23;
      } 

      check_id(a);//error
}

Your enum doesn't represent what you think it does, as you've written it. Currently, it's just two variants that each contain no data. You want something like this:

enum MyContainer {
    NameOfVariantContainingA(A),
    NameOfVariantContainingB(B),
}

And then you can match with:

match obj {
    MyContainer::NameOfVariantContainingA(a) => println!("{:?}", a),
    MyContainer::NameOfVariantContainingB(b) => println!("{:?}", b),
}

Come up with your own names though, you can even call the variants the same as the types they contain if you want. I only changed it to show that the name is independent.

OK thank you.

In my program, I have to pass in two different but related structs into a function.

I guess the only way to do it , is with the Enum solution that you provided.

You could also use traits and generics.

Oh, I was looking into that...But I got a little lost. Could you please make a small example of what you mean.

This is s far as I got, because in the function check_id(), the compiler doesn't recognize any fields of the struct passed in.


trait myPersonalTrait{}

impl myPersonalTrait for A{};

impl myPersonalTrait for B{};



pub fn check_id<T>(obj: T)
 where T: myPersonalTrait
{
   
    obj.id // error
    
}

When using traits and generics, you can only access things that are defined on the trait. If you want to access fields, you must provide a method on the trait for doing so.

Ok, from what I understood and tried. I couldn't access self.id on the trait if the method was defined in the trait definition itself.

But I can implement the trait as shown below and It does work! But, It means that I need to reimplement the same code for each type.

struct A {
    id: String,
    units: i32,
}

struct B {
    id: String,
    max: i32,
}

trait myPersonalTrait{

    fn check_id(&self);
}


impl myPersonalTrait for A{
    
     fn check_id(&self){
        println!("{:#?}",  self.id);
    }
    
}

impl myPersonalTrait for B{
    
    fn check_id(&self){
        println!("{:#?}",  self.id);
       
    }
    
}

fn main(){
      let a = A{
             id:"ok".to_string(),
             units:23,
      } ;
      a.check_id();
}

But why do you have two distinct types with identical layout in the first place? That, not Rust's trait system, is what seems to be the source of the code smell.

Oh! My real program has more fields for each struct.
I was trying to figure out how to pass the two types into a common function without using an enum. As such, was suggested using generics and traits.
Definitely not the best example..

Others can probably offer better advice than me, but maybe this context will be helpful.

Coming from an object-oriented background, it's easiest to think of traits like interfaces in Java / C# / etc. They expose behaviour (functions), but never the data members. It's just a defined way to interact with the object.

Traits are are great if your method needs to handle any number of different objects in the future, and you don't know what they all are right now.

Enums are great if you do know exactly what variants you want the function to handle, and the compiler will help you enforce this. And you do get to use the data inside them if you want to. These special enums are one of Rust's super powers, and you'll see them in plenty of functional languages. There is no equivalent in Java or C#.

Then you could factor the common fields into a single struct, and have the enum variants contain only the fields that differ across the types.

1 Like

Thank you, your explanation is pretty clear.

The only thing that I can't quite catch is :

I can't define a trait with a function like this: This seems impossible to do.

 trait MyPersonalTrait{
  fn incrementID(&self){
       self.id +=1; // error here, since concrete type is
                    // not known yet which has `id` field.
   }
}

The only way to get around this, is by implementing the trait for each struct.

You can also add a getter like fn id(&self) -> u32; to the trait and use that in other trait methods, or in some cases a constant like const ID: u32; might work, though probably not here.

But yes, there's no way to directly access fields of the implementing struct in default implementations. Making it possible to have fields in traits has been suggested before (see for example fields-in-traits-rfc/0000-fields-in-traits.md at master · nikomatsakis/fields-in-traits-rfc · GitHub) but I don't think that's something that's being actively worked on right now.

2 Likes

That makes sense -- I wasn't super clear.

When I'm talking about the method handling different objects in the future, I'm talking about a method that takes a MyPersonalTrait as input. Hopefully this makes sense -- it's the one called use_trait in this example:

trait MyPersonalTrait{
    fn increment_id(&mut self);
}

struct Thing1 {
    id: i32
}

struct Thing2 {
    some_other_id: i32
}

// this has to be implemented for each struct that
// implements the trait
impl MyPersonalTrait for Thing1 {
    fn increment_id(&mut self) {
        self.id += 1;
    }
}

impl MyPersonalTrait for Thing2 {
    fn increment_id(&mut self) {
        self.some_other_id += 1;
    }
}

// method using the trait - defined once, and will accept any MyPersonalTrait
fn use_trait<T: MyPersonalTrait>(thing: &mut T) {
    thing.increment_id()
}

fn main() {
    let mut t = Thing1 {
        id: 12
    };
    
    let mut t2 = Thing2 {
        some_other_id: 33
    };
    
    use_trait(&mut t);
    println!("{}", t.id);
    
    use_trait(&mut t2);
    println!("{}", t2.some_other_id);
}
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.