Срезы и время жизни
Срезы — ссылки на часть коллекции; на их примере мягко знакомимся со временем жизни (lifetimes).
Срез (slice) — ссылка на непрерывный участок коллекции (строки или массива), которая не владеет данными, а только указывает на них.
Строковые срезы &str
Срез — это ссылка на часть данных. Строковый срез &str указывает на участок строки, не копируя его. Диапазон задаётся через [начало..конец] (конец не включается).
fn main() {
let s = String::from("привет мир");
let hello = &s[0..12]; // срез: первое слово (12 байт в UTF-8)
let world = &s[13..]; // от 13-го байта до конца
println!("{hello}|{world}");
}Вывод:
привет|мир
Срез не владеет строкой — он лишь ссылается на её часть. Поэтому он подчиняется тем же правилам заимствования: пока живёт срез, нельзя изменить исходную строку.
String против &str
Это различие сбивает с толку всех новичков. String — владеющая строка, растущая, хранится в куче. &str — заимствованный взгляд на строку, ничем не владеет. Строковые литералы вроде "привет" уже имеют тип &str.
| String | &str | |
| Владеет данными? | да | нет (заимствует) |
| Можно менять? | да (если mut) | нет |
| Откуда берётся | String::from(...) | литерал или срез |
Практическое правило: функции лучше принимать &str — тогда им можно передать и String (через &), и литерал. Так функция становится универсальнее.
fn first_word(s: &str) -> &str { // принимаем &str — самый гибкий вариант
match s.find(' ') {
Some(i) => &s[..i],
None => s,
}
}
fn main() {
let owned = String::from("привет мир");
println!("{}", first_word(&owned)); // String -> &str через &
println!("{}", first_word("один два")); // литерал — уже &str
}Вывод:
привет один
Время жизни (lifetimes) — мягкое введение
Раз срез не владеет данными, возникает вопрос: что если исходные данные удалят, а срез останется? Это была бы висячая ссылка. Чтобы такого не случилось, Rust отслеживает время жизни каждой ссылки — как долго она действительна — и гарантирует, что ссылка не переживёт свои данные.
Чаще всего компилятор выводит время жизни сам, и вы его даже не пишете. Но иногда, когда функция возвращает ссылку, ему нужна подсказка — какая из входных ссылок «связана» с результатом. Тогда появляется аннотация вида &'a str, где 'a — имя времени жизни.
// 'a говорит: результат живёт ровно столько, сколько оба входных среза
fn longest<'a>(a: &'a str, b: &'a str) -> &'a str {
if a.len() > b.len() { a } else { b }
}
fn main() {
let s1 = String::from("длинная строка");
let s2 = String::from("строка");
println!("{}", longest(&s1, &s2));
}Вывод:
длинная строка
Аннотации времени жизни ничего не меняют в рантайме — это лишь контракт для компилятора: «возвращаемая ссылка действительна не дольше, чем входные». Благодаря ему вернуть висячую ссылку невозможно. На старте достаточно понимать идею; подробности приходят с практикой.
Итог
- Срез (
&str,&[T]) — ссылка на часть коллекции, без владения. Stringвладеет строкой,&str— заимствует; функции удобнее принимать&str.- Время жизни гарантирует, что ссылка не переживёт данные; обычно выводится автоматически.