🌌 Розшифровка часових міток координат — С.І.Д. підсуває тобі фрагмент, що повертає одну з двох координатних міток, і одразу тішиться нагоді пояснити: без явної анотації часу життя компілятор не знає, від якої з двох залежить результат, і радше відмовиться збирати код, ніж ризикне віддати посилання на мітку, яку вже стерли. «Отака в нього чесність, і я його за це люблю», — усміхається С.І.Д. з екрана.
Запозичення (borrowing) дозволяє функції тимчасово отримати доступ до значення через
посилання (&T — незмінне, &mut T — змінне), не забираючи володіння. Це знімає
жорсткість моделі власності з попередньої теми: більше не потрібно передавати й
повертати значення туди-сюди, щоб просто його прочитати. Але запозичення породжує
нове питання: посилання не має сенсу довше, ніж живе значення, на яке воно вказує —
інакше отримаємо класичний dangling pointer (висяче посилання), джерело
use-after-free в мовах без такого контролю. Rust вирішує це не в рантаймі, а
статично, через часи життя (lifetimes).
Найголовніше, що варто зрозуміти про анотації часу життя: 'a не встановлює,
скільки саме живе значення, і не подовжує нічиє існування. Вона лише описує
компілятору відношення між часами життя різних посилань у сигнатурі — «результат
живе не довше, ніж найкоротше з вхідних посилань» чи «ці два вхідні параметри
мають жити принаймні стільки ж, скільки». Сама тривалість життя визначається
структурою коду (областями видимості змінних) незалежно від анотацій; анотації —
це лише мова, якою функція повідомляє виклику про вже наявні обмеження.
Класичний навчальний приклад: функція fn longest(x: &str, y: &str) -> &str,
що повертає довший з двох рядкових зрізів. Без анотацій компілятор не має
підстав знати, від якого саме з двох вхідних посилань «походить» вихідне —
а без цього знання неможливо перевірити безпеку виклику (раптом викликач
звільнить y одразу після виклику, а результат насправді «походить» від y?).
Помилка компіляції в цьому випадку — missing lifetime specifier. Анотація
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str каже: «результат живе не
довше за найкоротше з x і y» — і тепер компілятор може відхилити будь-який
виклик, де результат використовується довше, ніж живе коротше з двох вхідних
посилань.
У більшості практичних випадків програміст не пише анотації часу життя
явно — за них це робить elision (правила виведення часу життя): якщо в
сигнатурі рівно один вхідний параметр-посилання, вихідне посилання неявно
отримує його час життя; якщо серед параметрів є &self, вихідне посилання
неявно отримує час життя self. Явна анотація потрібна лише тоді, коли
компілятор не може однозначно вивести зв'язок сам — як у прикладі з двома
рівноправними вхідними параметрами вище.
Часи життя можуть з'являтися і в структурах, що зберігають посилання
(struct Excerpt<'a> { part: &'a str }) — тоді компілятор гарантує, що
жоден екземпляр Excerpt не переживе значення, на яке вказує part. Це та
сама ідея, поширена з функцій на дані: посилання всередині структури не може
«пережити» оригінал. Головний висновок педагогічно важливий: часи життя — це
не окрема, «важка» тема поверх звичайних типів, а формалізація того, що будь-яка
інша мова з ручними вказівниками просто залишає на відповідальність
програміста, часто помилково.
Прочитати розділ «Validating References with Lifetimes» у The Rust Book; спробувати скомпілювати функцію, що повертає одне з двох вхідних посилань, спершу без анотації 'a, потім з нею, і зафіксувати обидва повідомлення компілятора.
Що саме описує анотація 'a у сигнатурі fn f<'a>(x: &'a str) -> &'a str — тривалість існування самого значення чи зв'язок між часами життя різних посилань?
fn last_signal<'a>(freq: &'a str, _noise: &str) -> &'a str — результат походить лише від першого параметра, тому другий обійшовся без 'a, і Прикордонник пропустив її без питань. Єдиний виклик у їхньому тесті: last_signal("101.9", "ICE"). С.І.Д. каже, що це посилання живе рівно стільки, скільки живе частота, — а вона, за його словами, мовчить якось не так.Кожна команда терміналу з цієї сторінки — одним реченням. Позначка нове — команда зустрічається в курсі вперше; далі вважаємо її знайомою.
cargo buildtarget/debug/; --release — з оптимізаціями у target/release/.cargo💡 Прочитати README прямо в терміналі: cat README.md (виведе весь файл) або less README.md (посторінково; вихід — клавіша q).
Довідка з будь-якої команди: man curl (повний посібник, вихід — q) або коротко curl --help; для підкоманд — docker run --help, cargo build --help.