Skip to content

Six pointer types, one table

References › Six pointer types, one table

Level: 201 · working knowledge

One line: &T, &mut T, *const T, *mut T, Box<T> and Rc<T> differ on seven questions — who drops the pointee, what a write needs, Copy, unsafe, null, size, and where the pointee may be — and the popular table of them fills in columns it never defines.

use std::rc::Rc;

fn main() {
    let mut n = 1;

    let shared: &i32 = &n;               // borrows n
    println!("{shared}");                // 1

    let exclusive: &mut i32 = &mut n;    // borrows n, and nothing else may
    *exclusive += 1;

    let raw: *mut i32 = &mut n;          // no borrow the compiler tracks
    unsafe { *raw += 1 };                // so every use is unsafe
    println!("{n}");                     // 3

    let boxed: Box<i32> = Box::new(n);   // copies 3 into an allocation it owns
    let counted: Rc<i32> = Rc::new(n);   // the same, plus a count of owners
    let second = Rc::clone(&counted);    // a second owner, not a second 3
    println!("{boxed} {second} {}", Rc::strong_count(&counted)); // 3 3 2
}

Two of these borrow n, one points at it untracked, and two own a copy of it. That split — borrow, point, own — decides most of the table.

The table

Verified output of pointer_types_compared.rs — regenerated by tools/run_examples.py, never hand-typed.

                       &T         &mut T     *const T   *mut T     Box<T>     Rc<T>
owns (drops) pointee   no         no         no         no         yes        last one
write through it       no         yes        no         in unsafe  if mut     if unique
Copy                   yes        no         yes        yes        no         no
deref needs unsafe     no         no         yes        yes        no         no
can be null            no         no         yes        yes        no         no
pointee may live       anywhere   anywhere   anywhere   anywhere   heap       heap
P<u8>, words           1          1          1          1          1          1
P<[u8]>, words         2          2          2          2          2          2
P<str>, words          2          2          2          2          2          2
P<dyn Display>, words  2          2          2          2          2          2
Option<P<u8>>, words   1          1          2          2          1          1

How each row was filled
  owns       destructor runs when the pointer's scope ends:
             &T 0, &mut T 0, *const T 0, *mut T 0, Box<T> 1;
             Rc<T> 0 after dropping one of two clones, 1 after the last
  write      *m = 2 -> n = 2   unsafe { *q = 3 } -> n = 3   let mut b ...; *b = 4 -> *b = 4
             Rc::get_mut wrote 5 while unique; None after one clone: true
  Copy       controls for the probe: i32 true, String false
  deref      *at_local + *boxed + *counted = 13, outside any unsafe block
  null       yes where None needs a word of its own; std::ptr::null::<u8>().is_null() = true
  lives      one type, &i32, at a local 1, at a static 7, inside a Box 4
  words      size_of::<P>() / size_of::<usize>(), P being the pointer in that column

What each row asks, where the name alone does not say:

  • owns (drops) pointee — ending the pointer's scope runs the pointee's destructor. The program counts the runs.
  • write through it — *p = v compiles and lands. if mut is the binding: let mut b = Box::new(1), as for any owned value, while a &mut T binding needs no mut (*exclusive += 1 above). if unique is Rc::get_mut ↗, which returns Some only while the count is 1.
  • Copy — Probe<T> has an inherent is_copy in an impl<T: Copy> block and a same-named trait method returning false. Method lookup takes the inherent one only when the bound holds, so the call answers the question for a concrete type; the controls line shows it saying true for i32 and false for String. The three yeses are also compiled through a fn assert_copy<T: Copy>().
  • can be null — yes where Option<P> needs a word of its own. A pointer that is never null lends None the null bit pattern; Option's docs ↗ guarantee this for &T, &mut T and Box<T> with a sized T; for Rc<T> it is measured, not promised.
  • pointee may live — anywhere is one type, &i32, pointed at a local, a static and the inside of a Box. A raw pointer may also point nowhere (ptr::null()). heap for Box and Rc is what their std docs state; for Box, only for a non-zero-sized T ↗.
  • words — the handle's own size. Every one of the six grows to two words for [u8], str and dyn Display, for the reason str is unsized gives.

The refusals behind the noes

The four "no" cells of the write row, from one file:

Abridged — real rustc output for write_through.rs, help blocks trimmed
error[E0594]: cannot assign to `*r`, which is behind a `&` reference
 --> write_through.rs:7:5
  |
7 |     *r = 2;
  |     ^^^^^^ `r` is a `&` reference, so it cannot be written to

error[E0594]: cannot assign to `*p`, which is behind a `*const` pointer
  --> write_through.rs:10:14
   |
10 |     unsafe { *p = 3; }
   |              ^^^^^^ `p` is a `*const` pointer, so it cannot be written to

error[E0594]: cannot assign to `*b`, as `b` is not declared as mutable
  --> write_through.rs:13:5
   |
13 |     *b = 4;
   |     ^^^^^^ cannot assign

error[E0594]: cannot assign to data in an `Rc`
  --> write_through.rs:16:5
   |
16 |     *c = 5;
   |     ^^^^^^ cannot assign
   |
   = help: trait `DerefMut` is required to modify through a dereference, but it is not implemented for `Rc<i32>`

The &T refusal has a way round, interior mutability; the *const T one has a cast, below.

The two "yes" cells of the unsafe row:

Abridged — real rustc output for deref_raw.rs
error[E0133]: dereference of raw pointer is unsafe and requires unsafe block
 --> deref_raw.rs:4:13
  |
4 |     let v = *p;
  |             ^^ dereference of raw pointer
  |
  = note: raw pointers may be null, dangling or unaligned; they can violate aliasing rules and cause data races: all of these are undefined behavior

Making the pointer was safe; reading through it is the step that needs the block — What unsafe turns off.

The three "no" cells of the Copy row:

Abridged — real rustc output for not_copy.rs, notes trimmed
error[E0277]: the trait bound `&mut i32: Copy` is not satisfied
 --> not_copy.rs:6:19
  |
6 |     assert_copy::<&mut i32>();
  |                   ^^^^^^^^ the trait `Copy` is not implemented for `&mut i32`
  |
  = note: `Copy` is implemented for `&i32`, but not for `&mut i32`

error[E0277]: the trait bound `Box<i32>: Copy` is not satisfied
 --> not_copy.rs:7:19
  |
7 |     assert_copy::<Box<i32>>();
  |                   ^^^^^^^^ the trait `Copy` is not implemented for `Box<i32>`

error[E0277]: the trait bound `Rc<i32>: Copy` is not satisfied
 --> not_copy.rs:8:19
  |
8 |     assert_copy::<Rc<i32>>();
  |                   ^^^^^^^ the trait `Copy` is not implemented for `Rc<i32>`

Box and Rc both implement Drop — one frees the allocation, the other decrements the count — and a type with a destructor cannot be Copy at all: deriving Copy on a struct that implements Drop is E0184, "the trait Copy cannot be implemented for this type; the type has a destructor" (rustc 1.98.0). Why &mut T is not Copy is on Copy vs Clone.

The second-highest answer on Stack Overflow's Understanding and relationship between Box, ref, & and * (Mohamad-Jaafar Nehme, October 2022, edited June 2023) tabulates the same six types under Data Location, Mutable, Shared Ownership, Safe and impl Copy. Its impl Copy column matches the table above, and so does Safe, read the way its own note defines it (dereferencing needs unsafe). The rest:

  • Data Location: "stack" for &T, &mut T, *const T, *mut T. That is where a pointer variable often sits, and says nothing about the pointee — and the variable is not always there either. Section 1 of the verified output below: let r: &i32 = &*boxed; points into the Box's allocation (ptr::eq is true); a Vec<&str> keeps its references as elements of its buffer, which Vec's docs ↗ place on the heap; Box<&i32> is a reference on the heap pointing at a local. The row worth having is where the pointee may live.
  • Shared Ownership ✓ for &T, *const T and *mut T. None of them owns anything: ending each one's scope ran 0 destructors. Many &T can share access to a value; its one owner is still the binding they borrowed from.
  • &mut T ✗, because it "has only one owner". The refusal it cites, E0499 "cannot borrow x as mutable more than once at a time", is about two live exclusive borrows of x — and x is the owner throughout. A &mut T ran 0 destructors too. Exclusive access, not ownership.
  • Box<T> ✗, shown by an E0597. The example it links borrows b = Box::new(1) in an inner block and uses the borrow after the block. Change Box::new(1) to 1 and rustc prints the same error — same code, same message, same four labels:
Abridged — real rustc output for borrow_outlives_int.rs
error[E0597]: `b` does not live long enough
 --> borrow_outlives_int.rs:5:17
  |
4 |         let b = 1;
  |             - binding `b` declared here
5 |         b_ref = &b;
  |                 ^^ borrowed value does not live long enough
6 |     }
  |     - `b` dropped here while still borrowed
7 |     println!("{}", b_ref);
  |                    ----- borrow later used here

E0597 is a borrow outliving whatever it borrowed. A Box has one owner, but the evidence for that is the drop count — one Box in scope, one destructor run — not this error. - Rc<T> Mutable ✗, shown by an E0594. The refusal's own help: line names what is missing, DerefMut, and std writes through an Rc another way: Rc::get_mut while the count is 1, and Rc::make_mut ↗, which clones the value first when it is shared (section 2: a = 12, b = 2, no longer the same allocation). Several owners that all write is Rc<RefCell<T>> — Shared means read-only.

*const T and *mut T differ in more than a lint

The accepted answer on the same question (Vladimir Matveev, 2015) says the two cast into each other without restriction, so the distinction "serves mostly as a lint". The casts are real — section 3 goes *mut → cast_const() → as *mut i32 and writes through the result, no unsafe needed for the casts. Two things the compiler still holds you to: writing through a *const T is the E0594 above, and the two have different variance.

fn shorten_const<'a>(p: *const &'static str) -> *const &'a str {
    p // compiles: *const T is covariant in T
}

// fn shorten_mut<'a>(p: *mut &'static str) -> *mut &'a str {
//     p // refused: *mut T is invariant in T
// }
Abridged — real rustc output for mut_ptr_invariant.rs
error: lifetime may not live long enough
 --> mut_ptr_invariant.rs:6:5
  |
5 | fn shorten_mut<'a>(p: *mut &'static str) -> *mut &'a str {
  |                -- lifetime `'a` defined here
6 |     p
  |     ^ returning this value requires that `'a` must outlive `'static`
  |
  = note: requirement occurs because of a mutable pointer to `&str`
  = note: mutable pointers are invariant over their type parameter

A *const T may be handed a shorter lifetime; a *mut T may not, because something could be written through it that does not live long enough. The Reference's variance table ↗ lists the same, and What &'a T claims is the &/&mut version of the argument.

Box<T> is neither always one pointer wide nor only a pointer

The accepted answer also describes a Box<T> as a T that always occupies one pointer's worth of bytes. The table's words rows refute it for unsized pointees: Box<[u8]>, Box<str> and Box<dyn Display> are two words, like every other pointer to those types. The third owned form is the page on Box<str>.

And Box does something the other smart pointer here cannot: *boxed moves the value out. Section 4 moves a whole String out of a Box<String> and then two fields, one at a time, out of a Box<Pair>. The same line on an Rc is refused — the file's Box line, line 5, compiled:

Abridged — real rustc output for move_out_of_rc.rs, help blocks trimmed
error[E0507]: cannot move out of an `Rc`
 --> move_out_of_rc.rs:8:27
  |
8 |     let from_rc: String = *counted;
  |                           ^^^^^^^^ move occurs because value has type `String`, which does not implement the `Copy` trait

Noratrieb's Box Is a Unique Type ↗ opens with that partial move and is mostly about a subtler special case: the no-aliasing promise the compiler attaches to a Box argument.

The accepted answer's let y: &u32 = &*x; borrows through the box: y points at the u32 in the allocation, so the Box cannot go while y is still used. No lifetime is written anywhere, and the refusal comes anyway:

Abridged — real rustc output for drop_box_while_borrowed.rs, help block trimmed
error[E0505]: cannot move out of `x` because it is borrowed
 --> drop_box_while_borrowed.rs:4:10
  |
2 |     let x: Box<u32> = Box::new(12);
  |         - binding `x` declared here
3 |     let y: &u32 = &*x;
  |                   --- borrow of `*x` occurs here
4 |     drop(x);
  |          ^ move out of `x` occurs here
5 |     println!("{y}");
  |                - borrow later used here

== looks through the pointer; ptr::eq does not

A 2026 answer on the same question (plugwash) points out that comparing references compares what they point at. Section 5: &five == &also_five is true for two different locals holding 5, and std::ptr::eq ↗ on the same pair is false. Box::new(5) == Box::new(5) and Rc::new(5) == Rc::new(5) are true for the same reason. For Rc, the address comparison is Rc::ptr_eq ↗.

A reference is a pointer with rules

Cliffle's Learn Rust the Dangerous Way, part 2 ↗ puts it this way: to the machine a reference is a pointer, and to the programmer it is a pointer that cannot be null, must point to initialized memory, and has no pointer arithmetic — checked at compile time. Three of those are visible from safe code:

  • Not null. Option<&i32> is one word and Option<*const i32> is two (section 6, and the table's last row). Null dereference is the C side of that.
  • No arithmetic. On a *const i32 into [10, 20, 30], raw.add(1) moves the pointer and reads 20. On a &i32 there is no such method:
Abridged — real rustc output for no_arithmetic_on_references.rs
error[E0599]: no method named `add` found for reference `&i32` in the current scope
 --> no_arithmetic_on_references.rs:4:18
  |
4 |     let next = r.add(1);
  |                  ^^^ method not found in `&i32`
  |
  = help: items from traits can only be used if the trait is in scope
help: trait `Add` which provides `add` is implemented but not in scope; perhaps you want to import it
  |
1 + use std::ops::Add;
  |

Take rustc's suggestion and it compiles — as integer addition. first.add(1) with std::ops::Add in scope is 11: the pointee plus one. - The length is not implied. &[i32; 3] is one word, because the 3 is part of the type; &[i32] is two, because the length has to travel with the pointer. A C pointer to the first element carries neither — the C library's A string is bytes up to a NUL ↗ shows an array losing its length the moment it is passed to a function.

The initialized-memory rule has no safe-code demonstration: breaking it takes unsafe, and the result is undefined behaviour rather than an output.

Is Rc a smart pointer?

Yes. The Book's chapter 15 introduction ↗ calls a reference the most common kind of pointer, one that only borrows, and defines smart pointers as data structures that act like pointers while carrying extra metadata and capabilities, and in many cases owning what they point to. It lists Rc<T> among std's smart pointers, with Box<T>; an Rc's extra metadata is its count.

The verified output

Verified output of pointer_types_evidence.rs — regenerated by tools/run_examples.py, never hand-typed.

1. "Data location" is where the pointer sits, not where it points
   let r: &i32 = &*boxed;          ptr::eq(r, &*boxed) = true
   let names: Vec<&str> = ...;     ptr::eq(&names[0], names.as_ptr()) = true, capacity 2
   let on_heap: Box<&i32> = Box::new(&local);   **on_heap = 9

2. Rc<T> is writable while it is the only Rc
   Rc::get_mut, unique:            a = 2
   Rc::make_mut, shared:           a = 12, b = 2, Rc::ptr_eq = false, counts 1 and 1

3. *const T and *mut T cast into each other with no unsafe
   q.cast_const() as *mut i32, write 2:   n = 2, ptr::eq(p, back) = true

4. Moving out through a Box
   let whole: String = *boxed_string;             "moved out"
   let left = pair.left; let right = pair.right;  "left" "right"
   let y: &u32 = &*x;                             12

5. == on references compares pointees; ptr::eq compares addresses
   &five == &also_five             true
   ptr::eq(&five, &also_five)      false
   ptr::eq(&five, &five)           true
   Box::new(5) == Box::new(5)      true
   Rc::new(5) == Rc::new(5)        true

6. A reference is a pointer with rules
   unsafe { *raw.add(1) }          20   (the pointer moved)
   first.add(1), ops::Add in scope 11   (the pointee was added to)
   &[i32; 3] 1 word, &[i32] 2 words
   Option<&i32> 1 word, Option<*const i32> 2 words

Checkpoint

Predict before you open the answer. Three types the table does not list:

  1. How many words is Box<[u8; 64]>?
  2. How many words is Option<Rc<str>>?
  3. After let mut a = Rc::new(1); let b = Rc::clone(&a); drop(b);, what does Rc::get_mut(&mut a) return?
The answer

Verified output of pointer_types_checkpoint.rs — regenerated by tools/run_examples.py, never hand-typed.

1. Box<[u8; 64]>, in words
   1   (Box<[u8]> is 2: the length 64 is in the type, not in the pointer)

2. Option<Rc<str>>, in words
   2   (Rc<str> alone is 2: None costs nothing, since an Rc is never null)

3. let mut a = Rc::new(1); let b = Rc::clone(&a); drop(b); Rc::get_mut(&mut a)?
   Some(1)   (the count is back to 1, so a is unique again)

Practice

Pick the pointer. Choose one of the table's six for each, and write it: (1) the parameter of fn describe(config: …) -> String, which only reads a Config; (2) the left and right fields of a binary-tree Node whose parent owns its children and drops them with it; (3) the field through which several Pages read one Cache, when any of them — or the code that built the cache — may be dropped first; (4) the Rust declaration of C's void *memchr(const void *s, int c, size_t n), and the read of the byte it found. Make a program show each choice working, with a SAFETY comment on every unsafe block saying what makes it sound.

Solution

pointer_types_compared_kata.rs in full — pasted here by tools/run_examples.py from the file CI compiles and runs.

//! Kata solution: pick the pointer.
//!
//! 1. Only reads a config        -> `&Config`: borrows, drops nothing, and accepts
//!                                  a `Config` owned any way.
//! 2. Parent owns its children   -> `Option<Box<Node>>`: one owner, dropped with the
//!                                  parent. `Option<Node>` is E0072, recursive type
//!                                  has infinite size.
//! 3. A cache several pages hold -> `Rc<Cache>`: the last holder frees it, whoever
//!                                  that is. With `cache: &'a Cache`, dropping the
//!                                  cache while a page still reads it is E0505.
//! 4. C's memchr                 -> `*const c_void` in, `*mut c_void` out. A `&[u8]`
//!                                  parameter compiles with an `improper_ctypes`
//!                                  warning: slices have no C equivalent.
//!
//! The two errors and the warning are rustc 1.98.0 output.
//!
//!   rustc --edition 2024 pointer_types_compared_kata.rs -o /tmp/pointer_types_compared_kata && /tmp/pointer_types_compared_kata

use std::ffi::{c_int, c_void};
use std::mem::size_of;
use std::rc::Rc;

// 1. &T
struct Config {
    host: &'static str,
    retries: u32,
}

fn describe(config: &Config) -> String {
    format!("{} x{}", config.host, config.retries)
}

// 2. Box<T>
struct Node {
    value: i32,
    left: Option<Box<Node>>,
    right: Option<Box<Node>>,
}

impl Drop for Node {
    fn drop(&mut self) {
        println!("   drop node {}", self.value);
    }
}

fn sum(node: &Node) -> i32 {
    node.value + node.left.as_deref().map_or(0, sum) + node.right.as_deref().map_or(0, sum)
}

// 3. Rc<T>
struct Cache {
    statuses: Vec<(&'static str, u16)>,
}

impl Drop for Cache {
    fn drop(&mut self) {
        println!("   drop cache");
    }
}

struct Page {
    path: &'static str,
    cache: Rc<Cache>,
}

impl Page {
    fn status(&self) -> u16 {
        self.cache.statuses.iter().find(|(p, _)| *p == self.path).map_or(404, |(_, s)| *s)
    }
}

// 4. *const T / *mut T
unsafe extern "C" {
    // void *memchr(const void *s, int c, size_t n);
    fn memchr(s: *const c_void, c: c_int, n: usize) -> *mut c_void;
}

/// The index of the first `byte` in `bytes`, found by C.
fn find_byte(bytes: &[u8], byte: u8) -> Option<(usize, u8)> {
    // SAFETY: `s` and `n` come from one live slice, so memchr reads only bytes
    // that exist, and nothing writes `bytes` while it runs (it is a `&[u8]`).
    let hit = unsafe { memchr(bytes.as_ptr().cast(), c_int::from(byte), bytes.len()) };
    if hit.is_null() {
        return None; // the table's "can be null: yes"
    }
    let at: *const u8 = hit.cast_const().cast();
    // SAFETY: non-null means memchr found the byte inside `bytes[..len]`, and
    // `bytes` is still borrowed, so the allocation is live and unchanged.
    let found = unsafe { *at };
    Some((at.addr() - bytes.as_ptr().addr(), found))
}

fn main() {
    let word = size_of::<usize>();

    println!("1. &Config: one signature for any owner");
    let config = Config { host: "localhost", retries: 3 };
    let boxed = Box::new(Config { host: "boxed", retries: 1 });
    let counted = Rc::new(Config { host: "counted", retries: 2 });
    println!("   describe(&config)              -> {}", describe(&config));
    println!("   describe(&boxed)               -> {}", describe(&boxed));
    println!("   describe(&counted)             -> {}", describe(&counted));
    println!("   config.host after the call     -> {}", config.host);

    println!();
    println!("2. Option<Box<Node>>: the parent owns its children");
    let root = Node {
        value: 1,
        left: Some(Box::new(Node { value: 2, left: None, right: None })),
        right: Some(Box::new(Node { value: 3, left: None, right: None })),
    };
    println!("   sum(&root)                     -> {}", sum(&root));
    println!("   Option<Box<Node>>, words       -> {}", size_of::<Option<Box<Node>>>() / word);
    println!("   drop(root)                     -> three destructors, parent first");
    drop(root);

    println!();
    println!("3. Rc<Cache>: whoever holds it last frees it");
    let cache = Rc::new(Cache { statuses: vec![("/", 200), ("/old", 301)] });
    let home = Page { path: "/", cache: Rc::clone(&cache) };
    let old = Page { path: "/old", cache: Rc::clone(&cache) };
    println!("   holders                        -> {}", Rc::strong_count(&cache));
    drop(cache); // the builder goes first
    println!("   builder dropped, home reads    -> {}", home.status());
    drop(home);
    println!("   home dropped, old reads        -> {}", old.status());
    println!("   holders now                    -> {}", Rc::strong_count(&old.cache));
    println!("   drop(old)                      -> the last holder frees it");
    drop(old);

    println!();
    println!("4. *const c_void into C, *mut c_void back");
    let header = b"Content-Length: 42";
    println!("   find ':'                       -> {:?}", find_byte(header, b':').map(|(i, b)| (i, b as char)));
    println!("   find '#'                       -> {:?}   (memchr returned null)", find_byte(header, b'#'));
}

Verified output of pointer_types_compared_kata.rs — regenerated by tools/run_examples.py, never hand-typed.

1. &Config: one signature for any owner
   describe(&config)              -> localhost x3
   describe(&boxed)               -> boxed x1
   describe(&counted)             -> counted x2
   config.host after the call     -> localhost

2. Option<Box<Node>>: the parent owns its children
   sum(&root)                     -> 6
   Option<Box<Node>>, words       -> 1
   drop(root)                     -> three destructors, parent first
   drop node 1
   drop node 2
   drop node 3

3. Rc<Cache>: whoever holds it last frees it
   holders                        -> 3
   builder dropped, home reads    -> 200
   home dropped, old reads        -> 301
   holders now                    -> 1
   drop(old)                      -> the last holder frees it
   drop cache

4. *const c_void into C, *mut c_void back
   find ':'                       -> Some((14, ':'))
   find '#'                       -> None   (memchr returned null)

If you are coming from another language

  • C++. *const T and *mut T are const T* and T*. Box<T> is closest to std::unique_ptr<T> — one owner, freed at scope end, not copyable — with two differences the table measures or rustc enforces: a Box is never null (Option<Box<T>> is one word, so "no box" is spelled None), and using a Box after moving it is E0382 at compile time rather than a null unique_ptr at run time. Box<[T]> is two words because it carries its length, which unique_ptr<T[]> does not store. Rc<T> is std::shared_ptr<T> without the atomic count — Rust splits that into Rc and Arc — and is one word for a sized T, where a shared_ptr is two pointers (libc++, measured with clang). Then the trap: == on two unique_ptrs or shared_ptrs compares addresses; on two Boxes, Rcs or references in Rust it compares values. std::ptr::eq and Rc::ptr_eq are the C++ meaning. A Rust reference is also not a C++ reference: it is an ordinary value you can store, copy (&T) and re-point through a mut binding.
  • Python. In CPython every name is a reference to a reference-counted object, so all of Python is roughly Rc<T> — without the write restriction, which is why aliasing bugs surface at run time there. is is std::ptr::eq and == is ==, the same split section 5 prints. Python has no &mut T: nothing lets you promise that a list you passed will not be changed by someone else holding it.

See also

Sources

Understanding and relationship between Box, ref, & and * ↗ on Stack Overflow — the accepted answer (Vladimir Matveev, 2015), the table answer (Mohamad-Jaafar Nehme, 2022, edited 2023) and the 2026 answer by plugwash, read through the Stack Exchange API on 2026-09-16; the E0597 example is the one the table answer links, answer 65871083 ↗. Cliffle, Learn Rust the Dangerous Way part 2, "References Available Upon Request" ↗. Noratrieb, Box Is a Unique Type ↗ (2022). The Book, ch. 15 ↗. Every transcript on this page is a real rustc 1.98.0, edition 2024, compile of the file its title names; all three programs also ran clean under Miri.

Po polsku

Sześć typów wskaźnikowych — &T, &mut T, *const T, *mut T, Box<T> i Rc<T> — różni się w siedmiu pytaniach, a każdą komórkę tabeli wyżej wypełnia albo pomiar programu, albo prawdziwa odmowa kompilatora. Pierwsze pytanie rozstrzyga najwięcej: kto jest właścicielem. Referencje i surowe wskaźniki niczego nie posiadają — koniec ich zasięgu nie uruchomił ani jednego destruktora. Box posiada wskazywaną wartość sam, Rc wspólnie z innymi klonami i zwalnia ją dopiero przy ostatnim.

Popularna tabela ze Stack Overflow myli pojęcia. Kolumna „położenie danych: stos” opisuje, gdzie zwykle leży zmienna wskaźnikowa, a nie to, na co wskazuje: &*boxed wskazuje na stertę, a Vec<&str> trzyma referencje w buforze na stercie. „Współdzielona własność” dla &T i surowych wskaźników to pomylenie dostępu z własnością. &mut T to dostęp wyłączny, nie jedyny właściciel. Błąd E0597 pojawia się identycznie, gdy zamiast Box::new(1) pożyczymy zwykłe 1, więc niczego nie mówi o Box. A Rc da się modyfikować, dopóki jest jedynym właścicielem: Rc::get_mut albo Rc::make_mut (to drugie najpierw klonuje wartość, jeśli jest współdzielona).

Przyjęta odpowiedź też ma dwie nieścisłości. Box<str>, Box<[T]> i Box<dyn Trait> zajmują dwa słowa, nie jedno — tak jak każdy inny wskaźnik do typu bez rozmiaru. A *const T i *mut T to nie tylko „podpowiedź”: przez *const T nie da się pisać (E0594), a wariancja jest inna — *const T jest kowariantny, *mut T niezmienniczy. Do tego Box jako jedyny pozwala wyprowadzić wartość przez *boxed; to samo na Rc to E0507. Porównanie == na referencjach porównuje wartości, a adresy porównuje std::ptr::eq. Rc jest inteligentnym wskaźnikiem w sensie rozdziału 15 Księgi: zachowuje się jak wskaźnik, a dodatkowo niesie licznik.

Szukaj po polsku: wskaźniki w Ruscie · surowe wskaźniki · inteligentne wskaźniki · referencja a wskaźnik · wariancja wskaźników · rust Box vs Rc vs reference · rust *const vs *mut · rust null pointer optimization