Если функция возвращает ссылку, то для нее должно быть определено время жизни. При применении аннотаций к параметрам и возвращаемому результату функции, название аннотаций указывается в угловых скобках после названия функций:
fn имя_функции<'аннотация>(параметр: &'аннотация тип_данных) -> &'аннотация тип_данных {
}
Но нам не всегда необходимо явно указывать аннотацию времени жизни. Иногда компилятор Rust сам может вывести время жизни возвращаемой ссылки. И в данном случае он опирается на три правила:
Первое правило. Каждый параметр, который представляет ссылку, имеет свою собственную аннотацию время жизни. Так, функция с одним параметром-ссылкой применяет одну аннотацию:
fn foo<'a>(x: &'a i32);
Функция с двумя параметрами-ссылками применяет две разные аннотации:
fn foo<'a, 'b>(x: &'a i32, y: &'b i32);
Функция с тремя параметрами-ссылками применяет три параметра и так далее.
Второе правило. Если функция имеет один параметр-ссылку, то его аннотация автоматически применяется к возвращаемой ссылке:
fn foo<'a>(x: &'a i32) -> &'a i32 {}Третье правило. Если функция имеет несколько параметров-ссылок, но один из них &self или &mut self,
то время жизни возвращаемой ссылки соответствует времени жизни параметра-ссылки &self. Применяется в определениях методов.
Если компилятор может применить к функции второе или третье правило, то указывать явным образом аннотацию для возвращаемой ссылки не нужно. В частности, возьмем следующий пример.
fn main(){
let username = String::from("Sam");
let checked_username = check_name(&username);
println!("username: {}", checked_username);
}
fn check_name(name: &str) -> &str {
if name == "admin" { "Tom"}
else {name}
}
В данном случае определена функция check_name(), которая проверяет имя пользователя. Если передаваемое в функцию имя соответствует
некоторому запрещенному имени, тогда функция возвращает некоторую строку по умолчанию - "Tom". Если имя корректно, тогда возвращается это имя.
Так как функция возвращает ссылку, то по идее нам надо использовать аннотации. Однако при компиляции и запуске этой программы никаких проблем не возникнет. Так как к этой функции применяются второе правило. То есть по факту в данном случае мы имеем дело со следующей функцией:
fn check_name<'a>(name: &'a str) -> &'a str {
if name == "admin" { "Tom"}
else {name}
}
То есть мы получим функции, где время возвращаемой ссылки привязано к времени жизни ссылки-параметра. Мы это можем увидеть, чуть изменив пример:
fn main(){
let checked_username;
{
let username = String::from("admin");
checked_username = check_name(&username);
}
println!("username: {}", checked_username);
}
fn check_name(name: &str) -> &str {
if name == "admin" { "Tom"}
else {name}
}
При компиляции этой программы мы получим ошибку. Поскольку ссылка check_name применяется вне области видимости ссылки username.
Причем здесь даже в функцию передается такое значение, при котором функция возвращает строковый литерал. Тем не менее даже в этом случае в реальности
время жизни возвращаемой ссылки соответствует времени жизни ссылки-параметра.
Теперь возьмем другой пример:
fn main(){
let username = String::from("Sam");
{
let default_name = String::from("Tom");
let checked_username = check_name(&username, &default_name);
println!("username: {}", checked_username);
}
}
fn check_name(name: &str, default: &str) -> &str {
if name == "admin" { default}
else {name}
}
Теперь функция check_name() принимает два параметра-ссылки. Второй параметр представляет значение по умолчанию, которое возвращается,
если переданное имя равно строке "admin". Но теперь мы столкнемся с ошибкой при компиляции:
Данное определение функции check_name() теперь будет равносильно следующей:
fn check_name<'a, 'b>(name: &'a str, default: &'b str) -> &str {
if name == "admin" { default}
else {name}
}
Каждый параметр-ссылка имеет свое время жизни, и теперь для каждого параметра-ссылки фактически будет определена своя аннотация. То есть для первого параметра условно аннотация 'a,
а для второго параметра - аннотация 'b. Поэтому к данной функции нельзя применить ни второе, ни третье правило, чтобы вывести
время жизни для возвращаемой ссылки. Поэтому компилятор при компиляции сгенерирует ошибку и укажет, что нам надо явно указать аннотацию для возвращаемой ссылки.
Рассмотрим пару распространенных ситуаций, где необходимо использовать аннотации.
При наличии параметров-ссылок время жизни возвращаемой ссылки соответствует параметру с наименьшим временем жизни.
Возьмем пример выше и применим аннотации:
fn main(){
let username = String::from("Sam");
{
let default_name = String::from("Tom");
let checked_username = check_name(&username, &default_name);
println!("username: {}", checked_username);
}
}
fn check_name<'a>(name: &'a str, default: &'a str) -> &'a str {
if name == "admin" { default}
else {name}
}
Для применения в функции определена аннотация 'a, которая применяется к параметрам и возвращаемому результату. И если мы
посмотрим на те значения,
которые передаются при вызове этой функции, а именно ссылки на переменные username и default_name, то увидим, что
время жизни переменной username больше, а у переменной default_name. В частности, время действия переменной username - вплоть до конца функции main:
fn main() {
let username = String::from("Sam");
//............
} // конец функции main - конец времени жизни username
Переменная default_name определяется во вложенном блоке кода, поэтому ее действие заканчивается с завершением этого блока кода:
fn main() {
let username = String::from("Sam");
{
let default_name = String::from("Tom");
//..........
} // конец блока кода - конец времени жизни ссылки на default_name
}
Соответственно при вызове функции check_name() время жизни возвращаемой ссылки будет соответствовать времени жизни переменной
default_name:
fn main() {
let username = String::from("Sam");
{
let default_name = String::from("Tom");
let checked_username = check_name(&username, &default_name);
println!("username: {}", checked_username);
} // ! конец времени жизни ссылки checked_username
}
Это может показаться странным, поскольку в данной программе возвращаемая ссылка очевидно использует значение переменной
username. Но для примера изменим код и попробуем использовать полученную ссылку вне блока кода:
fn main(){
let username = String::from("Sam");
let checked_username;
{
let default_name = String::from("Tom");
checked_username = check_name(&username, &default_name);
}
println!("username: {}", checked_username);
}
fn check_name<'a>(name: &'a str, default: &'a str) -> &'a str {
if name == "admin" { default}
else {name}
}
И в данном случае мы получим при компиляции ошибку, так как время жизни default_name не соответствует той области видимости, в рамках которой мы пытаемся использовать ссылку на этот объект.
Если функция не использует параметры-ссылки, то время жизни возвращаемой ссылки представляет время жизни возвращаемого значения:
fn main() {
let message = get_message();
println!("Message: {}", message);
}
fn get_message<'a>() -> &'a str {
let result = String::from("Hello Rust");
result.as_str()
}
В данном случае функция get_message() возвращает строковый слайс из объекта result. Для получения строкового слайса
&str из объекта String, у последнего вызывается метод as_str().
Но время жизни переменной result ограничено функцией
get_message(). То есть после завершения этой функции переменная result становится недействительной, и ее память очищается. Соответственно все ссылки
на этот объект становятся недействительными. Поэтому при компиляции данного кода мы получим ошибку:
При этом неважно, что к возвращаемой ссылке применяется аннотация. Мы все равно не можем в данном случае возвратить ссылку. И единственным решением в данном случае будет изменение функции, например, чтобы она возвращала не ссылку, а конкретный объект. Например:
fn get_message() -> String {
let result = String::from("Hello Rust");
result
}