- Формулировать три правила владения и объяснять разницу между перемещением и копированием
- Передавать значения в функции через ссылки
&и&mut, не забирая владение - Читать и исправлять ошибки E0382, E0499 и E0502
- Работать со срезами и правильно выбирать между
Stringи&str
У тебя есть книга. Ты можешь подарить её другу — тогда она уже не твоя. Можешь дать её почитать сразу нескольким друзьям. А можешь дать только одному другу делать в ней пометки — пока он пишет, никто другой не должен её читать, чтобы не увидеть незаконченные записи. Rust управляет памятью именно по этой логике: владение, заимствование для чтения и заимствование для изменения.
Стек, куча и правила владения
Значения, размер которых известен заранее (i32, bool, char), хранятся в быстром стеке. Данные переменного размера, например растущая строка String, лежат в динамической памяти — куче: в стеке остаются только указатель, длина и ёмкость. Кто-то должен освободить память в куче — в Rust это работа владельца.
- У каждого значения в Rust есть владелец — переменная, которая его хранит.
- В каждый момент владелец может быть только один.
- Когда владелец выходит из области видимости (кончается его блок
{ }), значение автоматически удаляется (drop), а память освобождается.
Перемещение, клонирование и Copy
Когда пишешь let s2 = s1;, содержимое String в куче не копируется — владение перемещается (move) от s1 к s2. Если бы владельцами были обе переменные, в конце блока они обе освободили бы одну и ту же память. Поэтому Rust считает s1 недействительной и запрещает её использовать. Если действительно нужна вторая копия, вызови .clone().
fn main() {
let s1 = String::from("Baku");
let s2 = s1;
println!("{s1} {s2}");
}fn main() {
let s1 = String::from("Baku");
let s2 = s1.clone();
println!("{s1} {s2}");
}Baku Baku. clone создаёт полную копию данных в куче, поэтому для больших данных это дорого.error[E0382]: borrow of moved value: `s1`
--> src/main.rs:4:15
|
2 | let s1 = String::from("Baku");
| -- move occurs because `s1` has type `String`, which does not implement the `Copy` trait
3 | let s2 = s1;
| -- value moved here
4 | println!("{s1} {s2}");
| ^^^^ value borrowed here after moveЦелые и дробные числа, bool, char и кортежи только из них — типы Copy: они целиком живут в стеке, и копировать их дёшево. Такие значения не перемещаются, а копируются — после let b = a; обе переменные рабочие. Передача аргумента в функцию подчиняется тому же правилу:
fn take(text: String) {
println!("took: {text}");
}
fn show(n: i32) {
println!("copied: {n}");
}
fn main() {
let city = String::from("Ganja");
take(city);
// println!("{city}"); // error E0382: city was moved
let year = 2024;
show(year);
println!("still have: {year}");
}took: Ganja copied: 2024 still have: 2024
take стала владельцем String и удалила её в конце. А year имеет тип i32, поэтому была передана лишь копия.Ссылки и заимствование
Каждый раз отдавать владение и забирать его обратно утомительно. Вместо этого значение можно одолжить: &name создаёт ссылку, которая не забирает владение. Функция читает значение через ссылку, а владелец после этого не меняется. Чтобы изменить значение, нужна изменяемая ссылка &mut — и сама переменная тоже должна быть mut.
fn length(text: &String) -> usize {
text.len()
}
fn add_suffix(text: &mut String) {
text.push_str(" city");
}
fn main() {
let mut name = String::from("Baku");
let len = length(&name);
println!("{name} has {len} letters");
add_suffix(&mut name);
println!("{name}");
}Baku has 4 letters Baku city
В любой момент у значения может быть либо сколько угодно ссылок &, либо ровно одна ссылка &mut — но не то и другое вместе. Кроме того, ссылки всегда должны быть действительными: ссылка не может жить дольше владельца. Ссылка «заканчивается» там, где её использовали в последний раз.
fn main() {
let mut s = String::from("hi");
let r1 = &mut s;
let r2 = &mut s;
r1.push('!');
r2.push('?');
println!("{s}");
}fn main() {
let mut s = String::from("hi");
let r1 = &mut s;
r1.push('!');
let r2 = &mut s;
r2.push('?');
println!("{s}");
}&mut «живы» одновременно (cannot borrow s as mutable more than once at a time). Справа r1 в последний раз используется в push и заканчивается до создания r2.fn main() {
let mut scores = vec![90, 85];
let first = &scores[0];
scores.push(70);
println!("{first}");
}fn main() {
let mut scores = vec![90, 85];
let first = scores[0];
scores.push(70);
println!("{first}");
}vec![...] создаёт расширяемый список (Vec). Справа first — не ссылка, а копия значения i32, поэтому список можно менять.error[E0502]: cannot borrow `scores` as mutable because it is also borrowed as immutable
--> src/main.rs:4:5
|
3 | let first = &scores[0];
| ------ immutable borrow occurs here
4 | scores.push(70);
| ^^^^^^^^^^^^^^^ mutable borrow occurs here
5 | println!("{first}");
| ------- immutable borrow later used hereСрезы: String и &str
Срез (slice) — ссылка на часть коллекции: &numbers[1..4] показывает элементы с индексами 1, 2 и 3. Текстовый срез имеет тип &str. Любой строковый литерал в программе ("hello") — тоже &str: ссылка на текст, хранящийся в самом файле программы. А String владеет своими данными, живёт в куче и может расти.
fn first_word(text: &str) -> &str {
for (i, ch) in text.char_indices() {
if ch == ' ' {
return &text[..i];
}
}
text
}
fn main() {
let sentence = String::from("hello brave world");
let word = first_word(&sentence);
println!("{word}");
println!("{}", first_word("Rust"));
let numbers = [10, 20, 30, 40, 50];
let middle = &numbers[1..4];
println!("{:?} len={}", middle, middle.len());
}hello Rust [20, 30, 40] len=3
first_word принимает и &String, и литерал: Rust автоматически превращает &String в &str. &text[..i] — часть от начала до байта i.| Свойство | String | &str |
|---|---|---|
| Владение | владеет данными | заимствует |
| Изменение | с mut может расти: push_str | только чтение |
| Создание | String::from("hi"), "hi".to_string() | "hi", &s, &s[0..2] |
| Типичное место | поля структур, сборка текста | параметры функций |
Главное
- У каждого значения один владелец; когда он выходит из области видимости, значение удаляется.
- Типы вроде
Stringпри присваивании и передаче в функцию перемещаются; типы Copy копируются. &заимствует для чтения,&mut— для изменения; владение при этом не меняется.- Одновременно может быть много
&или одна&mut; ссылка не живёт дольше владельца. &str— текстовый срез,String— владеющий растущий текст; в параметрах пиши&str.
Проверь себя
Вопросов: 10. Каждый правильный ответ приносит XP.
let a = String::from("x"); let b = a;?