Start learning free

LESSON 563 OF 576 · OBJECT-ORIENTED PROGRAMMING - CHALLENGES

Thermostat in Rust: A Private Field with a Valid Range

Practice O(1) Read the full explanation

A thermostat keeps a target temperature, and the target must always stay between 10 and 30 degrees. If any code could change the target directly, nothing would stop it from setting 95. So the target is a private field: only the thermostat's own methods change it, and every one of them keeps it between 10 and 30.

As in the Access Modifiers lesson, a Rust struct field is private by default: without pub, code in other modules can't read or change it. Write:

  • The Thermostat struct, with one private field, target (an i32).
  • Thermostat::new(): returns a thermostat whose target is 20.
  • set_target(t) sets the target to t. A value below 10 sets it to 10, and a value above 30 sets it to 30.
  • warmer() raises the target by 1 and cooler() lowers it by 1, but neither ever takes it outside 10 to 30: at 30, warmer() leaves it at 30, and at 10, cooler() leaves it at 10.
  • get_target() returns the target. It is the only way for other code to read it.

The methods that change the target take &mut self; get_target only reads, so it takes &self. The starter code already has these headers.

The input is a list of operations, applied in order to one new thermostat made by Thermostat::new(). The output lists what the get_target calls return, in order.

 

Example 1:

Input:
    get_target()
    warmer()
    warmer()
    get_target()
    cooler()
    get_target()
Output: 20 22 21
Explanation: A new thermostat starts at 20. Two warmer() calls take it to 22, and cooler() brings it back to 21.

Example 2:

Input:
    set_target(35)
    get_target()
    warmer()
    get_target()
    cooler()
    get_target()
Output: 30 30 29
Explanation: 35 is above 30, so the target becomes 30. warmer() can't go past 30, so the target stays 30.
cooler() lowers it to 29.

Example 3:

Input:
    set_target(4)
    get_target()
    cooler()
    get_target()
    set_target(10)
    warmer()
    get_target()
Output: 10 10 11
Explanation: 4 is below 10, so the target becomes 10, and cooler() can't go below 10. set_target(10) keeps 10,
since 10 itself is allowed, and warmer() raises it to 11.

 

Constraints:

  • -1000 <= t <= 1000
  • A test case has from 1 to 1000 operations, and at least one of them is get_target.
STEP 1 OF 8
    Ctrl + Enter to check

    RUST · OBJECT-ORIENTED PROGRAMMING - CHALLENGES

    Thermostat in Rust, explained

    Problem overview

    A thermostat keeps a target temperature, and the target must always stay between 10 and 30 degrees. If any code could assign to the target directly, nothing would stop it from setting 95. So the target is a private field, and only the thermostat's own methods change it. Each of them keeps the value in range.

    You write the Thermostat struct with one private field, target (an i32), and these functions:

    • Thermostat::new() returns a thermostat whose target is 20.
    • set_target(t) sets the target to t. A value below 10 sets it to 10, and a value above 30 sets it to 30.
    • warmer() raises the target by 1 and cooler() lowers it by 1, but neither ever leaves the range 10 to 30.
    • get_target() returns the target. It is the only way for other code to read it.

    For example, a new thermostat reads 20. Two warmer() calls take it to 22, and cooler() brings it back to 21. After set_target(35) it reads 30, and warmer() leaves it at 30.

    Approach

    The real work is one rule: every new target is clamped into 10 to 30. A value that is too low isn't refused. The target becomes the lowest one allowed, and in the same way a too-high value becomes 30. Values from 10 to 30, endpoints included, are kept as they are.

    If warmer and cooler each repeated that rule, there would be three copies to keep in step. Instead they call set_target with self.target + 1 or self.target - 1. At 30, warmer asks for 31 and set_target turns it back into 30. The rule lives in one place.

    Privacy is what makes the rule worth anything. In Rust a struct field is private by default: without pub, code in other modules can't read or change it. This lesson writes the struct and its methods in one module, but code in another module would have to go through set_target, warmer, cooler and get_target. With a field marked pub, it could write t.target = 95 and break the rule.

    Step by step

    1. Declare the field as target: i32, without pub.
    2. Start at 20 in new, with the struct literal as the last expression.
    3. In set_target, handle the low side: if t < 10, set self.target to 10.
    4. Handle the high side: an else if t > 30 that sets 30.
    5. Everything else is in range: an else that stores t. The values 10 and 30 themselves end up here.
    6. warmer calls self.set_target(self.target + 1) and cooler calls self.set_target(self.target - 1).
    7. get_target returns self.target, with no semicolon.

    The solution in Rust

    struct Thermostat {
        target: i32,
    }
    impl Thermostat {
        fn new() -> Thermostat {
            Thermostat { target: 20 }
        }
        fn set_target(&mut self, t: i32) {
            if t < 10 {
                self.target = 10;
            } else if t > 30 {
                self.target = 30;
            } else {
                self.target = t;
            }
        }
        fn warmer(&mut self) {
            self.set_target(self.target + 1);
        }
        fn cooler(&mut self) {
            self.set_target(self.target - 1);
        }
        fn get_target(&self) -> i32 {
            self.target
        }
    }
    

    A run that uses every method:

    let mut t = Thermostat::new();
    println!("{}", t.get_target());
    t.warmer();
    t.warmer();
    println!("{}", t.get_target());
    t.cooler();
    println!("{}", t.get_target());
    t.set_target(35);
    println!("{}", t.get_target());
    t.warmer();
    println!("{}", t.get_target());
    t.set_target(4);
    println!("{}", t.get_target());
    t.cooler();
    println!("{}", t.get_target());
    t.set_target(10);
    t.warmer();
    println!("{}", t.get_target());
    

    It prints, one number per line:

    20
    22
    21
    30
    30
    10
    10
    11
    

    Rust details worth knowing:

    • &mut self and &self. The methods that change the target take &mut self, a mutable borrow. get_target only reads, so &self is enough. The variable that holds the thermostat must be declared let mut to call the first group.
    • Calling one method from another. Inside warmer, self is already a mutable borrow, so it can call self.set_target(...) directly. The argument self.target + 1 is computed first, so there is no clash between the read and the write.
    • The privacy error. Outside the module, an assignment such as t.target = 95 fails to compile with "field target of struct Thermostat is private". The compiler enforces the rule, not a convention.
    • Value in, value out. set_target returns nothing, so every branch ends with a semicolon. get_target returns a number, so its body is an expression with no semicolon.

    Complexity

    Each method does a few comparisons and one assignment, so every call is O(1) time and the struct uses O(1) space.

    Edge cases and common mistakes

    • The endpoints. 10 and 30 are allowed, so set_target(10) keeps 10. The checks are strict (t < 10 and t > 30), so the endpoints fall through to the last branch and are stored unchanged.
    • Refusing instead of clamping. set_target(35) doesn't leave the old target in place. It sets 30.
    • Stepping past the end. At 30, warmer leaves the target at 30, and at 10, cooler leaves it at 10. Writing self.target += 1 directly would reach 31.
    • Making the field pub. It would compile and pass simple examples, but other code could then store 95.
    • Very large or small values. A t of -1000 gives 10 and 1000 gives 30. A cooler() call after that gives 29, so the thermostat can always step again from the end it landed on.

    Testing

    • A new thermostat: get_target() gives 20. After two warmer() calls it gives 22, and after cooler() it gives 21.
    • set_target(35), get_target(), warmer(), get_target(), cooler(), get_target() gives 30, 30, 29.
    • set_target(4), get_target(), cooler(), get_target() gives 10, 10. Then set_target(10), warmer(), get_target() gives 11.
    Keep going with Rust500+ step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free