Reputation: 5774
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
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 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.
Upvotes: 4