Дивитися в дані, не володіючи ними — і описувати їхню відсутність явно
ВТФК · Системне програмування · 2026
Фокус: позичити — не означає володіти
Очікувано: Перша версія: помилка компіляції з точним рядком конфлікту. Друга: успішна збірка
| Власник | Зріз | Що містить зріз | Приклад |
|---|---|---|---|
String | &str | вказівник на початок + довжина | "літерал", s.as_str(), &s[..i] |
Vec<T> | &[T] | вказівник + довжина | &v[1..3] |
[T; N] | &[T] | вказівник + довжина | &arr[..] |
Зріз — посилання на неперервну ділянку колекції: не володіє даними, нічого не копіює і не звільняє
`&v[2..5]` — це вказівник на v[2] плюс довжина 3. Поки цей зріз живий, `v.push(60)` потребує `&mut v` і не компілюється: error[E0502]
fn first_word(s: &str) -> &str {
let bytes = s.as_bytes();
for (i, &b) in bytes.iter().enumerate() {
if b == b' ' { return &s[..i]; }
}
s
}
let v = vec![10, 20, 30, 40];
let mid = &v[1..3]; // зріз [20, 30], без копіювання данихЗрізи дозволяють працювати з частиною даних без володіння й без копіювання
fn largest(list: &[i32]) -> i32 {
let mut max = list[0];
for &item in list {
if item > max { max = item; }
}
max
}
let numbers = vec![34, 50, 25, 3101, 65];
println!("{}", largest(&numbers)); // 3101, без копіювання вектораФункція приймає зріз — працює і з Vec<i32>, і з масивом [i32; N]
Ціна — явно продумувати, хто й коли позичає дані; це і є «суворість», яку ставлять Rust у докір
NLL аналізує потік керування, а не текстову область видимості змінної
let mut v = vec![1, 2, 3];
let first = &v[0]; // незмінне запозичення починається
println!("{}", first); // останнє використання first — тут
v.push(4); // ОК під NLL: first більше не використовуєтьсяДо появи NLL цей код відмовлявся компілюватись — first текстово "жила" до кінця блоку
Детально — на ЛР5 і в парному СРС: там це інструмент, а не теорія заради теорії
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() { x } else { y }
}
let s1 = String::from("довгий рядок");
let s2 = String::from("короткий");
println!("{}", longest(&s1, &s2));Без 'a компілятор не знав би, з якого саме аргументу походить повернене посилання
Саме правило 3 дозволяє писати fn get(&self) -> &str без 'a
let mut v = vec![1, 2, 3];
for x in &v {
v.push(*x); // error[E0502]: cannot borrow `v` as mutable
// because it is also borrowed as immutable
}Iterator invalidation — класичний рантайм-баг в інших мовах — тут помилка компіляції
| Пастка | Що станеться | Як уникнути |
|---|---|---|
&mut і & до однієї змінної в одній ділянці | error[E0502] | розвести запозичення в часі; часто рятує NLL |
Повернути &str на локальну String | висяче посилання — не компілюється | повертати String або приймати вхідне посилання |
v.push() усередині for x in &v | error[E0502]: інвалідація ітератора | зібрати зміни окремо, застосувати після циклу |
Вважати, що 'a «створює» час життя | контракт не збігається з реальністю | 'a лише описує наявну область життя даних |
Option<T> (Rust)Дістати значення безпечно: match або комбінатори (map, unwrap_or, and_then)
enum Option<T> {
Some(T),
None,
}
let some_number: Option<i32> = Some(3101); // 0xC1D
let absent_number: Option<i32> = None;Option<T> — частина стандартної бібліотеки, є завжди в prelude
fn describe(x: Option<i32>) -> String {
match x {
Some(n) if n < 0 => format!("від'ємне: {}", n),
Some(n) => format!("значення: {}", n),
None => String::from("значення відсутнє"),
}
}Прибрати гілку None — і код взагалі не скомпілюється: error[E0004] non-exhaustive
Альтернатива — прапорці is_connected + is_connecting + error: дозволяє «з'єднано» і «помилка» водночас. Option — окремий випадок цього прийому (СРС 12)
У будь-який момент часу можна мати або одне змінне посилання, або будь-яку кількість незмінних — і посилання завжди мають бути дійсними.
— The Rust Programming Language, розд. 4.2 «References and Borrowing»
🥚 Найбільше число у прикладі largest — 3101, тобто 0xC1D. Функція повертає його копією, не забираючи володіння вектором; сама константа супроводжує курс від першого модуля.