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 = vcompiles and lands. if mut is the binding:let mut b = Box::new(1), as for any owned value, while a&mut Tbinding needs nomut(*exclusive += 1above). if unique isRc::get_mut↗, which returnsSomeonly while the count is 1. - Copy —
Probe<T>has an inherentis_copyin animpl<T: Copy>block and a same-named trait method returningfalse. 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 sayingtruefori32andfalseforString. The three yeses are also compiled through afn assert_copy<T: Copy>(). - can be null — yes where
Option<P>needs a word of its own. A pointer that is never null lendsNonethe null bit pattern;Option's docs ↗ guarantee this for&T,&mut TandBox<T>with a sizedT; forRc<T>it is measured, not promised. - pointee may live — anywhere is one type,
&i32, pointed at a local, astaticand the inside of aBox. A raw pointer may also point nowhere (ptr::null()). heap forBoxandRcis what their std docs state; forBox, only for a non-zero-sizedT↗. - words — the handle's own size. Every one of the six grows to two words for
[u8],stranddyn Display, for the reasonstris unsized gives.
The refusals behind the noes¶
The four "no" cells of the write row, from one file:
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:
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:
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.
What the popular table gets wrong¶
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 theBox's allocation (ptr::eqistrue); aVec<&str>keeps its references as elements of its buffer, whichVec'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 Tand*mut T. None of them owns anything: ending each one's scope ran 0 destructors. Many&Tcan 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 borrowxas mutable more than once at a time", is about two live exclusive borrows ofx— andxis the owner throughout. A&mut Tran 0 destructors too. Exclusive access, not ownership.Box<T>✗, shown by anE0597. The example it links borrowsb = Box::new(1)in an inner block and uses the borrow after the block. ChangeBox::new(1)to1and rustc prints the same error — same code, same message, same four labels:
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
// }
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:
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:
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 andOption<*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 i32into[10, 20, 30],raw.add(1)moves the pointer and reads20. On a&i32there is no such method:
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:
- How many words is
Box<[u8; 64]>? - How many words is
Option<Rc<str>>? - After
let mut a = Rc::new(1); let b = Rc::clone(&a); drop(b);, what doesRc::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 Tand*mut Tareconst T*andT*.Box<T>is closest tostd::unique_ptr<T>— one owner, freed at scope end, not copyable — with two differences the table measures or rustc enforces: aBoxis never null (Option<Box<T>>is one word, so "no box" is spelledNone), and using aBoxafter moving it isE0382at compile time rather than a nullunique_ptrat run time.Box<[T]>is two words because it carries its length, whichunique_ptr<T[]>does not store.Rc<T>isstd::shared_ptr<T>without the atomic count — Rust splits that intoRcandArc— and is one word for a sizedT, where ashared_ptris two pointers (libc++, measured with clang). Then the trap:==on twounique_ptrs orshared_ptrs compares addresses; on twoBoxes,Rcs or references in Rust it compares values.std::ptr::eqandRc::ptr_eqare 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 amutbinding. - 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.isisstd::ptr::eqand==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¶
- Stack and heap — what
letputs on the stack, and why a pointer's size is not its pointee's - Where the
&sits —*const Tis the name of a type, and*ris a place Rc: the clone that copies a pointer and Sharing across threads:Arc— the last column in depthBox— what aBoxis forstris unsized and Step 2: Some types have no size — the second word- What
unsafeturns off — the raw-pointer columns in full - Interior mutability — writing through a
&Tanyway - Null dereference — the null a reference cannot hold
- What reference explanations get wrong, run — more claims from the same thread and its neighbours, run
- Pointers — wide pointers, alignment and raw pointers, each measured in its own lesson
- What makes a pointer smart and What a smart pointer costs — the
BoxandRccolumns:Deref,Drop, and the allocations behind them pointer↗,reference↗,std::boxed↗,std::rc↗
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