| name | rust-syntax-ownership |
| description | Use when the user writes Rust code that triggers ownership-transfer compile errors, asks "why moved value", asks to choose between Copy / Clone / Drop, encounters E0382 / E0507 / E0509 / E0382, or needs to design APIs around ownership transfer. Prevents excessive cloning, accidental moves, Copy+Drop mistakes, partial-move confusion, and ownership patterns that defeat the borrow checker. Covers: the three ownership rules, move semantics (by-value transfer on assignment / fn arg / return), Copy trait (which types qualify, why String / Vec / Box do not), Clone trait, scope-based drop and explicit `drop()`, ownership transfer patterns (builder, into-impl, by-value method receivers), partial moves out of fields. Keywords: ownership, move, moved value, "use of moved value", E0382, E0507, E0509, "cannot move out of", Copy, Clone, Drop, "drop function", scope, "ownership transfer", "by value", partial move, "move out of borrowed content", builder pattern, into method, "do I need to clone", "why can't I use this".
|
| license | MIT |
| compatibility | Designed for Claude Code. Requires Rust 1.85+, edition 2024. |
| metadata | {"author":"OpenAEC-Foundation","version":"1.0"} |
rust-syntax-ownership
The mechanics of ownership transfer in Rust: how moves work, when Copy applies, when to call .clone(), when Drop runs, and the API patterns that consume self by value (builder, From/Into, into_inner). Partial moves out of struct fields are covered here too.
Cross-references: [[rust-core-memory-model]] (conceptual overview, big picture) [[rust-syntax-borrowing]] (the alternative to move) [[rust-errors-borrow-checker]] (E-codes in depth)
When to use this skill
- User writes code that hits a move-related compile error: E0382, E0507, E0509, E0040, E0184, E0204, E0515
- User asks "why was this value moved" or "why can't I use
x after passing it to a function"
- User wonders whether to derive
Copy, derive Clone, both, or neither
- User wants to design a fluent builder, an
Into<T> conversion, or an into_inner accessor
- User struggles with partial moves out of struct fields (
s.field moved, but s still partly usable)
- User asks "do I need to clone here" or wants to remove
.clone() calls that Clippy flagged
For the borrow alternative (sharing without taking ownership) see [[rust-syntax-borrowing]]. For the conceptual story (stack vs heap, smart pointers, Send/Sync) see [[rust-core-memory-model]].
The three ownership rules (verbatim from the Book)
- Each value in Rust has an owner.
- There can only be one owner at a time.
- When the owner goes out of scope, the value will be dropped.
ALWAYS state these three rules first when explaining any ownership scenario. Every other rule in this skill is a consequence of them.
Source: Rust Book Ch 4.1 - What is Ownership?
Quick reference table
| Situation | What happens | Compiler behaviour |
|---|
let b = a; and a: String | a is moved into b | Using a afterwards: E0382 |
let b = a; and a: i32 | a is copied into b | Both a and b remain valid |
fn take(s: String) called with take(my_string) | my_string moved into the function | Caller's my_string invalidated |
fn take(s: String) -> String { s } | Ownership returned to caller | Receiver of return value owns it |
let s = my_struct.field; (non-Copy field) | Partial move out of my_struct | my_struct cannot be used as a whole; other fields still usable |
drop(value) | Calls Drop::drop immediately, then frees | value invalidated after the call |
value.drop() | NEVER allowed; method is private | E0040 |
| Owner goes out of scope | Drop::drop runs, then memory is freed | Automatic, no syntax |
Move semantics
Assigning a non-Copy value moves ownership. The original binding is invalidated at compile time:
let s1 = String::from("hi");
let s2 = s1;
println!("{s2}");
The same rule applies to:
- Function arguments:
fn take(s: String) consumes the caller's value.
- Return values:
fn give() -> String { String::from("hi") } moves the value to the caller.
- Destructuring:
let (a, b) = pair; where pair: (String, String) moves both halves; pair is invalidated.
- Closure captures:
move || println!("{s}") moves s into the closure.
ALWAYS think of a binding as the owner, not as the storage cell. The value is moved; the heap payload (if any) is now reachable only through the new owner.
NEVER expect a moved binding to be usable. The compiler tracks moves statically; even an if-only move makes the binding unconditionally invalid on the other path unless the value is re-initialised.
Source: Rust Book Ch 4.1 - What is Ownership?
The Copy trait
Copy makes assignment a bit-by-bit duplication rather than a move. Copy is implicit, never overloadable:
let x: i32 = 5;
let y = x;
println!("{x} {y}");
Rules for Copy
- A type can implement
Copy only if all its fields are Copy.
Copy and Drop are mutually exclusive. A type that implements Drop can never be Copy (E0184).
Copy requires Clone. Every Copy type must also derive or implement Clone (E0204 if missing).
#[derive(Copy, Clone)] works only when all fields are themselves Copy. If any field is String, Vec, Box, Rc, Arc, etc., the derive fails.
Which types are Copy
| Always Copy | Never Copy |
|---|
All integers: i8 ... u128, isize, usize | String (owns heap buffer) |
Floats: f32, f64 | Vec<T> (owns heap buffer) |
bool, char | Box<T> (owns heap allocation) |
The never type ! | Rc<T>, Arc<T> (own refcount) |
Shared references &T for any T | Anything implementing Drop |
Raw pointers *const T, *mut T | Tuples / arrays containing non-Copy elements |
Function pointers fn(...) -> _ | Mutable references &mut T |
Function items (each fn's anonymous type) | Most user-defined types unless explicitly derived |
Tuples / arrays where every element is Copy | |
ALWAYS check field-by-field before deriving Copy. NEVER derive Copy on a type owning heap data.
Source: std::marker::Copy
The Clone trait
Clone is explicit and arbitrary. Calling .clone() runs user-defined code:
let a = String::from("hi");
let b = a.clone();
println!("{a} {b}");
Rules for Clone
Clone is a supertrait of Copy. Every Copy type also implements Clone.
- For a
Copy type, clone() can be (and typically is, when derived) implemented as *self.
#[derive(Clone)] requires all fields to be Clone.
- A type can implement
Clone without being Copy (the common case for heap-owning types).
Clone-but-not-Copy types
String, Vec<T>, Box<T>, HashMap<K, V>, BTreeMap<K, V>, Rc<T>, Arc<T> are all Clone.
Rc::clone(&rc) and Arc::clone(&arc) bump the refcount; they do NOT deep-copy the inner T.
Vec<T> requires T: Clone to derive Clone.
ALWAYS prefer Rc::clone(&rc) / Arc::clone(&arc) over rc.clone() in code that should make the cheap refcount-bump intent explicit. Both compile to identical code.
NEVER use .clone() to silence the borrow checker. Borrow first; clone only when you genuinely need two independent owners (Clippy lint: clippy::redundant_clone).
Source: std::clone::Clone
Decision tree: move vs borrow vs clone
Need to pass a value to a function or another binding?
|
+-- Will the original be used again afterwards?
| |
| +-- NO -> move (just pass it; ownership transfers cleanly)
| |
| +-- YES -> borrow (& for read, &mut for write)
| See [[rust-syntax-borrowing]] for details.
|
+-- The borrow checker rejected your borrow attempt?
|
+-- Step 1: restructure (scope the borrow shorter, split the data,
| return an owned value, take ownership by value).
+-- Step 2: ONLY clone when:
| - The type is cheap to clone (Copy-like, Arc<T>, small Box).
| - OR you genuinely need two independent owners.
+-- NEVER clone "just to make it compile". This is the
single most common Rust anti-pattern (Clippy: redundant_clone).
ALWAYS try borrowing before cloning. ALWAYS try restructuring before cloning. Cloning is the third choice, not the first.
Drop and the explicit drop() function
Drop runs automatically when an owner goes out of scope. This is Rust's RAII guarantee:
pub trait Drop {
fn drop(&mut self);
}
Critical rules
Drop order
- Local variables in a block: dropped in reverse declaration order (LIFO).
- Struct fields, tuple elements, array elements: dropped in declaration order (first-to-last).
- Enum variants: the active variant's fields drop in declaration order.
struct Loud(&'static str);
impl Drop for Loud {
fn drop(&mut self) { println!("drop {}", self.0); }
}
fn demo() {
let a = Loud("a");
let b = Loud("b");
}
ALWAYS rely on Drop for resource cleanup. NEVER design a type that requires the user to call a manual .close() method.
Source: std::mem::drop Source: std::ops::Drop
Ownership transfer patterns
Pattern A: Builder consuming self
A builder takes self by value, mutates, and returns Self. Each call moves the builder forward:
pub struct Request {
method: String,
url: String,
headers: Vec<(String, String)>,
}
impl Request {
pub fn new(url: impl Into<String>) -> Self {
Self { method: "GET".into(), url: url.into(), headers: vec![] }
}
pub fn method(mut self, m: impl Into<String>) -> Self {
self.method = m.into();
self
}
pub fn header(mut self, k: impl Into<String>, v: impl Into<String>) -> Self {
self.headers.push((k.into(), v.into()));
self
}
pub fn build(self) -> Request { self }
}
let r = Request::new("https://example.com")
.method("POST")
.header("accept", "application/json")
.build();
ALWAYS prefer mut self -> Self builders over &mut self -> &mut Self builders when each step is logically a fresh value, because the consuming form chains cleanly into a final build() that returns an owned Self.
Pattern B: From / Into for conversions
Implement From<X> for Y and you get Into<Y> for X for free via the blanket impl:
pub struct Celsius(pub f64);
pub struct Fahrenheit(pub f64);
impl From<Celsius> for Fahrenheit {
fn from(c: Celsius) -> Self { Fahrenheit(c.0 * 9.0 / 5.0 + 32.0) }
}
let f: Fahrenheit = Celsius(100.0).into();
let f2 = Fahrenheit::from(Celsius(0.0));
ALWAYS implement From rather than Into directly; Into is then automatic. NEVER implement both From<X> for Y and Into<Y> for X by hand; they will conflict with the blanket impl.
For conversions that can fail, use TryFrom / TryInto.
Pattern C: By-value method receiver (fn into_inner(self))
A method that consumes the receiver returns owned inner data:
pub struct Wrapper<T> { inner: T }
impl<T> Wrapper<T> {
pub fn into_inner(self) -> T { self.inner }
}
let w = Wrapper { inner: String::from("hi") };
let s: String = w.into_inner();
ALWAYS name an into_* method when it takes self by value and returns ownership. NEVER call such a method as_* (that name is reserved for cheap borrow conversions) and NEVER call it to_* (reserved for explicit owned conversions that do not consume self).
Source: Rust API Guidelines - as/to/into
Partial moves out of fields
Moving a non-Copy field out of a struct partially invalidates the struct:
struct Pair { a: String, b: String }
let p = Pair { a: "A".into(), b: "B".into() };
let a = p.a;
println!("{}", p.b);
drop(a);
After a partial move:
- The struct as a whole cannot be used (no
let q = p;, no &p, no passing by value).
- Other fields are still usable individually by name (
p.b).
- The remaining fields will still be dropped when
p goes out of scope.
Fixing partial-move blockers
When you need the field back and still want the whole struct usable, use mem::take or mem::replace:
use std::mem;
struct Buffer { data: String }
let mut buf = Buffer { data: "payload".into() };
let owned: String = mem::take(&mut buf.data);
let prev: String = mem::replace(&mut buf.data, String::from("next"));
mem::take(&mut x) returns the value at x and leaves T::default() in its place. Requires T: Default.
mem::replace(&mut x, new) returns the old value at x and stores new there. Does NOT require T: Default.
ALWAYS use mem::take / mem::replace to extract owned data from behind a &mut reference. NEVER attempt to move out of &self or &mut self directly: the compiler rejects it (E0507, E0509).
Source: std::mem::take Source: std::mem::replace
Common error codes
| Code | Trigger | Fix |
|---|
| E0382 | "use of moved value" : a binding was moved, then used | Clone before the move, restructure to borrow, or do not use afterwards |
| E0507 | "cannot move out of borrowed content" : moving through & or &mut | Use mem::take / mem::replace, clone, or redesign to take by value |
| E0509 | "cannot move out of type ... which implements Drop" | Same as E0507; Drop types cannot be partially moved out of |
| E0040 | "explicit use of destructor method" : called .drop() directly | Use the free function drop(value) |
| E0184 | "the trait Copy may not be implemented for this type ... has a destructor" | Remove Copy or remove Drop; they are mutually exclusive |
| E0204 | "the trait Copy cannot be implemented for this type" : a field is not Copy | Remove #[derive(Copy)]; keep Clone |
| E0515 | "cannot return reference to local variable" | Return an owned value instead of a reference |
Full E-code drilldown lives in [[rust-errors-borrow-checker]].
Section: Avoid these mistakes
(Full list with WHYs in references/anti-patterns.md.)
- Sprinkling
.clone() everywhere to silence the borrow checker (Clippy redundant_clone).
- Deriving
#[derive(Copy, Clone)] on a struct containing String, Vec, Box, Rc, or Arc (E0204).
- Implementing
Drop on a type derived as Copy (E0184).
- Calling
value.drop() instead of drop(value) (E0040).
- Trying to move a field out of
&self or &mut self (E0507) without mem::take / mem::replace.
- Returning a reference to a function-local value (E0515: "cannot return reference to local variable").
- Re-using a binding after it was moved into a function or another binding (E0382).
- Treating a partial move as "the whole struct is broken" : the individual unmoved fields are still usable.
Reference links
For deeper drill-downs see:
references/methods.md: trait signatures (Copy, Clone, Drop, From, Into) and free-function signatures for mem.
references/examples.md: complete working examples for moves, partial moves, builders, conversions, by-value receivers.
references/anti-patterns.md: the most common ownership mistakes with root-cause explanations and fixes.