Хто власник значення
VTFK · 2026
Фокус: одне значення — один власник, завжди
Ці три правила компілятор перевіряє статично, без збирача сміття в рантаймі
let x = 3101; // 0xC1D, i32 — живе на стеку
let y = x; // копія: обидва x і y валідні
println!("{} {}", x, y); // працює: i32 реалізує трейт Copy
// прості типи фіксованого розміру на стеку (i32, bool, char, f64, кортежі з них)
// копіюються автоматично — тут немає moveCopy-типи — виняток із загального правила move
let s1 = String::from("hello");
let s2 = s1; // власність переходить від s1 до s2
println!("{}", s1); // error[E0382]: value borrowed here after move
// ^ s1 більше не власник — компілятор це знає і не дає скомпілюватиerror[E0382]: use of moved value
Копіюється лише заголовок (ptr/len/cap = 24 байти); буфер "hello" на купі не рухається, а s1 стає недійсним. Copy-типи на кшталт i32 — виняток: у них на купі нічого нема
Rust робить дешеву операцію типовою, а дорогу — явною: читач бачить кожну алокацію
Move не пропорційний розміру даних — саме тому він може бути типовою поведінкою
let s1 = String::from("hello");
let s2 = s1.clone(); // глибока копія: нова ділянка пам'яті
println!("{} {}", s1, s2); // ОК: обидві змінні валідні й незалежніClone видно в коді — це свідомий вибір, а не прихована вартість
Передавати й повертати власність через кожну функцію незручно — тому в Л6 з'явиться запозичення
fn takes_ownership(s: String) -> String {
println!("отримав: {}", s);
s // повертаємо власність назад викликачу
}
let s1 = String::from("дані");
let s1 = takes_ownership(s1); // власність пішла у функцію й повернулась
println!("{}", s1); // s1 знову валіднийПостійно передавати й повертати власність незручно — саме тому в Л6 з'явиться запозичення (borrowing)
Очікувано: Перша версія не компілюється з чіткою вказівкою рядка й причини; виправлена версія збирається і виконується
| Тип | Copy? | Присвоєння let b = a | Чому |
|---|---|---|---|
i32, f64, bool, char | так | побітова копія, a валідне | фіксований розмір, повністю на стеку |
кортеж (i32, f64) | так | побітова копія | усі елементи — Copy |
масив [u8; 4] | так | побітова копія | усі елементи — Copy |
String, Vec<T>, Box<T> | ні | move, a недійсне | володіють буфером на купі |
struct з полем String | ні | move | хоч одне поле не Copy |
struct з #[derive(Copy)] | так | побітова копія | усі поля Copy і немає Drop |
Тип не може одночасно реалізовувати Copy і мати власну реалізацію Drop
| Ознака | Copy | Clone |
|---|---|---|
| Викликається | неявно, при присвоєнні | явно, методом .clone() |
| Вартість | завжди дешева: побітова копія | будь-яка: глибоке копіювання |
| Хто може реалізувати | типи без ресурсів на купі й без Drop | практично будь-який тип |
| Приклади | i32, bool, (f64, f64) | String, Vec<T>, більшість структур |
| Зв'язок | Copy вимагає Clone | Clone не вимагає Copy |
`#[derive(Clone, Copy)]` працює, лише якщо всі поля структури самі реалізують ці трейти
#[derive(Debug, Clone, Copy)]
struct Point {
x: i32,
y: i32,
}
let p1 = Point { x: 1, y: 2 };
let p2 = p1; // Copy: побітова копія, p1 і далі валідний
println!("{:?} {:?}", p1, p2);derive працює, лише якщо всі поля структури самі реалізують Copy
struct Runner {
name: String,
level: u32, // u32 реалізує Copy
}
let r = Runner { name: String::from("ранер"), level: 3 };
let name = r.name; // часткове переміщення поля name
println!("{}", r.level); // ОК: level — Copy, не було переміщене
// println!("{}", r.name); // error: r.name вже переміщеноКомпілятор дозволяє доступ до непереміщених полів навіть після часткового move
std::mem::drop(x)Без збирача сміття і без ручного free()/close() у кожному місці виходу з функції
struct Guard {
name: String,
}
impl Drop for Guard {
fn drop(&mut self) {
println!("звільняю ресурс: {}", self.name);
}
}
fn main() {
let _g = Guard { name: String::from("з'єднання") };
println!("працюємо...");
} // тут автоматично: "звільняю ресурс: з'єднання"Звільнення ресурсу детерміноване й гарантоване компілятором, без ручного close()
| Пастка | Що станеться | Як уникнути |
|---|---|---|
Очікувати, що s2 = s1 копіює String | це move: s1 стає недійсним | .clone(), якщо потрібні дві копії |
#[derive(Copy)] на структурі з String | не скомпілюється | Copy — лише коли всі поля Copy |
Викликати x.drop() вручну | заборонено компілятором | std::mem::drop(x) |
Часткове переміщення, потім {:?} структури | error: use of partially moved value | перемістити поле в останню чергу або .clone() |
| Повертати власність з функції, що лише читає | багатослівний код | запозичення &T — тема Л6 |
Володіння — найунікальніша риса Rust, і вона має глибокі наслідки для всієї мови: саме воно дає гарантії безпеки пам'яті без збирача сміття.
— The Rust Programming Language, розд. 4 «Understanding Ownership»
🥚 У прикладі Copy змінна x дорівнює 3101 — 0xC1D. Побітова копія такого i32 коштує рівно 4 байти; сама константа в матеріалах курсу з'являється не вперше.