简体   繁体   中英

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?

PS 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?

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 String s 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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM