Skip to content

← Shape Your Data: Structs, Enums, Pattern Matching step 4 of 26

Easy Primitives

impl blocks: associated functions, methods, Self and associated constants

Complete a counter type, process its commands in order, and return its final value. Each call starts with a fresh counter at zero.

pub fn run_counter(ops: Vec<String>) -> i64

Commands and required methods

  • "bump" adds three to the current value.
  • "reset" sets the value back to zero.
  • Every other string is ignored. Matching is case-sensitive and does not trim whitespace: "Bump", " bump" and "bump " are all ignored.

An empty list returns zero. For example, ["bump", "bump", "reset", "bump"] returns three, while ["bump", "skip", "bump"] returns six.

Keep struct Counter(i64) and its supplied constructor. Complete its impl with these items:

Item Purpose
const STEP: i64 = 3 The amount added by each bump.
bump(&mut self) Add Self::STEP to the stored value.
reset(&mut self) Restore the state produced by Self::new().
get(&self) -> i64 Read the value without consuming the counter.

Assume the running value fits in i64. Overflow is outside the exercise's input domain. The starter fails with E0599 because the called methods are missing. The output tests check the final value; they do not independently enforce the requested internal type and method design.

Associated functions and methods

An impl block associates functions and constants with a type. A method is an associated function with a receiver, such as &self or &mut self. An associated function without a receiver, such as Counter::new(), does not operate on an existing instance unless you pass one as an ordinary argument.

new is a conventional name, not a keyword or a special constructor mechanism. It is a function that returns a value. Rust also constructs struct values directly using struct expressions or tuple-struct constructors.

Methods support dot syntax. They can also be called through the type with an explicit receiver. This complete example shows both forms:

struct Meter(u32);

impl Meter {
    const STEP: u32 = 2;
    fn new() -> Self { Self(0) }
    fn advance(&mut self) { self.0 += Self::STEP; }
    fn value(&self) -> u32 { self.0 }
}

fn main() {
    let mut meter = Meter::new();
    meter.advance();
    Meter::advance(&mut meter);
    assert_eq!(meter.value(), 4);
    assert_eq!(Meter::value(&meter), 4);
}

Choose a receiver for the operation

&self borrows shared access. It is appropriate for get, which only reads this counter. Shared access does not universally forbid mutation: types with interior mutability can change through shared references.

&mut self borrows exclusive access and permits mutation. Use it for bump and reset. The borrow can end after the call when nothing keeps it active; a returned reference, for example, can extend it.

A by-value self receiver moves a non-Copy value into the method. Counter is non-Copy, so that receiver would consume it. For Copy types, a by-value call can copy the value instead. Other receiver forms exist, including self: Box<Self>; these three cover this exercise.

Self, self and the stored value

Inside impl Counter, capitalized Self names the Counter type. Lowercase self is the receiver value. self.0 accesses the tuple struct's stored i64. Self(0) constructs a Counter, and Self::STEP accesses its constant.

In reset, *self = Self::new() assigns a fresh value through the mutable reference. Setting self.0 = 0 also produces the required state for this one-field counter. The first form expresses reset in terms of the constructor.

STEP is associated with the type; it is not a field stored in each instance. Counter::STEP and Self::STEP name the same constant in this impl. Access from other modules still follows Rust's visibility rules: a private constant is not made public merely because it belongs to a public type.

Submissions must compile, pass the output tests and pass Clippy with warnings treated as errors. Keep the methods small and focused on their stated jobs.