Start learning free

LESSON 565 OF 576 · OBJECT-ORIENTED PROGRAMMING - CHALLENGES

Employees and Managers in Rust: A Trait and Composition

Practice O(1) Read the full explanation

A company pays every employee a salary. A manager is an employee too, but earns a bonus on top of the salary. Rust has no class inheritance: as in the lesson Inheritance, a Manager holds an Employee in a field and forwards calls to it, and the behavior both share lives in a trait, Worker.

Write the trait Worker:

  • get_name(&self) -> &str and pay(&self) -> i32, which each type writes for itself;
  • describe(&self) -> String, with a default body written once, in the trait: it returns the text <name> earns <pay>, where the amount is what self.pay() returns, for example Ana earns 3000. Every type that implements Worker gets this describe unchanged, and inside it self.pay() runs that type's own pay.

the struct Employee:

  • its fields name and salary, and Employee::new(name, salary);
  • in impl Worker for Employee: get_name returns the name, pay returns the salary.

and the struct Manager:

  • its fields employee, the Employee inside it, and bonus, and Manager::new(name, salary, bonus), which builds the inner employee with Employee::new;
  • in impl Worker for Manager: get_name forwards to the inner employee, and pay returns the inner employee's pay plus the bonus. That call, self.employee.pay(), is how Rust calls "the parent's version": it is what knows how an employee is paid.

The input is a list of people, one per line: employee name salary or manager name salary bonus. For each line, the code that tests your types creates that value with Employee::new or Manager::new and prints what its describe() returns, on a line of its own.

 

Example 1:

Input:
    employee Ana 3000
    manager Bob 4000 1000
Output:
    Ana earns 3000
    Bob earns 5000
Explanation:
Ana's pay is her salary. Bob's pay is the inner employee's pay, his salary 4000, plus his bonus 1000.
Bob's describe() is the trait's default describe(), but the pay() it calls is Manager's.

Example 2:

Input:
    manager Cleo 5000 0
    employee Dan 5000
Output:
    Cleo earns 5000
    Dan earns 5000
Explanation: A manager whose bonus is 0 earns exactly the salary, like an employee.

Example 3:

Input:
    manager Eve 7000 2500
    employee Finn 2800
    employee Gus 3100
    manager Hana 6000 500
Output:
    Eve earns 9500
    Finn earns 2800
    Gus earns 3100
    Hana earns 6500
Explanation: Employees and managers come in any order, and each value keeps its own name and numbers.

 

Constraints:

  • 0 <= salary <= 106
  • 0 <= bonus <= 106
  • A name is one word of 1 to 20 English letters. Two people may have the same name.
  • At most 100 people per test case.
STEP 1 OF 11
    Ctrl + Enter to check

    RUST · OBJECT-ORIENTED PROGRAMMING - CHALLENGES

    Employees And Managers in Rust, explained

    Problem overview

    A company pays every employee a salary. A manager is an employee too, but earns a bonus on top. In many languages a Manager class would inherit from Employee. Rust has no class inheritance, so this lesson uses two other tools:

    • A trait, Worker, for the behavior both types share.
    • Composition: a Manager holds an Employee in a field and forwards calls to it.

    The trait Worker has three functions: get_name(&self) -> &str and pay(&self) -> i32, which each type writes for itself, and describe(&self) -> String, which has a default body written once in the trait. It returns text such as Ana earns 3000.

    An Employee has a name and a salary, and its pay is the salary. A Manager has an employee and a bonus, and its pay is the inner employee's pay plus the bonus. Ana, an employee with a salary of 3000, earns 3000. Bob, a manager with a salary of 4000 and a bonus of 1000, earns 5000.

    Approach

    Look at what the two types share and what differs:

    • Both have a name, and both can be described the same way: "name earns pay". That sentence is written once, as the default body of describe.
    • Only pay differs. An employee pays its salary. A manager pays whatever its employee pays, plus the bonus.

    The default describe calls self.pay(), and self is the type it is called on. For a Manager that runs the manager's own pay, so a manager's description includes the bonus without any extra code. A type gets describe for free by writing impl Worker for ... and providing the two required functions.

    A manager answers by asking the employee it holds. Rust doesn't pass methods on by itself, so Manager::get_name returns self.employee.get_name(), and Manager::pay calls self.employee.pay(). That call is Rust's way of asking for "the parent's version": it knows how an employee is paid, so the manager doesn't repeat it.

    Step by step

    1. Write describe in the trait. The body is format!("{} earns {}", self.get_name(), self.pay()), without a semicolon. format! works like println!, but returns the text as a String instead of printing it.
    2. Declare the Employee fields: name: String and salary: i32.
    3. Build the employee in Employee::new. The name arrives as a &str, so name.to_string() makes the owned String to store.
    4. Implement Worker for Employee. get_name returns &self.name, a borrow of the stored string, and pay returns self.salary.
    5. Declare the Manager fields: employee: Employee and bonus: i32. The manager reuses the employee's data and code through this field.
    6. Build the manager in Manager::new. The inner employee is made by Employee::new(name, salary), the "parent's constructor".
    7. Implement Worker for Manager. get_name forwards to self.employee.get_name(), and pay returns self.employee.pay() + self.bonus.

    The solution in Rust

    trait Worker {
        fn get_name(&self) -> &str;
        fn pay(&self) -> i32;
        fn describe(&self) -> String {
            format!("{} earns {}", self.get_name(), self.pay())
        }
    }
    struct Employee {
        name: String,
        salary: i32,
    }
    impl Employee {
        fn new(name: &str, salary: i32) -> Employee {
            Employee { name: name.to_string(), salary: salary }
        }
    }
    impl Worker for Employee {
        fn get_name(&self) -> &str {
            &self.name
        }
        fn pay(&self) -> i32 {
            self.salary
        }
    }
    struct Manager {
        employee: Employee,
        bonus: i32,
    }
    impl Manager {
        fn new(name: &str, salary: i32, bonus: i32) -> Manager {
            Manager { employee: Employee::new(name, salary), bonus: bonus }
        }
    }
    impl Worker for Manager {
        fn get_name(&self) -> &str {
            self.employee.get_name()
        }
        fn pay(&self) -> i32 {
            self.employee.pay() + self.bonus
        }
    }
    

    Using the types:

    let ana = Employee::new("Ana", 3000);
    let bob = Manager::new("Bob", 4000, 1000);
    println!("{}", ana.describe());
    println!("{}", bob.describe());
    println!("{}", bob.employee.describe());
    let staff: Vec<Box<dyn Worker>> = vec![
        Box::new(Employee::new("Finn", 2800)),
        Box::new(Manager::new("Hana", 6000, 500)),
    ];
    for w in &staff {
        println!("{}", w.describe());
    }
    

    It prints:

    Ana earns 3000
    Bob earns 5000
    Bob earns 4000
    Finn earns 2800
    Hana earns 6500
    

    The third line is the inner employee asked on its own: it knows only the salary, 4000. The manager adds the bonus on top. The last two lines show why a trait is useful: a vector of Box<dyn Worker> mixes employees and managers, and each element runs its own pay.

    Rust details worth knowing:

    • Default methods. A trait function with a body is a default. Types that implement the trait get it unchanged, but any type may write its own version instead.
    • Borrowed returns. get_name returns &str, a borrow tied to &self. Returning &self.name works because a &String converts to a &str where one is expected. The caller never gets a copy of the name.
    • self.pay() inside Manager::pay would loop. It calls the very same function again. The compiler warns "function cannot return without recursing". The forwarding call goes through the field: self.employee.pay().
    • Struct fields are private to the module. The types reach the data through their methods.

    Complexity

    Each call does a fixed amount of work, so describe, pay and get_name take constant time apart from building the output string, which is proportional to the length of the name. Each value stores its name, a salary, and for a manager a bonus.

    Edge cases and common mistakes

    • A bonus of 0. A manager with a salary of 5000 and a bonus of 0 earns exactly 5000, like an employee. The line is Cleo earns 5000.
    • Reading the salary directly. Writing self.employee.salary + self.bonus compiles in the same module but bypasses Employee::pay. If the way employees are paid changed, managers would silently keep the old rule.
    • Writing describe twice. Copying it into both impl blocks works but defeats the trait default. A change in the sentence would then need two edits.
    • Forgetting to forward get_name. A manager has no name field of its own, so it must ask its employee.
    • Semicolon after the last expression. format!(...); returns () instead of the String, and the compiler reports a type mismatch.

    Testing

    • Employee::new("Ana", 3000) describes itself as Ana earns 3000.
    • Manager::new("Bob", 4000, 1000) gives Bob earns 5000.
    • Manager::new("Cleo", 5000, 0) gives Cleo earns 5000.
    • Manager::new("Hana", 6000, 500) gives Hana earns 6500.
    Keep going with Rust500+ step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free