Rust’s ownership system governs who is responsible for a value and how other parts of a program may access it. It is a foundation for memory safety without a mandatory tracing garbage collector. The compiler checks how ownership moves, how references borrow data, and whether accesses can overlap safely; it rejects many programs that could otherwise use invalid pointers or cause unsynchronized mutations.
This lesson follows OSRust.005: Control Flow, the earlier functions and return values lesson, and variables, scope and mutability. The two previously published OSRust.005 articles share an identifier and both cover control flow; this new lesson takes the next distinct topic instead of repeating those articles.
Learning objectives
- State Rust’s three ownership rules and distinguish the lifetime of a value from the scope of a named binding.
- Explain the difference between a move, a
Copyoperation and an explicitclone(). - Use
&T,&mut Tand&strreferences when passing values to functions. - Explain why overlapping shared and mutable access is restricted and how a borrow may end after its last use.
- Recognize common compiler diagnostics such as
E0382,E0499,E0502andE0515. - Read and construct a small data-processing example without cloning values unnecessarily.
What ownership means in Rust
The official Rust Book: What Is Ownership? presents three central rules: each value has an owner; one owner controls a value at a time; and when the owner goes out of scope, the value is dropped. A drop may release heap allocation or another managed resource. Ownership is not an instruction to manually free every piece of memory; the compiler checks the rules and generates the appropriate cleanup behavior.
The rules apply to values and are not merely an assertion about where bytes physically reside. An integer usually has an inexpensive value representation; an owned String manages a buffer that can be allocated on the heap. Multiple references can view one owned value without becoming owners themselves. Advanced abstractions such as Rc and Arc express shared ownership of a separate allocation, but do not invalidate the basic ownership model.
Stack and heap are useful mental models, not a guarantee of every physical placement in optimized machine code. The stack commonly stores fixed-size local values, while the heap supports allocations with runtime-determined sizes. A String value contains metadata including a pointer, length and capacity; its character buffer is allocated separately as required. Rust controls who is responsible for releasing that allocation.
Moves versus copies
Consider let first = String::from("node"); let second = first;. Because String is not Copy, the assignment moves the value to second. The heap buffer is not automatically duplicated, and the binding first can no longer be used to access that moved value. Trying println!("{first}"); afterward causes a compiler error described by E0382: use of a moved value.
fn main() {
let first = String::from("node");
let second = first;
println!("{second}"); // Works: second now owns the String.
// println!("{first}"); // E0382 if uncommented.
}
Video: Learning Podcasts — Rust Ownership and Move Semantics Explained (2026). Demonstrates ownership transfer, Copy, Clone and move-related compiler errors.
u32, bool and many other simple types implement the Copy trait. An assignment of a Copy value makes a separate copy and leaves the source usable. Rust does not permit types requiring custom Drop behavior to also implement Copy. Whether an operation is copying or moving is determined by the type’s traits, not just by a variable’s visual spelling.
fn main() {
let count: u32 = 7;
let another_count = count; // Copy, not move.
println!("{count} and {another_count}");
}
Use clone() when a type supports explicit cloning and a separate value is genuinely needed. For a String, cloning copies the string content into separately owned storage; it can therefore consume additional time and memory. For other types, the cost and semantics of Clone depend on the implementation. Cloning simply to silence a borrow-checker error can hide an inefficient API design.
| Operation | Example | Effect |
|---|---|---|
| Move | let b = a; for String | a becomes unusable; b owns the value |
| Copy | let b = a; for u32 | Both bindings remain usable and have their own copied value |
| Clone | let b = a.clone(); | Explicit operation whose cost depends on the type; separate String storage |
| Shared borrow | let r = &a; | Temporary read-only access; ownership remains with a |
| Mutable borrow | let r = &mut a; | Temporary exclusive mutable access; requires appropriate mutable binding |
Video study: ownership, scope and moves
Let’s Get Rusty’s ownership walkthrough follows the Rust Book and covers stack/heap at approximately 4:30, the three ownership rules at 6:53, memory and allocation at 8:22, function ownership at 10:32, and references and borrowing around 12:23. This is an English-language instructional video, not a benchmark or compiler specification; the linked official documentation governs language behavior.
Video: Let’s Get Rusty — Understanding Ownership in Rust (2021). Chapters cover moves, allocation, function boundaries and borrowing. This is the first distinct player in the lesson.
References let functions use a value without owning it
A reference borrows access to an existing value. In fn inspect(text: &str), the parameter points to borrowed string data without receiving ownership of the original String. Calling inspect(&status) leaves status usable afterward. A function can usually accept &str rather than &String when it only needs string content, allowing callers to pass either string literals or borrowed string data. See the Rust Book’s References and Borrowing chapter.
fn inspect(text: &str) {
println!("Status: {text}");
}
fn main() {
let status = String::from("ASIC online");
inspect(&status);
println!("Still owned here: {status}");
}
A reference is not the same thing as a second owner. A shared reference &T normally permits reading; a mutable reference &mut T permits modification while satisfying Rust’s exclusivity requirements. Use the narrowest permission a function needs. For instance, a validator accepting &str should not require &mut String when no mutation is necessary.
Shared borrowing and mutable borrowing
The central safe-Rust aliasing rule is commonly summarized as either multiple shared references or one active mutable reference to the same value, but not overlapping shared and mutable accesses that violate the borrowing rules. A mutable borrow helps prevent mutation while another active reference expects a stable view. This is relevant to data races, iterator invalidation and the lifetime of references into resizable data structures.
Rust uses non-lexical lifetimes to determine that an immutable borrow can end after its last use, even if the surrounding brace-enclosed scope has not ended. In the next valid example, the two shared references are last used in println! before the exclusive mutable reference is created.
fn main() {
let mut s = String::from("online");
let first = &s;
let second = &s;
println!("{first}, {second}");
let exclusive = &mut s; // Shared borrows no longer active.
exclusive.push_str(" | checked");
println!("{exclusive}");
}
Video: DigiKey — Intro to Embedded Rust, Part 4: Ownership and Borrowing (2026). Practical demonstration of immutable and mutable borrows, lifetimes and non-overlapping mutable slices.
A conflicting program like the one below is rejected when the immutable borrow remains active across a mutable borrow. It is intentionally invalid: E0502 reports borrowing as mutable while already borrowed as immutable. Separately, creating two simultaneously active mutable borrows can produce E0499. The point is to reorganize accesses into safe phases, not automatically clone every string.
fn main() {
let mut s = String::from("online");
let read = &s;
let write = &mut s; // E0502: conflict while read remains active.
println!("{read}");
write.push_str(" now");
}
// Intentionally DOES NOT COMPILE.
Video study: explaining why the borrow checker matters
Gary Explains’ separate English walkthrough examines ownership and borrowing and gives a practical way to reason about when a value is moved and when a function can temporarily refer to it. Watch the two videos comparatively: the same core restrictions should lead to the same predictions even when the instructors use different analogies.
Video: Gary Explains — Rust: What is Ownership and Borrowing? (2020). Second distinct English tutorial, focused on the relationship between memory safety, moved values and references.
Dangling references and valid lifetimes
A dangling reference is a pointer-like reference to data that no longer exists. Rust’s borrow checking rejects safe code that attempts to return a reference to a local value dropped at the end of a function. An example can fail with E0515: a reference to a local variable was returned. A function may instead return an owned String when the caller must retain the result, or borrow data from an input whose lifetime supports the returned reference.
fn make_status() -> String {
String::from("ready") // Ownership moves to the caller.
}
fn main() {
let status = make_status();
println!("{status}");
}
The lifetime system reasons about the validity of references. It is not a guarantee that any arbitrary integer or FFI raw pointer can be safely dereferenced. Rust’s unsafe features and external C interfaces permit operations that require additional developer-established invariants. Ownership and borrowing prevent major classes of mistakes in safe Rust; they do not remove the need for tests, input validation or resource management.
Complete compiling example: ownership through a function
The following complete program demonstrates borrowing a string to print it, changing it through an exclusive mutable reference, cloning it, moving the original into a new binding, and copying a small integer. These are distinct operations: a successful call accepting &str should not invalidate the original string, while a move of the String does.
fn inspect(s: &str) {
println!("Inspect: {s}");
}
fn append_note(s: &mut String) {
s.push_str(" | inspected");
}
fn main() {
let mut status = String::from("ASIC online");
inspect(&status); // Borrow shared, no ownership transfer.
append_note(&mut status); // Borrow mutably after shared use ends.
println!("Updated: {status}");
let first = status.clone(); // Deliberate separate String.
println!("Cloned: {first}");
let moved = status; // Ownership transfers to moved.
println!("New owner: {moved}");
let count = 3_u32;
let copy = count; // u32 implements Copy.
println!("Counters: {count}, {copy}");
}
Expected standard output:
Inspect: ASIC online
Updated: ASIC online | inspected
Cloned: ASIC online | inspected
New owner: ASIC online | inspected
Counters: 3, 3
Save this as main.rs. With Rust installed, run rustc --edition 2021 main.rs && ./main in a compatible Unix-like shell, or use cargo run inside a Cargo binary project. Rust’s Cargo introduction explains the normal project workflow. The example is written for stable Rust; its output has been logically checked against the language rules, but execution has not been performed in this environment.
Applied example: borrowing a telemetry sample collection
Consider a fixed list of sample temperatures or diagnostic values received from an ASIC monitoring service. The function count_valid borrows a slice of readings instead of taking ownership of the collection, whereas record borrows a vector mutably to append a new reading. A negative value is simply a teaching sentinel for missing data; the code does not implement real miner thermal protection or any safety-critical device control.
fn count_valid(readings: &[i16]) -> usize {
readings.iter().filter(|&&v| v >= 0).count()
}
fn record(readings: &mut Vec<i16>, new_value: i16) {
readings.push(new_value);
}
fn main() {
let mut samples = vec![42, 44, -1];
println!("Valid before: {}", count_valid(&samples));
record(&mut samples, 46);
println!("Valid after: {}", count_valid(&samples));
println!("All readings: {samples:?}");
}
Expected output:
Valid before: 2
Valid after: 3
All readings: [42, 44, -1, 46]
The two reads borrow samples immutably only while count_valid uses it. The middle function takes an exclusive mutable borrow for push; afterward the collection remains available for a later shared borrow and a final debug print. No unnecessary Vec cloning is required. This is an example of the same general principle discussed in our hashboard sensor diagnostics and C iteration lessons, although each language has its own memory rules.
Common compiler mistakes and corrections
- Use after move: using a moved
Stringtriggers E0382. Borrow it, return ownership, or deliberately clone when you need another owned copy. - Read/write overlap: if an immutable reference is used after a mutable borrow is created, restructure the phases so the first borrow is no longer active.
- Two mutable borrows: avoid simultaneously active exclusive references to the same value; split work into separate scopes or use an appropriate safe API.
- Borrowing a temporary too long: a reference cannot outlive the value it refers to. Return an owned value or borrow from an input with a suitable lifetime.
- Overcloning: using
clone()reflexively may allocate and copy more than necessary; often a read-only reference is clearer. - Mixing
Stringand&str:Stringowns growable text;&stris a borrowed view into UTF-8 string data. - Confusing a safe borrow with guaranteed concurrency: shared reading has rules, but actual multithreaded synchronization and
Send/Syncbelong to later topics.
Glossary
- Owner: the binding or value responsible for the lifetime and cleanup of an owned resource.
- Move: transfer of a non-
Copyvalue that makes its previous binding unusable for that value. - Copy: implicit duplication for types implementing the
Copytrait. - Clone: explicit creation of another value by the type’s
Cloneimplementation. - Borrow: temporary access through a reference without transferring ownership.
- Mutable reference: a reference granting appropriately exclusive write access to data.
- Scope: a region where a name and various values are available; borrow liveness may end before the textual scope’s final brace.
- Drop: cleanup performed when an owned value’s lifetime ends.
- Dangling reference: a reference to data no longer valid; safe Rust forbids constructing one that escapes its lifetime.
Knowledge check
- Why does assigning one
Stringto another variable generally invalidate the source binding, while assigning au32does not? - Can two
&Stringreferences exist at once, and under what conditions can an&mut Stringbe used afterward? - Does passing
&statusto a function accepting&strtransfer ownership? - What does
clone()do for aString, and why might it cost memory? - Why would returning a reference to a newly created local
Stringbe invalid? - How does the telemetry example mutate a vector without transferring ownership to the helper function?
Answer guide: A String is non-Copy and moves, while u32 is Copy. Shared references can coexist, followed by an exclusive borrow once those shared references are no longer in use. &str access does not own the string. Cloning a String creates separately owned contents. A local’s storage is destroyed before a returned reference could be used. A &mut Vec temporarily borrows the vector, leaving it owned by the caller.
References and next lesson
Primary language references: Rust Book — What Is Ownership?; Rust Book — References and Borrowing; Rust compiler E0382; Rust compiler E0502; Rust compiler E0515; Rust Book — The Slice Type. The next Rust lesson can develop slices and UTF-8 borrowing in greater detail without repeating today’s introductory ownership examples.
Editor’s Note: We volunteer daily to ensure the credibility of the information on this platform is Verifiably True. If you would like to support our work and help further secure the integrity of our research initiatives, please scan the QR code below to donate 5,000 sats.

For mobile scanning, open the lesson on a larger screen and scan the QR code with a compatible Bitcoin wallet. Verify the destination address before sending: 3C9o19EH5HSiwEPyCTmEKzxhNCbo2X6TTb.
BitcoinVersus.tech is not a financial advisor. This media platform reports on financial subjects purely for informational purposes.

Leave a Reply