Tyrone Tudehope
blog.tyrone.dev

Is it Rusty? Reusing struct fields

  • 10/07/2019 - Published
  • 18/07/2019 - Fix typo

Is it Rusty? These are posts chronicling my journey learning Rust. At this point I’m uncertain whether what I’m doing is actually the recommended way of doing things.

Given two structs with common fields, I was wondering how I could go about creating a “base” struct so that those fields could be shared. For example, if we had the following two structs, how would we share the name field?

Rats and dogs
struct MoleRat {
    name: String,
    variant: String,
}

struct Dog {
    name: String,
    breed: String,
}

In OOP I’d create a base class which contains that property. However, as far as I can tell, in Rust, there is no notion of inheritance of fields. A solution is to have a third struct which defines the common fields, and an extra field which holds the value of the “child” struct.

Adding a "base" struct
struct Pet<T> {    name: String,    animal: T,}
struct MoleRat {
    variant: String,
}

struct Dog {
    breed: String,
}

struct Car {
    make: String,
}

These can be used like this:

fn main() {
    let mole_rat = Pet {
        name: "Oscar".into(),
        animal: MoleRat {
            variant: "Glowing mole-rat".into(),
        },
    };
    println!("My mole-rat: {:?}", mole_rat);
    // My mole-rat: Pet { name: "Oscar", animal: MoleRat { variant: "Glowing mole-rat" } }

    let dog = Pet {
        name: "Odin".into(),
        animal: Dog {
            breed: "Collie-x".into()
        },
    };
    println!("My dog: {:?}", dog);
    // My dog: Pet { name: "Odin", animal: Dog { breed: "Collie-x" } }

    let car = Pet {
        name: "Mini-truck".into(),
        animal: Car {
            make: "Opel".into(),
        }
    };
    println!("My car: {:?}", car);
    // My car: Pet { name: "Mini-truck", animal: Car { make: "Opel" } }
}

Cool. But we can add anything we like into Pet::animal, including a Car. To solve this you can implement a trait to restrict the types that can be set on animal.

Using a trait
trait Animal {}
#[derive(Debug)]
struct Pet<T: Animal> {    name: String,
    animal: T,
}

#[derive(Debug)]
struct MoleRat {
    variant: String,
}

impl Animal for MoleRat {}
#[derive(Debug)]
struct Dog {
    breed: String,
}

impl Animal for Dog {}
#[derive(Debug)]
struct Car {
    make: String,
}

Now by restricting what can be used in the animal field, the previous fn main { code will no longer work:

Cannot add a car
fn main() {
    let mole_rat = Pet {
        name: "Oscar".into(),
        animal: MoleRat {
            variant: "Glowing mole-rat".into(),
        },
    };
    println!("My mole-rat: {:?}", mole_rat);

    let dog = Pet {
        name: "Odin".into(),
        animal: Dog {
            breed: "Collie-x".into()
        },
    };
    println!("My dog: {:?}", dog);

    // let car = Pet {
    //     name: "Mini-truck".into(),
    //     animal: Car {
    //         make: "Opel".into(),
    //     }
    // };
    // let car = Pet {
    //           ^^^ the trait `Animal` is not implemented for `Car`
}

Try it out in the playground.

Leave a comment