Kind of polymorphism

Hello world. I'm trying to save objects of different types in a vector. Here is my code:

struct Obj1 {}

struct Obj2 {}

struct Obj3 {}

enum ObjKind {
    Obj1,
    Obj2,
    Obj3
}

struct Obj {
    kind: ObjKind
    o1: Option<Obj1>
    o2: Option<Obj2>
    o3: Option<Obj3>
}

impl Obj {
    fn new(o:  Obj1) -> Obj {
        {
            kind: Obj1
            o1: Some(o),
            o2: None,
            o3: None
        }
    }

    fn new(o:  Obj2) -> Obj {
        {
            kind: Obj2
            o1: None,
            o2: Some(o),
            o3: None
        }
    }

    fn new(o:  Obj3) -> Obj {
        {
            kind: Obj3
            o1: None,
            o2: None,
            o3: Some(o)
        }
    }
}

fn main() {
    let obj_list = Vec::<Obj>::new()
}

The problem is that the struct Obj always contains unused fields. Can you suggest a better design ?

Use a data-carrying enum instead of splitting out the discriminant (ObjKind) manually.

struct Obj1 {}
struct Obj2 {}
struct Obj3 {}

enum Obj {
    Obj1(Obj1),
    Obj2(Obj2),
    Obj3(Obj3),
}

fn main() {
    let obj_list = vec![
        Obj::Obj1(Obj1 {}),
        Obj::Obj2(Obj2 {}),
        Obj::Obj3(Obj3 {}),
    ];
}

Incidentally, Rust doesn't have argument-type based overloading (as attempted with new).

5 Likes

Pretty neat ! Thank you.

Oh yes ! My bad :sweat_smile::sweat_smile:

Two more notes. First: If you still want Obj to have some abstraction boundary (non-public fields), then you’d need to wrap that enum into another struct, something like

struct Obj1 {}
struct Obj2 {}
struct Obj3 {}

enum ObjData {
    Obj1(Obj1),
    Obj2(Obj2),
    Obj3(Obj3),
}

struct Obj {
    data: ObjData,
}

Second: While direct overloading isn’t supported, you can still achieve it by defining a trait. For this case, just using an enum with fields, as @quinedot suggested, is of course the superior design, but even your original code could be made to work e.g. as follows

struct Obj1 {}

struct Obj2 {}

struct Obj3 {}

enum ObjKind {
    Obj1,
    Obj2,
    Obj3,
}

struct Obj {
    kind: ObjKind,
    o1: Option<Obj1>,
    o2: Option<Obj2>,
    o3: Option<Obj3>,
}

trait ObjNewArgument {
    fn obj_new(o: Self) -> Obj;
}

impl ObjNewArgument for Obj1 {
    fn obj_new(o: Self) -> Obj {
        Obj {
            kind: ObjKind::Obj1,
            o1: Some(o),
            o2: None,
            o3: None,
        }
    }
}

impl ObjNewArgument for Obj2 {
    fn obj_new(o: Self) -> Obj {
        Obj {
            kind: ObjKind::Obj2,
            o1: None,
            o2: Some(o),
            o3: None,
        }
    }
}

impl ObjNewArgument for Obj3 {
    fn obj_new(o: Self) -> Obj {
        Obj {
            kind: ObjKind::Obj3,
            o1: None,
            o2: None,
            o3: Some(o),
        }
    }
}

impl Obj {
    fn new<T: ObjNewArgument>(o: T) -> Obj {
        T::obj_new(o)
    }
}

If you wanted to combine the two things, i.e. a Obj type using the good enum approach but with private fields, and a new function supporting all types, that works, too, of course:

mod obj_module {
    pub struct Obj1 {}
    pub struct Obj2 {}
    pub struct Obj3 {}

    enum ObjData {
        Obj1(Obj1),
        Obj2(Obj2),
        Obj3(Obj3),
    }

    pub struct Obj {
        data: ObjData,
    }

    pub trait ObjNewArgument {
        fn obj_new(o: Self) -> Obj;
    }

    impl ObjNewArgument for Obj1 {
        fn obj_new(o: Self) -> Obj {
            Obj {
                data: ObjData::Obj1(o),
            }
        }
    }

    impl ObjNewArgument for Obj2 {
        fn obj_new(o: Self) -> Obj {
            Obj {
                data: ObjData::Obj2(o),
            }
        }
    }

    impl ObjNewArgument for Obj3 {
        fn obj_new(o: Self) -> Obj {
            Obj {
                data: ObjData::Obj3(o),
            }
        }
    }

    impl Obj {
        pub fn new<T: ObjNewArgument>(o: T) -> Obj {
            T::obj_new(o)
        }
    }
}

fn main() {
    use obj_module::{Obj, Obj1, Obj2, Obj3};

    let obj_list = vec![
        Obj::new(Obj1 {}),
        Obj::new(Obj2 {}),
        Obj::new(Obj3 {}),
    ];
}

Though realistically, if only the private fields are wanted, in idiomatic Rust you’d probably just encounter multiple new_… methods:

mod obj_module {
    pub struct Obj1 {}
    pub struct Obj2 {}
    pub struct Obj3 {}

    enum ObjData {
        Obj1(Obj1),
        Obj2(Obj2),
        Obj3(Obj3),
    }

    pub struct Obj {
        data: ObjData,
    }

    impl Obj {
        pub fn new_obj1(o: Obj1) -> Obj {
            Obj {
                data: ObjData::Obj1(o),
            }
        }

        pub fn new_obj2(o: Obj2) -> Obj {
            Obj {
                data: ObjData::Obj2(o),
            }
        }

        pub fn new_obj3(o: Obj3) -> Obj {
            Obj {
                data: ObjData::Obj3(o),
            }
        }
    }
}

fn main() {
    use obj_module::{Obj, Obj1, Obj2, Obj3};

    let obj_list = vec![
        Obj::new_obj1(Obj1 {}),
        Obj::new_obj2(Obj2 {}),
        Obj::new_obj3(Obj3 {}),
    ];
}
1 Like

Wow. Great! Thank you very much.

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.