Аннотации ссылок в функциях

Последнее обновление: 26.04.2021

Если функция возвращает ссылку, то для нее должно быть определено время жизни. При применении аннотаций к параметрам и возвращаемому результату функции, название аннотаций указывается в угловых скобках после названия функций:

fn имя_функции<'аннотация>(параметр: &'аннотация тип_данных) -> &'аннотация тип_данных {
	
}

Но нам не всегда необходимо явно указывать аннотацию времени жизни. Иногда компилятор Rust сам может вывести время жизни возвращаемой ссылки. И в данном случае он опирается на три правила:

  1. Первое правило. Каждый параметр, который представляет ссылку, имеет свою собственную аннотацию время жизни. Так, функция с одним параметром-ссылкой применяет одну аннотацию:

    fn foo<'a>(x: &'a i32);

    Функция с двумя параметрами-ссылками применяет две разные аннотации:

    fn foo<'a, 'b>(x: &'a i32, y: &'b i32);

    Функция с тремя параметрами-ссылками применяет три параметра и так далее.

  2. Второе правило. Если функция имеет один параметр-ссылку, то его аннотация автоматически применяется к возвращаемой ссылке:

    fn foo<'a>(x: &'a i32) -> &'a i32 {}
  3. Третье правило. Если функция имеет несколько параметров-ссылок, но один из них &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". Но теперь мы столкнемся с ошибкой при компиляции:

Аннотации ссылок и параметров в Rust

Данное определение функции 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 не соответствует той области видимости, в рамках которой мы пытаемся использовать ссылку на этот объект.

Аннотации ссылок и время жизни в Rust в функции

Аннотации в функции без параметров

Если функция не использует параметры-ссылки, то время жизни возвращаемой ссылки представляет время жизни возвращаемого значения:

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 становится недействительной, и ее память очищается. Соответственно все ссылки на этот объект становятся недействительными. Поэтому при компиляции данного кода мы получим ошибку:

reference lifetime in function in Rust

При этом неважно, что к возвращаемой ссылке применяется аннотация. Мы все равно не можем в данном случае возвратить ссылку. И единственным решением в данном случае будет изменение функции, например, чтобы она возвращала не ссылку, а конкретный объект. Например:

fn get_message() -> String {
	let result = String::from("Hello Rust");
	result
}
Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850