Skip to content

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:

  1. The Stack Overflow accepted answer's pointer snippet compiles once x is mut and each reference's last use comes before the next borrow. As posted it does not compile at all — below.
  2. A raw pointer can be made from any integer without unsafe (the 2015 answer by Veedrac): 123 as *const String is safe, and so is &raw const x, which makes a raw pointer without an intermediate reference. Only reading through them needs unsafe.
  3. Programming Rust: "at run time, a reference is nothing but an address." True for &u32. A &str or &[u8] is an address and a length — two words — which is why str is unsized exists as a page.
  4. The Book's Listing 15-19 prints 1, 2, 3, 2, as it says. Built with rustc it also prints three warnings the Book's transcript does not show — b and c unused, and the Cons fields never read — so this version reads them with sum.
  5. The Book: Rc<T> shares data "for reading only". Through Deref, yes. But Rc::get_mut writes in place while the count is one, and Rc::make_mut clones first when it is not. Rc has the rest.
  6. "References in Rust have explicit lifetimes" (a note that circulates with the Box example). let reference: &i32 = &*boxed; names no lifetime, and nothing in the program does; the compiler infers every one. What is explicit is the refusal if boxed is dropped while reference is still used — Six pointer types, one table runs that E0505.
  7. Cliffle's offset_Momentum, without the static mut. Part 2 passes &mut solar_Bodies from inside unsafe. On edition 2024 that line is an error (below); owning the array in main needs no unsafe at all, and keeps the part of the argument that matters — the length is in the parameter's type.
  8. std::cell: Cell<T> and RefCell<T> can be mutated through shared references. Two &Cell to 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; }
Abridged — real rustc output for matveev_pointers.rs, three errors
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:

Abridged — real rustc output for bare_trait_pointer.rs
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:

Abridged — real rustc output for static_mut_ref.rs, --edition 2024
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:

Abridged — real rustc output for wrong_array_length.rs
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:

Abridged — real rustc output for listing_4_6.rs
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:

Abridged — real rustc output for listing_15_17.rs
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 and cpp_reference.cpp
int main(void) {
    int x = 1;
    int &r = x;
    return r;
}
Abridged — real clang output for c_reference.c, -std=c17
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();
}
Real clang output for dangling_refs.cpp, -std=c++17 -Wall -Wextra
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

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