Ownership Model

On this page

For a first reading: work through the opening minimal examples for move, Copy/Clone, and Drop. Identify who may still use each value and who releases it, then continue to references and borrowing. Revisit shared pointers, closure capture, partial moves, and detailed destruction order later.

Before reading: be comfortable with variables and functions and able to run a Cargo example. After reading: explain who owns a resource after a move and distinguish Copy, Clone and Drop. New to Rust? Start with the beginner learning path.

Ownership answers "who is responsible for this value, when can it be handed off, and how is it finally cleaned up." Rust combines movement, borrow checking, and type encapsulation to statically prevent double frees and dangling accesses; however, the resource itself may still incur runtime costs for allocation, reference counting, or destruction.

When first learning, you can start by looking at a String that owns a resource: on the normal path, its resource responsibility moves with the owner and is finally cleaned up by the still-valid owner. Rc/Arc allow multiple handles to share resources, and each handle itself is governed by standard ownership rules, so the statement "there is always only one owner of any resource at all levels" is not accurate.

Move Transfers Availability and Resource Responsibility

Before move: s1 owns StringResponsible for final buffer releaseSame byte bufferhelloAfter move: s2 owns Strings1 can no longer be used as an initialized valueMove transfers resource responsibility without copying the string bufferSemantic illustration; does not mandate variables must be on the stack, nor require machine code to actually copy a fixed number of words. Move transfers responsibility Move transfers responsibility Before: s1 owns String s1 is responsible for cleanup Move ownership After: s2 owns String s1 is no longer initialized s2 points to the buffer The same byte buffer hello; the buffer is not cloned Semantic model only: no requiredstacklocation or fixed machine-level byte copy.
fn consume(text: String) -> usize { text.len() }
fn main() {
    let first = String::from("hello");
    let second = first;
    assert_eq!(consume(second), 5);
    // first has been moved; second is then handed to consume, neither can be read again.
}

Assignment, pass-by-value, return values, and pass-by-value destructuring can all trigger moves. After a move, the original location cannot continue to be used as an initialized value, but it can be reassigned if allowed. The compiler does not need to write a "invalidation flag" at runtime every time; concrete implementations can eliminate the copy, while certain control flows may need to track whether destruction is required.

The String buffer is not cloned by a simple move. This conclusion comes from the semantics of types and operations; it cannot be simplified to "bitwise copy on the stack is always safe": the stack may also hold handles to non-duplicable resources.

Interfaces taking ownership can also return resources on failure

fn admit(text: String, max_bytes: usize) -> Result<String, String> {
    if text.len() <= max_bytes { Ok(text) } else { Err(text) }
}
fn main() {
    let input = String::from("message");
    let mut returned = admit(input, 3).unwrap_err();
    assert_eq!(returned, "message");
    returned.truncate(3);
    assert_eq!(admit(returned, 3).unwrap(), "mes");
}

After the first call, input has been moved, but the returned Err hands the ownership of the same string back to the caller. Failure does not mean the value must be discarded, nor does it mean the original variable automatically becomes usable again; whether a resource can be retried depends on whether the error type retained it. Real-world interfaces might use Rejected { input, reason } to return both the input and the error reason.

Parameter or Return FormWhat Caller Hands OverCommon Use Case
fn inspect(value: &T)Temporary shared accessRead content, retain original value
fn update(value: &mut T)Temporary exclusive accessModify valid state in place
fn consume(value: T)Ownership of the valueSave, transfer, or finally clean up
Result<T, (T, E)>Return a T in both success and failure branchesNeed to retain retryable resources

Design interfaces based on these behaviors first, then decide if clone is needed. Cloning everything "just in case it's needed after the call" might mask APIs that only require borrowing, or inadvertently create shared state instead of independent snapshots.

Copy is Implicit Copy Capability, Clone is an Explicit Operation

OperationCan Original Variable Still Be Used?Can Custom Logic Be Executed?
Move of non-Copy valueUsually cannot continue reading the moved partDoes not invoke Clone
Assignment of Copy typeYesCopy itself does not execute custom copy methods
value.clone()Usually yesCopy and sharing behavior determined by Clone implementation

User-defined structs do not automatically implement Copy just because their fields are Copy; it requires explicit implementation or derive, satisfying field constraints. Copy requires Clone and cannot be used on types implementing Drop. Shared references &T are Copy, mutable references &mut T are not, to prevent arbitrary copying of exclusive access rights. Copy

Clone does not mean deep copy. String::clone typically creates an independent buffer; Arc::clone creates a new owner pointing to the same resource. Deriving Clone calls Clone on fields, so Arcs inside remain shared. Clone

use std::rc::Rc;
fn main() {
    let original = String::from("a");
    let mut independent = original.clone();
    independent.push('b');
    assert_eq!(original, "a");
    let shared = Rc::new(original);
    let another = Rc::clone(&shared);
    assert!(Rc::ptr_eq(&shared, &another));
}

What moves, and what gets cloned?

Separate binding usability from resource identity. A move changes the responsible binding, String::clone adds independent text, and Rc::clone adds a shared owner. Arrows denote ownership, not borrowing or particular machine code.

Preparing the visual
What moves, and what gets cloned?

Moving invalidates the old binding; String cloning creates an independent buffer, while Rc cloning shares it.

Closure Capture Depends on Usage Mode

Non-Copy does not necessarily mean it will be moved by the closure. Closures reading a Vec typically share-borrow it; when modification is needed, it might require exclusive borrowing, and taking ownership is only required when consuming the value. The move keyword requires capturing by value, but moving a reference only transfers the reference itself, not extending the lifetime of the referenced object. Closure Capture

fn main() {
    let values = vec![1, 2, 3];
    let read = || values.len();
    assert_eq!(values[0], 1); // Closure only borrows read-only, external can also read
    assert_eq!(read(), 3);
    let owned = move || values.len();
    assert_eq!(owned(), 3);
    assert_eq!(owned(), 3); // move capture does not mean it can only be called once
}

Whether a closure implements Fn, FnMut, or FnOnce depends on how the captured values are used during invocation, which is a different issue from whether move is written.

Partial Moves and Re-establishing Complete Values

fn main() {
    let mut pair = (String::from("left"), String::from("right"));
    let left = pair.0;
    assert_eq!(pair.1, "right"); // Unmoved fields are still accessible
    pair.0 = String::from("new");
    assert_eq!(pair, (String::from("new"), String::from("right")));
    assert_eq!(left, "left");
}

After partially moving fields, the whole cannot be used as a complete value; it can be restored by filling in that field again. For structs that implement Drop themselves, ordinary code cannot arbitrarily move out non-Copy fields because their destructor still requires a valid self. Common practices include using mem::replace, mem::take, or Option::take to leave a valid alternative value.

Extracting fields from objects implementing Drop requires leaving a destructible state

use std::{cell::Cell, rc::Rc};
struct Package {
    payload: Option<String>,
    released: Rc<Cell<usize>>,
}
impl Drop for Package {
    fn drop(&mut self) {
        self.released.set(self.released.get() + 1);
    }
}
fn main() {
    let released = Rc::new(Cell::new(0));
    let mut package = Package {
        payload: Some(String::from("data")),
        released: Rc::clone(&released),
    };
    let text = package.payload.take().unwrap();
    assert!(package.payload.is_none());
    drop(package);
    assert_eq!(released.get(), 1);
    assert_eq!(text, "data");
}

Option::take replaces the field with None and hands over the original Some content; Package's Drop still faces a complete value, while the moved String survives independently. If the business logic does not allow empty states, you should use other valid alternative values, or design methods that consume the entire object, rather than changing invariants to arbitrary nullable just to bypass errors.

mem::take leaves a replacement value using Default, while mem::replace uses a specified new value. They transfer the old value; the old value is not immediately destroyed just because it was "taken"; old and new resources are subsequently managed by their respective owners. mem::take

Drop Order and Boundaries

Ordinary local variables are released in reverse order of declaration when exiting their destruction scope; struct fields are released in declaration order, and the struct's own Drop method executes first. Array elements are released from front to back. Temporaries, patterns, and control flow have finer-grained scope rules; you cannot apply a single "reverse order" rule to all objects. Destruction Rules

Explicit drop(value) hands the value to a pass-by-value parameter, thus releasing it early; drop(&value) only handles a reference and does not release the original resource. Borrow ending early does not mean the owner's Drop executes early.

Local Declaration: earlier → groupScope end processes group firstgroup Fields: first → secondFirst group.drop, then first, then secondComplete Event Sequencegroup → first → second → earlierReverse order of locals and forward order of fields can appear simultaneouslyThis example has no moved fields or early drops; other control flows should be analyzed one by one based on initialized values. Two levels of Drop order Two levels of Drop order Locals: earlier → group Handle group first on scope exit Reverse local order Fields: first → second Run group.drop, then its fields Declared field order Complete event order group → first → second → earlier No fields are moved or droppedearly here.Other control flowmust track initializedvalues.

Verify Order Using Destruction Events, Not Guessing Stack Layout

use std::{cell::RefCell, rc::Rc};
type Log = Rc<RefCell<Vec<&'static str>>>;
struct Item(&'static str, Log);
impl Drop for Item {
    fn drop(&mut self) { self.1.borrow_mut().push(self.0); }
}
struct Group { first: Item, second: Item, log: Log }
impl Drop for Group {
    fn drop(&mut self) {
        assert_eq!(self.first.0, "first");
        assert_eq!(self.second.0, "second");
        self.log.borrow_mut().push("group");
    }
}
fn main() {
    let log = Rc::new(RefCell::new(Vec::new()));
    {
        let _earlier = Item("earlier", Rc::clone(&log));
        let _group = Group {
            first: Item("first", Rc::clone(&log)),
            second: Item("second", Rc::clone(&log)),
            log: Rc::clone(&log),
        };
    }
    assert_eq!(*log.borrow(), ["group", "first", "second", "earlier"]);
}

_group is a named binding; the leading underscore only suppresses the unused binding warning, but it still holds the value until the scope ends. It is different from the wildcard pattern _: let _ = construct_resource(); does not create such an owner binding; temporary resources are usually released at the end of that statement; lock guards especially should not rely on this pattern to persist.

Group::drop runs while fields are still valid; after the method ends, Rust recursively destructs the fields, requiring no manual repeated release of first/second. Here, RefCell is used to record events for observation; real-world Drop implementations should generally avoid adding work that might fail, especially paths that could panic again during unwinding. Destruction Rules

Normal exit and panic unwinding follow applicable destruction rules, but mem::forget, reference cycles, process termination, or abort may prevent cleanup. Rust allows memory leaks, so safe abstractions cannot rely on destruction definitely executing to avoid UB. The key point provided by ownership is safe access and responsibility transfer, not a promise that all resources are reclaimed on all termination paths.

Continue reading References and Borrowing to understand how to use values without taking ownership.