Start learning free

LESSON 566 OF 576 · OBJECT-ORIENTED PROGRAMMING - CHALLENGES

Shipping Options in Rust: Trait Default Methods

Practice O(1) Read the full explanation

An online shop offers three ways to ship a parcel. Standard shipping is the basic one. Express and Economy are kinds of shipping too: they answer the same two questions, what the option is called and what it costs, but each in its own way. Rust has no class inheritance; as in the lesson Method Overriding, the parent's behavior is a trait with default methods, and a type replaces a default by writing its own method in its impl block.

Write the trait Shipping, whose default methods are the standard option:

  • name(&self) -> &str returns "Standard".
  • cost(&self, weight: i32) -> i32 returns 5 + weight: a base price of 5, plus 1 for each kilogram.

Standard implements Shipping with an empty block, impl Shipping for Standard {}, so it keeps both defaults. Then write the two types that replace both methods:

  • Express: name returns "Express", and cost returns 10 + 2 * weight.
  • Economy: name returns "Economy", and cost returns 3 + weight / 2, where the division rounds down: 7 / 2 is 3.

The input lists parcels, one per line: the option (standard, express or economy) and the weight. For each line, the code that tests your types creates that option, keeps it as a Box<dyn Shipping>, and prints <name>: <cost> from its name() and cost(weight). The calls are the same for every option; each type answers with its own method, or with the trait's default where it has none.

 

Example 1:

Input:
    standard 4
    express 4
    economy 4
Output:
    Standard: 9
    Express: 18
    Economy: 5
Explanation: The same parcel of 4 kilograms costs 5 + 4 = 9 with Standard, 10 + 2 * 4 = 18 with Express
and 3 + 4 / 2 = 5 with Economy.

Example 2:

Input:
    standard 0
    express 0
    economy 0
Output:
    Standard: 5
    Express: 10
    Economy: 3
Explanation: With a weight of 0, only the base prices are left.

Example 3:

Input:
    economy 7
    economy 1
    standard 7
    express 7
Output:
    Economy: 6
    Economy: 3
    Standard: 12
    Express: 24
Explanation: Economy halves the weight and rounds down: 7 / 2 is 3, so 3 + 3 = 6, and 1 / 2 is 0, so 3 + 0 = 3.

 

Constraints:

  • 0 <= weight <= 1000, a whole number of kilograms.
  • At most 100 parcels per test case.
STEP 1 OF 6
    Ctrl + Enter to check

    RUST · OBJECT-ORIENTED PROGRAMMING - CHALLENGES

    Shipping Options in Rust, explained

    Problem overview

    A shop ships parcels in three ways. Standard is the basic option: its name is "Standard" and its cost for a parcel of weight kilograms is 5 + weight. Express and Economy answer the same two questions in their own way. Express costs 10 + 2 * weight, and Economy costs 3 + weight / 2, where the division drops the remainder (7 / 2 is 3).

    Rust has no class inheritance, so the shared behavior lives in a trait. The trait Shipping holds the standard option as default methods, and each type decides whether to keep or replace them.

    Approach: defaults in the trait, overrides in the types

    A trait method that has a body is a default. Any type that implements the trait gets that method for free. If the type writes a method with the same name in its own impl block, its version is used for that type only, and the default stays untouched for the others.

    That gives a simple layout. Standard has an empty block, impl Shipping for Standard {}, so it keeps both defaults. Express and Economy write both methods and replace both defaults.

    Step by step

    1. Write the default name. Its body is just the text "Standard", with no semicolon, because the last expression of a function is its return value. A string literal has type &str, which matches the return type.
    2. Write the default cost. It returns 5 + weight.
    3. Replace name for Express inside impl Shipping for Express.
    4. Replace cost for Express: 10 + 2 * weight. If you forget this method, an express parcel quietly gets the default price, 5 + weight.
    5. Replace name for Economy.
    6. Replace cost for Economy: 3 + weight / 2. Both operands are i32, so the division is an integer division.

    The solution in Rust

    trait Shipping {
        fn name(&self) -> &str {
            "Standard"
        }
    
        fn cost(&self, weight: i32) -> i32 {
            5 + weight
        }
    }
    
    struct Standard;
    
    impl Shipping for Standard {}
    
    struct Express;
    
    impl Shipping for Express {
        fn name(&self) -> &str {
            "Express"
        }
    
        fn cost(&self, weight: i32) -> i32 {
            10 + 2 * weight
        }
    }
    
    struct Economy;
    
    impl Shipping for Economy {
        fn name(&self) -> &str {
            "Economy"
        }
    
        fn cost(&self, weight: i32) -> i32 {
            3 + weight / 2
        }
    }
    

    The code that uses these types holds each option as a Box<dyn Shipping> and calls name() and cost(weight) the same way for every option. Here is a small program that does that, with the types above in the same file:

    fn main() {
        let options: Vec<Box<dyn Shipping>> = vec![Box::new(Standard), Box::new(Express), Box::new(Economy)];
        for option in &options {
            println!("{}: {}", option.name(), option.cost(4));
        }
    }
    

    It prints:

    Standard: 9
    Express: 18
    Economy: 5
    

    Each box runs the method of the type inside it. Standard answers with the defaults, the other two with their own methods.

    Rust details worth knowing

    • A unit struct. struct Standard; has no fields. You create it by writing its name, Standard, with no braces and no new.
    • Integer division truncates. With weight equal to 7, weight / 2 is 3, so Economy costs 6. With a weight of 1 the half is 0 and Economy costs 3.
    • Methods with the same signature. A replacement must match the trait's signature exactly: fn name(&self) -> &str and fn cost(&self, weight: i32) -> i32.

    Edge cases and common mistakes

    • A trailing semicolon. "Standard"; turns the body into a statement, and the function returns () instead of a &str. The compiler reports a type mismatch.
    • Replacing only one method. An Express that writes name but not cost reports itself as "Express" and charges the standard price.
    • Dividing the wrong thing. The Economy rule halves the weight, not the whole price. For a weight of 4 that gives 3 + 2 = 5, not (3 + 4) / 2 = 3.
    • Weight 0. Only the base prices remain: 5 for Standard, 10 for Express and 3 for Economy.

    Why defaults instead of copies

    You could write name and cost out in full for all three types. The result would work, but the standard price would then be written wherever a type happens to want it, and a change to the base price would have to be repeated in several places. With a default, the standard rule lives once in the trait, and only the types that differ say so. Reading impl Shipping for Standard {} tells you at a glance that Standard has no special behavior.

    The order of work in the lesson follows the same logic: first the defaults, which already make Standard complete, then one override at a time for Express and Economy. After each override, the type answers for itself and nothing else changes.

    Adding a fourth option

    The point of defaults is that a new type only writes what is different. An overnight option with its own price but the standard name needs only one method:

    struct Overnight;
    
    impl Shipping for Overnight {
        fn cost(&self, weight: i32) -> i32 {
            20 + 3 * weight
        }
    }
    

    Held in a Box<dyn Shipping> next to Standard and called with a weight of 2, it prints:

    Standard: 7
    Standard: 26
    

    The second line shows the default name at work: Overnight never wrote one, so it reports "Standard". In this lesson, Express and Economy replace both methods, so their names are their own. Whether a type keeps a default is a choice made type by type.

    If you do write a method in an impl Shipping for ... block, it must have the trait's exact signature. And a body such as "Standard";, with the semicolon, fails to compile with mismatched types: it expected &str and found ().

    Testing

    Running the three options on a few weights gives:

    standard 4 -> Standard: 9
    express 4 -> Express: 18
    economy 4 -> Economy: 5
    economy 7 -> Economy: 6
    economy 1 -> Economy: 3
    economy 0 -> Economy: 3
    express 0 -> Express: 10
    
    Keep going with Rust500+ step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free