Если функция возвращает ссылку, то для нее должно быть определено время жизни. При применении аннотаций к параметрам и возвращаемому результату функции, название аннотаций указывается в угловых скобках после названия функций:
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 }