When you need the *¶
References › When you need the *
Level: 101 → 201 · for newcomers
One line: Rust writes the * for you in five places — the dot, an operator with an impl for &T, formatting, a pattern, and a deref coercion between reference types — and nowhere else, so on a flag: &bool, !flag compiles and if flag does not.
let on = true;
let flag: &bool = &on;
if *flag { println!("on"); } // on
println!("{}", !flag); // false
// if flag { … } // E0308: expected `bool`, found `&bool`
Neither !flag nor if flag has a *. The first compiles because std carries a separate impl Not for &bool; a condition has no impl to look for.
Five places the * is written for you¶
you write, with flag: &bool and n: &i32 |
why no * |
|---|---|
n.abs(), n.pow(2) |
the dot: method resolution dereferences the receiver until a method fits |
!flag, flag & other, flag \| other, n + 1, -n |
std implements the operator on the reference as well: impl Not for &bool, impl BitAnd<bool> for &bool, impl BitOr<bool> for &bool, impl Add<i32> for &i32, impl Neg for &i32 — see operators are traits |
println!("{n}") |
impl<T> Display for &T where T: Display + ?Sized forwards to the i32 |
match flag { true => …, false => … } |
match ergonomics: a non-reference pattern matched against a reference dereferences it |
show(&r) with r: &bool, and show(&boxed) with boxed: Box<bool>, for fn show(flag: &bool) |
deref coercion: at a coercion site a &&bool or a &Box<bool> becomes the &bool the parameter wants — a * inserted inside the &, never one that reaches bool |
flag is still a &bool in the first four rows; nothing converted it. The fifth converts, but only from one reference type to another. Each row works because something was written for the reference type specifically, which is why the list is short.
A condition wants exactly bool¶
if, while, && and || take a bool and consult no trait — && and || cannot be overloaded — so there is nothing for a &bool to match:
error[E0308]: mismatched types
--> if_flag.rs:4:8
|
4 | if flag {
| ^^^^ expected `bool`, found `&bool`
|
help: consider dereferencing the borrow
|
4 | if *flag {
| +
while flag, flag && other and other || flag are the same E0308. So are let b: bool = flag; and passing flag to a fn(bool): no coercion goes from a reference to the value behind it.
== wants both sides at the same depth¶
std compares references with references — impl<A, B> PartialEq<&B> for &A where A: PartialEq<B> — and has no impl comparing &bool with bool:
error[E0277]: can't compare `&bool` with `bool`
--> flag_eq_true.rs:4:13
|
4 | if flag == true {
| ^^ no implementation for `&bool == bool`
|
= help: the trait `PartialEq<bool>` is not implemented for `&bool`
help: consider dereferencing here
|
4 | if *flag == true {
| +
Either side can move: *flag == true compares two bool, flag == &true compares two &bool. assert_eq!(flag, true) is the same E0277, because it compares with ==.
> on a &i32 is the same mistake under a different code:
error[E0308]: mismatched types
--> n_gt_one.rs:4:12
|
4 | if n > 1 {
| ^ expected `&i32`, found integer
|
help: consider dereferencing the borrow
|
4 | if *n > 1 {
| +
The code differs because the impl lists do. &A has two PartialEq impls, against &B and against &mut B, so rustc cannot settle the right-hand type and reports a missing impl. It has one PartialOrd impl, against &B, so rustc knows the right side must be &i32 and reports a mismatch. *n > 1 and n > &1 both compile — while n + 1 needs neither, because arithmetic does have an Add<i32> for &i32.
The dot stops at the first receiver that fits¶
n.abs() works because &i32 has no abs, so the search dereferences to i32. max is different: Ord is implemented for &A as well, so n.max(…) is found on &i32 itself, and its argument has to be a &i32:
error[E0308]: mismatched types
--> max_on_a_reference.rs:4:26
|
4 | println!("{}", n.max(3));
| --- ^ expected `&i32`, found integer
| |
| arguments to this method are incorrect
|
help: consider borrowing here
|
4 | println!("{}", n.max(&3));
| +
rustc's n.max(&3) compiles and hands back a &i32; (*n).max(3) hands back an i32. The search takes the first candidate, not the one you meant.
Closures: count the &¶
bits.iter() yields &bool. any ↗ passes that item on as it is; filter ↗ passes a reference to it:
let bits = vec![true, false, true];
let any = bits.iter().any(|b| *b); // true — b: &bool
let set = bits.iter().filter(|b| **b).count(); // 2 — b: &&bool
let same = bits.iter().filter(|&&b| b).count(); // 2 — both `&` taken apart in the pattern
let has = bits.contains(&true); // true — contains takes `x: &T`
One * short is the if flag error again: .filter(|b| *b) is E0308, expected bool, found &bool. contains ↗ goes the other way — bits.contains(true) is E0308, expected &bool, found bool. The second & in filter is the extra & you did not ask for.
Writing through &mut bool¶
*slot names the place slot points at — *r is a place, not a value. Without the *, slot = true tries to re-point the reference itself:
error[E0308]: mismatched types
--> write_through_mut.rs:4:12
|
3 | let slot: &mut bool = &mut on;
| --------- expected due to this type
4 | slot = true;
| ^^^^ expected `&mut bool`, found `bool`
|
help: consider dereferencing here to assign to the mutably borrowed value
|
4 | *slot = true;
| +
error[E0600]: cannot apply unary operator `!` to type `&mut bool`
--> write_through_mut.rs:5:21
|
5 | let off: bool = !slot;
| ^^^^^ cannot apply unary operator `!`
The second error is the operator row of the first table running out: std's Not, BitAnd and BitOr impls cover bool and &bool, not &mut bool. So it is *slot = !*slot, and *slot |= true — slot |= true is E0368.
The whole table¶
| construct | needs *? |
why |
|---|---|---|
n.abs() |
no | the dot dereferences |
n.max(&3) |
no — but the argument needs & |
the dot stops at &i32, which has Ord |
!flag, flag & other, n + 1, -n |
no | an impl for &T |
println!("{n}") |
no | Display for &T |
match flag { true => … } |
no | match ergonomics |
show(&r), fn show(&bool), r: &bool |
no | deref coercion, &&bool to &bool |
if flag, while flag, flag && other |
yes | a condition takes bool; no trait, no coercion |
let b: bool = flag |
yes | no coercion from &T to T |
flag == true, n > 1 |
yes, or & on the other side |
same depth |
.any(\|b\| *b) |
one | the item is &bool |
.filter(\|b\| **b) |
two | a & to the &bool item |
slot = true on &mut bool |
yes | *slot is the place |
!slot on &mut bool |
yes | no Not for &mut bool |
bits.contains(true) |
no — it needs a & added |
contains takes &T |
Checkpoint¶
Predict before you open the answer. if flag is refused. Does assert!(flag) compile?
The answer
Verified output of when_you_need_the_star.rs — regenerated by tools/run_examples.py, never hand-typed.
1. The dot dereferences the receiver for you
n.abs() -> 7
n.pow(2) -> 49
2. An operator with an impl for &T needs no `*`
!flag -> false
flag & other -> false
flag | other -> true
n + 1 -> -6
-n -> 7
3. Formatting reads through the reference
println!("{n}") -> -7
4. A literal pattern matched against a reference
match flag { true => .. } -> on
5. Deref coercion: a `&bool` parameter accepts `&&bool` and `&Box<bool>`
show(&r) -> true
show(&boxed) -> false
6. A condition wants exactly `bool`: write the `*`
if *flag -> taken
*flag && !other -> true
7. `==` and `>` want both sides at the same depth
*flag == true -> true
flag == &true -> true
*n > 1 -> false
n > &1 -> false
8. The dot stops at the first receiver that fits
n.max(&3) -> 3, a &i32
(*n).max(3) -> 3, an i32
9. A closure gets one `&` per adapter
bits.iter().any(|b| *b) -> true
bits.iter().filter(|b| **b) -> 2 kept
bits.iter().filter(|&&b| b) -> 2 kept
bits.contains(&true) -> true
10. Writing through `&mut bool` names the place with `*`
*slot = true -> *slot = true
*slot = !*slot -> *slot = false
*slot |= true -> lamp = true
Checkpoint. `if flag` is refused. Does `assert!(flag)` compile?
yes: assert!(flag) expands to `if !flag { panic!(..) }`,
and `!` has an impl for &bool
On 1.98.0 assert!(flag) expands to if !flag { ::core::panicking::panic("assertion failed: flag") }. The macro never writes if flag; it writes !flag, which is the first table's impl Not for &bool. assert_eq!(flag, true) goes through == instead, and is refused.
Practice¶
Fewest stars. Each marked line is written with no * and no borrowing &. Add the fewest that make it compile — none is a possible answer — change nothing else on the line, and name the rule from the whole table that decides it. With flags [true, false, false], readings [4, -2, 9] and limit 7, what does each line produce?
fn report(flags: &[bool], readings: &[i32], limit: &i32, lamp: &mut bool) {
let first = &flags[0]; // &bool
let peak = readings.iter().max().unwrap(); // &i32
if first { println!("taken"); } // (a)
let over = peak > limit; // (b)
let high = readings.iter().filter(|n| n > limit).count(); // (c)
let dips = readings.iter().any(|n| n.is_negative()); // (d)
lamp = first | dips; // (e)
}
// in main: flags and readings are Vecs, limit an i32, lamp a bool
report(flags, readings, limit, lamp); // (f)
Solution
when_you_need_the_star_kata.rs in full — pasted here by tools/run_examples.py from the file CI compiles and runs.
//! Kata solution: fewest stars.
//!
//! The task wrote every marked line with no `*` and no borrow `&`. Four of the
//! six were refused by rustc 1.98.0: (a) `if first` E0308, (c) `n > limit` E0308,
//! (e) `lamp = first | dips` E0308, (f) `report(flags, readings, limit, lamp)`
//! E0308. (b) and (d) compiled as written. Each fix below adds the fewest
//! sigils; the comment names the rule, and the error one sigil fewer gives.
//!
//! rustc --edition 2024 when_you_need_the_star_kata.rs -o /tmp/when_you_need_the_star_kata && /tmp/when_you_need_the_star_kata
fn report(flags: &[bool], readings: &[i32], limit: &i32, lamp: &mut bool) {
let first = &flags[0]; // &bool
let peak = readings.iter().max().unwrap(); // &i32
// (a) 1 `*`. A condition wants exactly `bool`: no trait, no coercion.
if *first {
println!("(a) if *first -> taken");
}
println!(" rule: a condition wants `bool`");
// (b) 0. Both sides are `&i32`: `PartialOrd<&B> for &A` compares them.
// `*peak > *limit` compiles too, with two stars it did not need.
let over = peak > limit;
println!("(b) peak > limit -> {over}");
println!(" rule: same depth, &i32 > &i32");
// (c) 1 `*`. `iter()` yields `&i32`, `filter` passes `&&i32`; one `*` reaches
// `limit`'s depth. With none: E0308, expected `&&i32`, found `&i32`.
// `**n > *limit` compiles with three.
let high = readings.iter().filter(|n| *n > limit).count();
println!("(c) filter(|n| *n > limit).count() -> {high}");
println!(" rule: filter adds a `&`; then same depth");
// (d) 0. `&i32` has no `is_negative`, so the dot dereferences to `i32`.
let dips = readings.iter().any(|n| n.is_negative());
println!("(d) any(|n| n.is_negative()) -> {dips}");
println!(" rule: the dot");
// (e) 1 `*`. `*lamp` is the place; without it, E0308, expected `&mut bool`,
// found `bool`. The right side needs none: `impl BitOr<bool> for &bool`.
*lamp = first | dips;
println!("(e) *lamp = first | dips -> *lamp = {}", *lamp);
println!(" rule: `*lamp` is the place; `|` has an impl for &bool");
}
fn main() {
let flags = vec![true, false, false];
let readings = vec![4, -2, 9];
let limit = 7;
let mut lamp = false;
// (f) one `&` each. `&flags` is a `&Vec<bool>`, and the parameter's `&[bool]`
// accepts it: `Vec<bool>` derefs to `[bool]`. Without them, E0308.
report(&flags, &readings, &limit, &mut lamp);
println!("(f) report(&flags, &readings, &limit, &mut lamp)");
println!(" rule: deref coercion, &Vec<bool> -> &[bool] and &Vec<i32> -> &[i32]");
println!();
println!("stars in report: 3 (a, c, e); lamp is now {lamp}");
}
Verified output of when_you_need_the_star_kata.rs — regenerated by tools/run_examples.py, never hand-typed.
(a) if *first -> taken
rule: a condition wants `bool`
(b) peak > limit -> true
rule: same depth, &i32 > &i32
(c) filter(|n| *n > limit).count() -> 1
rule: filter adds a `&`; then same depth
(d) any(|n| n.is_negative()) -> true
rule: the dot
(e) *lamp = first | dips -> *lamp = true
rule: `*lamp` is the place; `|` has an impl for &bool
(f) report(&flags, &readings, &limit, &mut lamp)
rule: deref coercion, &Vec<bool> -> &[bool] and &Vec<i32> -> &[i32]
stars in report: 3 (a, c, e); lamp is now true
If you are coming from another language¶
- C.
if (flag)on abool *flagcompiles and tests the pointer: any non-null address is true, whatever theboolbehind it holds. Apple clang 21 with-Wall -Wextraprints no warning for it. Rust'sE0308onif flagis that bug made unwritable — a condition takes abool, and a reference is a different type. What does not carry over is C's rule that the*is always yours to write: Rust writes it at the dot, and an operator like!accepts the pointer directly because std wrote an impl for it. - C++. A
bool&is an alias, soif (flag)on one reads thebool, and every operator works through it with no*. A Rust&boolis not an alias; it is a value of its own type, closer to abool*that cannot be null or dangle. C++ lets abool*into a condition through the pointer-to-boolconversion, and aunique_ptr<bool>throughexplicit operator bool— both test for non-null, not thebool. Rust has neither conversion; the operators that do accept a&boolare impls listed in thebooldocs. The C++ reflex to unlearn is expecting a reference to behave like the thing it refers to everywhere. - Python.
if x:takes any object and asks it for truthiness, soif [False]:is taken — a non-empty list is true whatever it holds. Rust has no truthiness at all:if 1 {}isE0308, expectedbool, found integer, the same error asif flag. Python has no*to forget because it has no reference type to dereference; every name already refers. The confusion Rust stops here — testing the container instead of its contents — is the one Python'sif [False]:lets through. - ABAP. There is no boolean type to be confused about:
abap_boolis a one-character field holding'X'or space, so every condition is a comparison,IF flag = abap_true.— the shape of Rust's*flag == true. A data reference is read whole withr->*and a component withr->comp; the second is ABAP's version of the dot writing the*for you. Rust extends that convenience to the operators std implemented on references, such as!flag, and stops at the condition, where ABAP would also make you writer->* = abap_true.
See also¶
- Where the
&sits decides what it does —*ras a place, and the*in a type that is not a dereference - Method resolution — the search the dot runs, and where it stops
- Operators are traits — the traits behind
!,&,+and== - Coercion — the conversions the compiler does insert, and method calls as a different rule
- Match ergonomics — the pattern side, and the
**nclosure trap - Reborrowing — another
*a call site writes for you, as&mut *r - The Reference, the dereference operator ↗ ·
Deref↗ ·bool's trait implementations ↗, whereNot for &boolis listed
Po polsku¶
Pytanie brzmi: dlaczego przy bool gwiazdka raz jest potrzebna, a raz nie? Odpowiedź: chodzi o to, gdzie Rust sam dopisuje *, a gdzie nie. Przy flag: &bool wyrażenie !flag się kompiluje, a if flag już nie — i nie ma w tym sprzeczności. !flag działa, bo biblioteka standardowa ma osobną implementację impl Not for &bool. if nie szuka żadnej cechy (trait): chce dokładnie bool, więc &bool kończy się błędem E0308: expected bool, found &bool, a poprawka to if *flag.
Pięć miejsc, w których gwiazdkę pisze kompilator: kropka (n.abs() — wyszukiwanie metody wyłuskuje odbiorcę), operator z implementacją dla referencji (!flag, flag & other, n + 1), formatowanie (println!("{n}")), wzorzec (match flag { true => … }) i koercja deref — show(&r) przy fn show(flag: &bool) i r: &bool zamienia &&bool w &bool, ale nigdy nie dochodzi do samego bool. Nigdzie indziej. W warunku if/while, w && i ||, przy przypisaniu let b: bool = flag — gwiazdkę piszesz sam.
Porównanie chce obu stron na tej samej głębokości: flag == true to E0277 (can't compare &bool with bool), a działa *flag == true albo flag == &true. W domknięciach licz referencje: iter() daje &bool, filter podaje referencję do elementu, stąd filter(|b| **b), a any(|b| *b). Zapis przez slot: &mut bool to zawsze *slot = true — bez gwiazdki próbujesz przestawić samą referencję. Ciekawostka na koniec: assert!(flag) się kompiluje, bo makro rozwija się do if !flag { panic… }.
Szukaj po polsku: wyłuskanie referencji · operator gwiazdki w Ruscie · rust expected bool found &bool · rust can't compare &bool with bool · rust auto deref operators · rust **n filter closure