What reference explanations get wrong, run¶
References › Claims, run
Level: 201 · a companion to the path
One line: Explanations of references mostly state the rule correctly and then get a snippet, an edition or a size wrong. Claims from a Stack Overflow thread, The Book, Programming Rust, Cliffle's Learn Rust the Dangerous Way, the Stacked Borrows paper and a chat answer about ref, each checked on rustc 1.98.0 — the ones that compile by the program below, the rest by the compiler's refusal.
Eight claims, run¶
Verified output of reference_claims_checked.rs — regenerated by tools/run_examples.py, never hand-typed.
1. Stack Overflow, accepted answer: the pointer snippet, once `x` is `mut`
*ref1 = 12
after *ref2 += 1 and *raw2 += 1: x = 14
2. Stack Overflow, 2015 answer: a raw pointer from an integer is safe to make
123 as *const String compiled with no unsafe; null? false
&raw const x, read in unsafe: 14
3. Programming Rust: at run time a reference is an address
&u32: 1 word &str: 2 words &[u8]: 2 words
4. The Book, ch. 15.4, Listing 15-19: the counts
count after creating a = 1
count after creating b = 2
count after creating c = 3
sum(c) = 19
count after c goes out of scope = 2
sum(b) = 18
5. The Book, ch. 15.4: "Rc<T> allows you to share data ... for reading only"
count 1, Rc::get_mut wrote in place: "roster v2"
count 2, Rc::get_mut is None: true
count 2, Rc::make_mut cloned first: roster "roster v2!", other "roster v2"
6. "References have explicit lifetimes": this borrow names none
let reference: &i32 = &*boxed; -> 42
7. Learn Rust the Dangerous Way, part 2, without the static mut
offset_momentum(&mut bodies): bodies[0].velocity = 0.5
8. std::cell: a Cell is written through shared references
two & to one Cell, r1.set(r2.get() + 1) -> 2
Reading them in order:
- The Stack Overflow accepted answer's pointer snippet compiles once
xismutand each reference's last use comes before the next borrow. As posted it does not compile at all — below. - A raw pointer can be made from any integer without
unsafe(the 2015 answer by Veedrac):123 as *const Stringis safe, and so is&raw const x, which makes a raw pointer without an intermediate reference. Only reading through them needsunsafe. - Programming Rust: "at run time, a reference is nothing but an address." True for
&u32. A&stror&[u8]is an address and a length — two words — which is whystris unsized exists as a page. - The Book's Listing 15-19 prints 1, 2, 3, 2, as it says. Built with
rustcit also prints three warnings the Book's transcript does not show —bandcunused, and theConsfields never read — so this version reads them withsum. - The Book:
Rc<T>shares data "for reading only". ThroughDeref, yes. ButRc::get_mutwrites in place while the count is one, andRc::make_mutclones first when it is not.Rchas the rest. - "References in Rust have explicit lifetimes" (a note that circulates with the
Boxexample).let reference: &i32 = &*boxed;names no lifetime, and nothing in the program does; the compiler infers every one. What is explicit is the refusal ifboxedis dropped whilereferenceis still used — Six pointer types, one table runs thatE0505. - Cliffle's
offset_Momentum, without thestatic mut. Part 2 passes&mut solar_Bodiesfrom insideunsafe. On edition 2024 that line is an error (below); owning the array inmainneeds nounsafeat all, and keeps the part of the argument that matters — the length is in the parameter's type. std::cell:Cell<T>andRefCell<T>can be mutated through shared references. Two&Cellto one value, one writes what the other read. Interior mutability is why that is not a hole in the borrow rules.
Refused by the compiler¶
The Stack Overflow pointer snippet¶
The accepted answer on Understanding and relationship between Box, ref, & and * ↗ (2015) shows how references and raw pointers are both made with &:
// matveev_pointers.rs
// let x: u32 = 12;
//
// let ref1: &u32 = &x;
// let raw1: *const u32 = &x;
//
// let ref2: &mut u32 = &mut x;
// let raw2: *mut u32 = &mut x;
//
// *ref1; *ref2;
//
// unsafe { *raw1; *raw2; }
error[E0596]: cannot borrow `x` as mutable, as it is not declared as mutable
--> matveev_pointers.rs:2:9
|
2 | let x: u32 = 12;
| ^ not mutable
...
7 | let ref2: &mut u32 = &mut x;
| ------ cannot borrow as mutable
8 | let raw2: *mut u32 = &mut x;
| ------ cannot borrow as mutable
error[E0502]: cannot borrow `x` as mutable because it is also borrowed as immutable
--> matveev_pointers.rs:7:26
|
4 | let ref1: &u32 = &x;
| -- immutable borrow occurs here
...
7 | let ref2: &mut u32 = &mut x;
| ^^^^^^ mutable borrow occurs here
...
10 | *ref1; *ref2;
| ----- immutable borrow later used here
The second E0502 is the same error at line 8. Adding mut removes the E0596 and leaves both E0502s: ref1 is still read on line 10, after &mut x on lines 7 and 8. Claim 1 above is the order that compiles, and A borrow is a loan explains the "later used here" line.
*mut SomeTrait¶
The same answer passes &[u32] or *mut SomeTrait around as its examples of fat pointers. A trait used as a type needs dyn since edition 2021:
error[E0782]: expected a type, found a trait
--> bare_trait_pointer.rs:2:17
|
2 | fn pass(p: *mut SomeTrait) -> *mut SomeTrait { p }
| ^^^^^^^^^
|
help: you can add the `dyn` keyword if you want a trait object
|
2 | fn pass(p: *mut dyn SomeTrait) -> *mut SomeTrait { p }
| +++
Under --edition 2015 and --edition 2018 the same file is a warning, "trait objects without an explicit dyn are deprecated". The answer's link for dynamically sized types, the first-edition book/unsized-types.html, still answers — with a stub pointing to ch. 20.3 ↗.
Learn Rust the Dangerous Way: a mutable reference to a static mut¶
Part 2 ↗ (written against rustc 1.39) changes the call site to offset_Momentum(&mut solar_Bodies) inside unsafe, where solar_Bodies is a static mut. The same shape on 1.98.0:
error: creating a mutable reference to mutable static
--> static_mut_ref.rs:8:25
|
8 | offset_momentum(&mut SOLAR_BODIES);
| ^^^^^^^^^^^^^^^^^ mutable reference to mutable static
|
= note: mutable references to mutable statics are dangerous; it's undefined behavior if any other pointer to the static is used or if any other reference is created for the static while the mutable reference lives
= note: for more information, see <https://doc.rust-lang.org/edition-guide/rust-2024/static-mut-references.html>
= note: `#[deny(static_mut_refs)]` (part of `#[deny(rust_2024_compatibility)]`) on by default
help: use `&raw mut` instead to create a raw pointer
Under --edition 2021 it is a warning, #[warn(static_mut_refs)]. The help line's &raw mut makes the error go away and keeps the hazard; claim 7 removes the static mut instead.
The part's other claim holds as written — a reference to an array of the wrong length is refused, larger as well as smaller:
error[E0308]: mismatched types
--> wrong_array_length.rs:6:21
|
6 | offset_momentum(&mut six);
| --------------- ^^^^^^^^ expected an array with a size of 5, found one with a size of 6
| |
| arguments to this function are incorrect
|
= note: expected mutable reference `&mut [Body; 5]`
found mutable reference `&mut [Body; 6]`
The Book, Listing 4-6: writing through a &¶
Ch. 4.2 ↗ shows change(&s) calling push_str on a &String. Still E0596 on 1.98.0, with different words from the Book's transcript — "so it cannot be borrowed as mutable" where the Book has "so the data it refers to cannot be borrowed as mutable", and the fix shown as a diff:
error[E0596]: cannot borrow `*some_string` as mutable, as it is behind a `&` reference
--> listing_4_6.rs:8:5
|
8 | some_string.push_str(", world");
| ^^^^^^^^^^^ `some_string` is a `&` reference, so it cannot be borrowed as mutable
|
help: consider changing this to be a mutable reference
|
7 | fn change(some_string: &mut String) {
| +++
The Book, Listing 15-17: two lists owning a third¶
Ch. 15.4 ↗ builds b and c from Box::new(a) twice. Still E0382, as the Book says; 1.98.0 adds a note the Book's transcript predates:
error[E0382]: use of moved value: `a`
--> listing_15_17.rs:11:30
|
9 | let a = Cons(5, Box::new(Cons(10, Box::new(Nil))));
| - move occurs because `a` has type `List`, which does not implement the `Copy` trait
10 | let b = Cons(3, Box::new(a));
| - value moved here
11 | let c = Cons(4, Box::new(a));
| ^ value used here after move
|
note: if `List` implemented `Clone`, you could clone the value
The Book's fix is Listing 15-18, Rc<List> in place of Box<List>; claim 4 above runs its next listing.
C has no reference variables, C++ does¶
A common summary: reference variables are supported by C++ and not by C. True — Apple clang 21, on the same three lines compiled as each language:
c_reference.c:3:9: error: expected identifier or '('
3 | int &r = x;
| ^
As C++ (-std=c++17) the file builds with no diagnostic. So does this, which is two dangling references — one to a local that is gone, one into a vector that push_back may have moved:
// dangling_refs.cpp
#include <vector>
int& to_a_local() {
int x = 1;
return x;
}
int main() {
std::vector<int> v{10, 11};
int& r = v[1];
v.push_back(12);
return r + to_a_local();
}
dangling_refs.cpp:5:12: warning: reference to stack memory associated with local variable 'x' returned [-Wreturn-stack-address]
5 | return x;
| ^
1 warning generated.
A warning for the first and nothing for the second, and the binary is built either way. In Rust the first is E0515 (A stack slot is reused) and the second is the paper's first example on A borrow is a loan.
Checked on the other pages¶
OTHER_PAGES_TABLE
Where this leaves you¶
- The rule, from the compiler's side: A borrow is a loan, after Borrowing.
- The pointer table the Stack Overflow thread got half right: Six pointer types, one table.
- The sources themselves, with what each is good for: Helpful resources.
- The same check on a fourth book, Rust: The Practical Guide: ownership in functions, run, and its §4.6 as a lesson, Mutable binding, mutable reference.
Docs: reference ↗ · pointer ↗ · Rc::get_mut ↗ · std::cell ↗ · Edition guide — static mut references ↗
Po polsku¶
Objaśnienia referencji zwykle dobrze podają regułę, a potem mylą się w przykładzie, edycji języka albo rozmiarze. Sprawdzone na rustc 1.98.0: fragment kodu z najwyżej ocenianej odpowiedzi na Stack Overflow się nie kompiluje (x nie jest mut, a ref1 jest użyte po &mut x); *mut SomeTrait od edycji 2021 to błąd E0782; referencja do str to adres i długość, więc „w czasie działania referencja to tylko adres” jest prawdą tylko dla typów o znanym rozmiarze; Rc::get_mut pozwala pisać, gdy licznik wynosi 1; &mut do static mut z kursu Cliffle'a to w edycji 2024 błąd; transkrypcje błędów w The Book pochodzą ze starszej wersji kompilatora. C nie ma zmiennych referencyjnych, C++ ma — i kompiluje dwie wiszące referencje, na jedną tylko ostrzegając.
Szukaj po polsku: wisząca referencja · rust static_mut_refs · rust E0782 dyn · rust Rc get_mut · referencja a wskaźnik