Matt Thomas
Matt Thomas

Reputation: 5774

How do I implement a trait for a struct when the trait requires more state than is contained in the struct?

How do I implement a trait for a struct when the trait requires more state than is contained in the struct? For example, how would I implement the Employee trait for the Human struct shown below?

struct Human {
    name: &str,
}

trait Employee {
    fn id(&self) -> i32;
    fn name(&self) -> &str;
}

impl Employee for Human {
    fn id(&self) -> i32 {
        // From where do I get the ID?
    }
    fn name(&self) -> &str {
        self.name
    }
}

I'm not seeing any way to tuck additional state into the impl or into the trait.

Is the only option to create a new HumanToEmployeeAdapter struct holding the missing info and then implement the Employee trait for the new struct?

P.S. My background is in C#. Here's how I would approach it in that language:

class Human
{
    public string Name { get; }

    public Human(string name) { Name = name; }
}

interface IEmployee
{
    int Id { get; }
    string Name { get; }
}

class HumanToEmployeeAdapter : IEmployee
{
    readonly Human _human;

    public int Id { get; }
    public string Name => _human.Name;

    public HumanToEmployeeAdapter(
        Human human,
        int id)
    {
        _human = human;
        Id = id;
    }
}

You'll notice that this is the "create a new HumanToEmployeeAdapter struct" path. So, is this the way Rustaceans solve this problem?

Upvotes: 0

Views: 254

Answers (1)

Peter Hall
Peter Hall

Reputation: 58805

You can translate your C# code almost exactly, something like this:

struct Human<'a> {
    name: &'a str,
}

trait Employee {
    fn id(&self) -> i32;
    fn name(&self) -> &str;
}

struct HumanToEmployeeAdapter<'a> {
    human: &'a Human<'a>,
    id: i32,
}

impl<'a> HumanToEmployeeAdapter<'a> {
    fn new(id: i32, human: &'a Human<'a>) -> Self {
        HumanToEmployeeAdapter { id, human }
    }
}

impl<'a> Employee for HumanToEmployeeAdapter<'a> {
    fn id(&self) -> i32 {
        self.id
    }

    fn name(&self) -> &str {
        self.human.name
    }
}

If your Human type can be made Copy (which behaves similarly to a C# value type) then you can simplify matters by making HumanToEmployeeAdapter own the Human, which means you don't have to worry about the lifetimes of the references:

#[derive(Copy, Clone)]
struct Human<'a> {
    name: &'a str,
}

trait Employee {
    fn id(&self) -> i32;
    fn name(&self) -> &str;
}

struct HumanToEmployeeAdapter<'a> {
    human: Human<'a>,
    id: i32,
}

impl<'a> HumanToEmployeeAdapter<'a> {
    fn new(id: i32, human: Human<'a>) -> Self {
        HumanToEmployeeAdapter { id, human }
    }
}

impl<'a> Employee for HumanToEmployeeAdapter<'a> {
    fn id(&self) -> i32 {
        self.id
    }

    fn name(&self) -> &str {
        self.human.name
    }
}

Note that you still need to track the lifetime of the name because &str is a reference. If you made it into an owned String, then you wouldn't need the lifetime parameter for Human, but then Human couldn't be Copy. That's because Strings cannot be safely copied in memory, due to their Drop impl (similar to a C# finalizer), which would cause a double-free if Rust allowed you to do it - which is why it doesn't.

Upvotes: 4

Related Questions