We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Shape Your Data: Structs, Enums, Pattern Matching step 4 of 26
impl blocks: associated functions, methods, Self and associated constants
Build a counter type and drive it from a list of opcodes.
pub fn run_counter(ops: Vec<String>) -> i64
The counter starts at 0. "bump" adds STEP (which is 3). "reset"
puts it back to the state a freshly built counter is in. Any other string is
ignored. Return the final value.
The starter gives you struct Counter(i64) and Counter::new(); you supply
the associated constant STEP and the methods bump, reset and get. It
does not compile until you do — E0599, “no method named bump found”.
Rust has no classes
It has types, and separately it has impl blocks that hang functions off a
type. Nothing in the language corresponds to a class body, and nothing
corresponds to a constructor.
impl Counter {
const STEP: i64 = 3;
fn new() -> Self { Self(0) } // associated function
fn bump(&mut self) { self.0 += Self::STEP; } // method
fn get(&self) -> i64 { self.0 } // method
}
Two kinds of thing live in there, and the difference is one word:
-
An associated function has no
selfparameter. You call it with::, as inCounter::new().newis just a name Rust programmers agreed on. It has no special status, no keyword, no compiler magic. You can call itbuild,with_capacity,from_parts, or have five of them. -
A method takes
self,&selfor&mut selffirst. You call it with., as incounter.bump().
Once that lands, a lot of transplanted object-oriented confusion evaporates.
There is no this. There is no implicit receiver. Counter::new() is an
ordinary function that happens to be namespaced under a type.
Which self?
-
&self— I will read you. The caller keeps the value and can use it after. -
&mut self— I will change you. The caller keeps it, but nothing else may touch it while I have it. -
self— I am consuming you. The caller cannot use it afterwards.
Choosing the weakest one that works is not politeness; it is what keeps your
API usable. get should be &self, not self.
Self with a capital S
Inside an impl Counter, Self is an alias for Counter. Use it. It is
shorter, it survives a rename, and it is what new() -> Self means. The
use_self lint exists to push you toward it.
It works in expression position too: Self(0) builds a Counter, and
Self::STEP reads the associated constant. And because reset has &mut self,
the tidiest body is *self = Self::new(); — assign a whole fresh value
through the mutable reference, rather than poking fields one at a time.
Associated constants
const STEP: i64 = 3; inside the impl block belongs to the type, not to
any value. Counter::STEP works from outside; Self::STEP from inside.
There is no instance storage — it is a compile-time constant with a
namespace, and it is the right home for a magic number that only means
something in the context of this type.
Two lints that will bite you eventually
new_without_default says: if you offer a public pub fn new() -> Self
taking no arguments, you should also implement Default, because generic
code asks for Default and cannot ask for new. The rule is narrow, and
worth knowing precisely: it is gated on visibility and arity. A private
fn new() does not trigger it — which is why the new here does not — and
neither does pub fn new(x: i64, y: i64).
wrong_self_convention enforces the naming convention the standard library
uses: to_* borrows (&self), into_* consumes (self), as_* is a cheap
borrowing view, is_* and has_* answer questions about &self. Name a
method to_u32 and take self by value on a non-Copy type and clippy will
stop you.
Remember the grade is compile + tests + clippy -D warnings.
Stuck?
Rust reference solution
Sign in to attempt this problem and reveal the reference solution.