We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Ownership II: Borrowing and the Borrow Checker step 11 of 24
ref, ref mut, and default binding modes in edition 2024
pub fn bump_present(opts: &mut [Option<i64>], by: i64) -> (usize, usize)
Walk the slice. Every slot that holds Some(v) gets by added to it, in
place. Every None is left alone. Return (how many were bumped, how many were empty).
JSON null is None; any number is Some(n).
[], by=5 -> (0,0), []
[null,null], by=5 -> (0,2), [null,null]
[1,2,3], by=10 -> (3,0), [11,12,13]
[1,null,-4], by=-3 -> (2,1), [-2,null,-7]
The starter does not compile. The error is one line long and completely opaque until you know that default binding modes exist.
The invisible feature
Look at this and say what type v has:
if let Some(v) = slot { ... }
You cannot, from that line alone. It depends entirely on the type of slot:
type of slot |
type of v |
|---|---|
Option<i64> |
i64 |
&Option<i64> |
&i64 |
&mut Option<i64> |
&mut i64 |
That is match ergonomics, stabilised in Rust 2018 and the reason almost
all modern Rust reads the way it does. When you match a reference against a
pattern that is not itself a reference pattern, the compiler dereferences for
you and switches the default binding mode from move to ref or ref mut. Every binding inside inherits that mode.
It is enormously convenient and it is completely invisible. Nothing in
if let Some(v) = &mut opt contains the characters &mut next to v. A
learner who does not know the feature exists cannot explain why *v += 1
works in one place and v += 1 is a type error in another, and has no way to
find out, because there is no syntax to look up.
Reading the starter’s error
for slot in opts.iter() { // slot: &Option<i64>
if let Some(v) = slot { // binding mode becomes `ref` -> v: &i64
*v += by; // error[E0594]: cannot assign to `*v`,
// which is behind a `&` reference
iter() yields shared references, so the default binding mode is ref, so
v is a &i64, so writing through it is forbidden. The message even tells
you the shape of the fix — “consider changing this to be a mutable
reference” — but the change belongs on iter(), several lines up, not on the
binding it points at.
Swap iter() for iter_mut() and everything else stays identical: the
iterator now yields &mut Option<i64>, the binding mode becomes ref mut,
v becomes &mut i64, and *v += by compiles. One method call, and the
types of three things you never wrote down changed.
The explicit form, and why you rarely see it
Before match ergonomics you had to write the mode yourself:
match slot {
&mut Some(ref mut v) => *v += by,
&mut None => {}
}
ref and ref mut are pattern keywords meaning “bind by reference rather
than by value”. They still exist and still work. You will meet them in older
code and occasionally need them when you want a different mode from the
default. Do not write them where the default already does the job — that is
noise. (Clippy has a pedantic lint, ref_binding_to_reference, aimed at one
variant of that noise. It is allow-by-default and will not fire on this
problem; do not go looking for it.)
One related lint that is on by default is needless_borrowed_reference,
which fires on patterns like &(ref x) where x alone would do.
And toplevel_ref_arg catches let ref x = ...; in a function body, which
should be let x = &...;.
Edition 2024 tightened this, and it matters
The original ergonomics rules produced genuinely surprising types. The classic example:
let [&x, y] = &[&(), &()]; // pre-2024: x: (), y: &&()
Matching &x against a &&() stripped two layers of reference in one step.
Nobody predicted that correctly.
Edition 2024 (RFC 3627) makes three previously-legal shapes into hard errors. All three still compile on edition 2021:
-
let [x, mut y] = &[..];— “cannot mutably bind by value within an implicitly-borrowing pattern”. Mixing an explicitmutbinding into a pattern whose default mode isrefis now rejected. -
let [ref a] = &[..];— writingrefexplicitly while the default mode is alreadyrefis rejected rather than silently doubling up. -
let [&b, c] = &[..];— a reference pattern under an inherited reference mode is rejected instead of stripping two layers.
Your submissions here compile as edition 2024, so these are errors, not
warnings. If you copy a pattern from a pre-2024 blog post and it does not
compile, this is usually why. The rustc lint rust_2024_incompatible_pat
exists to catch the same shapes when migrating an older crate.
About your loop body
The else branch is not decoration — the problem asks you to count the empty
slots too, and it also keeps clippy from suggesting .flatten() (which would
be the idiomatic shape if you only cared about the Somes, and which would
hide the very binding you are here to understand).
Remember the grade is compile + tests + clippy -D warnings.
Loading visualization…
Stuck?
Rust reference solution
Sign in to attempt this problem and reveal the reference solution.