[Rust] Конспектируем Книгу Rust:: Времена и функции
Автор
Сообщение
news_bot ®
Стаж: 6 лет 9 месяцев
Сообщений: 27286
После главы 4 (было здесь) переходим к 10.3. Ну а куда еще… такие нынче времена.
Проблема
Для начала обозначим проблему, ради которой затеваются игры со временем. Простой пример висячей ссылки (dangling reference):
fn main() {
let r;
{
let s = String::from("Hello");
r = &s;
}
// <- s will be drop()-ed here
println!("r: {}", r);
}
Значение s будет создано в куче, затем уничтожено, а r — останется и будет ссылаться на то, чего нет. Rust отклонит такую заявку на выстрел в ногу с подробным пояснением, что s не живет достаточно долго:
Compiling playground v0.0.1 (/playground) error[E0597]: `s` does not live long enough
--> src/main.rs:6:9
|
6 | r = &s;
| ^^ borrowed value does not live long enough
7 | }
| - `s` dropped here while still borrowed
8 |
9 | println!("r: {}", r);
| - borrow later used here
Но такие простые штуки давно ловятся и в других языках статическими анализаторами кода. Вот вариант посложнее, функция получает две ссылки на строки и возвращает ту, референт которой (то, на что указывает ссылка) длиннее:
fn longest(x: &String, y: &String) -> &String {
if x.len() > y.len() {
x
} else {
y
}
}
Вызывать будем так:
fn main() {
let short_string = String::from("Short string");
let r_longest;
{
let long_string = String::from("Long string................");
r_longest = longest(&short_string, &long_string);
}
println!("The longest string: {}", r_longest);
}
r_longest получит, очевидно, ссылку на long_string и будет жить непозволительно долго (дольше своего референта) — такое в production попасть не должно.
В принципе, статический анализатор кода для C++ может и и такую ситуацию выловить — если "зайдет" внутрь вызываемой функции. Интересно, PVS-Studio справится с такой задачей, что скажет Andrey2008?
Rust, однако, для проверки вызывающего кода смотрит только на сигнатуру того, что вызывается, внутрь не заглядывая. Посмотрим, как он это делает. Запускаем на компиляцию… Компилятор указывает нам, что сигнатура недостаточно информативна:
Compiling playground v0.0.1 (/playground)
error[E0106]: missing lifetime specifier
--> src/main.rs:12:39
|
12 | fn longest(x: &String, y: &String) -> &String {
| ------- ------- ^ expected named lifetime parameter
|
= help: this function's return type contains a borrowed value, but the signature does not say whether it is borrowed from `x` or `y`
help: consider introducing a named lifetime parameter
|
12 | fn longest<'a>(x: &'a String, y: &'a String) -> &'a String {
| ^^^^ ^^^^^^^^^^ ^^^^^^^^^^ ^^^
Указание подробное и очень полезное. Нам говорят, что функция возвращает одолженное значение (т.е. ссылку), но непонятно, с каким параметром это одалживание связано — с x или y. Более того, компилятор даже предлагает способ починить объявление функции, введя параметр времени жизни (lifetime parameter).
Обобщённые параметры времени жизни
Итак, нам нужно разметить сигнатуру функции специальным образом, чтобы компилятор Rust "понял", с чем связано возвращаемое значение (у нас связано со всеми параметрами). Это делается при помощи generic lifetime parameters:
fn longest<'a>(x: &'a String, y: &'a String) -> &'a String {
Такое описание означает, что результат может быть связан как с x, так и с y, и после изменения примера компилятор ожидаемо в ожидаемом месте сообщает, что:
6 | r_longest = longest(&short_string, &long_string);
| ^^^^^^^^^^^^ borrowed value does not live long enough
Враг пойман, пилоты молчат, смотрят на звезды, задание выполнено успешно. Все улыбаются. Но возникают два вопроса. Первый — а как сделать так, чтобы время жизни результата было связано, например, только со вторым параметром? Вот так:
fn longest<'a>(x: &String, y: &'a String) -> &'a String {
Но при этом функция перестанет компилироваться, так как мы в одной из веток if пытаемся возвратить x со временем жизни, не совпадающим с таковым для возвращаемого значения — как видим, машинерия Rust исправно работает.
Второй вопрос (риторический) — а нельзя было "засахарить" синтаксис, вводить <'a> неявно и применять его по умолчанию ко всем параметрам и результатам, а у кого есть специальная нужда, тот пусть и указывает другие времена? С учетом уникальности имен параметров можно было бы вообще сделать как-то так (но это не точно):
fn longest(x: &String, y: &String) -> &'y String {
Сразу было бы ясно, что результат связан с параметром y и не имеет права этот параметр пережить ("outlive")...
Впрочем, процесс засахаривания Rust идет, применительно к теме его результаты называются Lifetime Elision.
Правила неявного выведения времен жизни (Lifetime Elision)
Первое правило бойцовского клуба: Для каждого параметра-ссылки с неуказанным временем жизни заводится свой неявный параметр:
Вместо (возвращаемое значение связано со вторым параметром):
fn myfunc<'a, 'b>(x: &'a String, y: &'b String) -> &'b String {
Пишем:
fn myfunc<'a>(x: &String, y: &'a String) -> &'a String {
Ну, и на том спасибо.
Второе правило. Если в параметрах только одна ссылка, то ее время жизни переносится на все возвращаемые значения:
Вместо:
fn myfunc<'a>(p1: &'a str, p2: int, p3: int) -> (out1: &'a str, out2: &'a str) {
Пишем:
fn myfunc(p1: &str, p2: int, p3: int) -> (out1: &str, out2: &str) {
Вот это хорошо, конечно.
Третье правило. Если в параметрах много ссылок, но одна из них &self или &mut self, то ее время жизни переносится на все возвращаемые значения
Для методов все шикарно. Видимо, нас как бы подталкивают в сторону ООП (которого в Rust "по-настоящему" и нет, хе-хе).
Под занавес надо разрешить загадку из первой части.
Время жизни 'static
Напомню, вот такой пример не компилируется:
fn dangling_reference() -> &str {
let s = String::from("hello");
return &s
}
Результат:
error[E0106]: missing lifetime specifier
--> src/lib.rs:1:28
|
1 | fn dangling_reference() -> &str {
| ^ expected named lifetime parameter
|
= help: this function's return type contains a borrowed value, but there is no value for it to be borrowed from
help: consider using the `'static` lifetime
|
1 | fn dangling_reference() -> &'static str {
| ^^^^^^^^ ^^^^^^^^
Компилятор закономерно жалуется, мол, неоткуда занять возвращаемое значение, все умерло. И опять предлагает решение! — обозначить время жизни возвращаемого значения как 'static.
Делается так:
static STATIC_STR: &str = "I am STATIC_STR";
fn static_reference() -> &'static str {
return STATIC_STR
}
fn main(){
println!("static_reference: {}", static_reference());
}
- Статическую переменную типа String, по-моему, сделать нельзя. Но это не точно
===========
Источник:
habr.com
===========
Похожие новости:
- [Программирование, Совершенный код, C, Rust, Браузеры] Tor Project планирует заменить код C на Rust
- [Rust] Конспектируем Книгу Rust:: Владение
- Проект Tor представил реализацию на языке Rust, которая в будущем заменит вариант на Си
- [Информационная безопасность] Атака через поставщика или подрядчика глазами пентестера
- Вторая редакция патчей для ядра Linux с поддержкой языка Rust
- [Информационная безопасность, Программирование] Что под капотом у R-Vision Threat Intelligence Platform?
- [Open source, Программирование, Системное программирование, Компиляторы, Rust] Rust 1.53.0: IntoIterator для массивов, "|" в шаблонах, Unicode-идентификаторы, поддержка имени HEAD-ветки в Cargo (перевод)
- Представлена библиотека Aya для создания eBPF-обработчиков на языке Rust
- [Информационная безопасность] Google-like система поиска уязвимостей IT Security Search — анонс вебинара
- [R, Rust] extendr: вызываем rust из R (и наоборот)
Теги для поиска: #_rust, #_rust, #_rust
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 23-Ноя 13:20
Часовой пояс: UTC + 5
Автор | Сообщение |
---|---|
news_bot ®
Стаж: 6 лет 9 месяцев |
|
После главы 4 (было здесь) переходим к 10.3. Ну а куда еще… такие нынче времена. Проблема Для начала обозначим проблему, ради которой затеваются игры со временем. Простой пример висячей ссылки (dangling reference): fn main() {
let r; { let s = String::from("Hello"); r = &s; } // <- s will be drop()-ed here println!("r: {}", r); } Значение s будет создано в куче, затем уничтожено, а r — останется и будет ссылаться на то, чего нет. Rust отклонит такую заявку на выстрел в ногу с подробным пояснением, что s не живет достаточно долго: Compiling playground v0.0.1 (/playground) error[E0597]: `s` does not live long enough
--> src/main.rs:6:9 | 6 | r = &s; | ^^ borrowed value does not live long enough 7 | } | - `s` dropped here while still borrowed 8 | 9 | println!("r: {}", r); | - borrow later used here Но такие простые штуки давно ловятся и в других языках статическими анализаторами кода. Вот вариант посложнее, функция получает две ссылки на строки и возвращает ту, референт которой (то, на что указывает ссылка) длиннее: fn longest(x: &String, y: &String) -> &String {
if x.len() > y.len() { x } else { y } } Вызывать будем так: fn main() {
let short_string = String::from("Short string"); let r_longest; { let long_string = String::from("Long string................"); r_longest = longest(&short_string, &long_string); } println!("The longest string: {}", r_longest); } r_longest получит, очевидно, ссылку на long_string и будет жить непозволительно долго (дольше своего референта) — такое в production попасть не должно. В принципе, статический анализатор кода для C++ может и и такую ситуацию выловить — если "зайдет" внутрь вызываемой функции. Интересно, PVS-Studio справится с такой задачей, что скажет Andrey2008? Rust, однако, для проверки вызывающего кода смотрит только на сигнатуру того, что вызывается, внутрь не заглядывая. Посмотрим, как он это делает. Запускаем на компиляцию… Компилятор указывает нам, что сигнатура недостаточно информативна: Compiling playground v0.0.1 (/playground)
error[E0106]: missing lifetime specifier --> src/main.rs:12:39 | 12 | fn longest(x: &String, y: &String) -> &String { | ------- ------- ^ expected named lifetime parameter | = help: this function's return type contains a borrowed value, but the signature does not say whether it is borrowed from `x` or `y` help: consider introducing a named lifetime parameter | 12 | fn longest<'a>(x: &'a String, y: &'a String) -> &'a String { | ^^^^ ^^^^^^^^^^ ^^^^^^^^^^ ^^^ Указание подробное и очень полезное. Нам говорят, что функция возвращает одолженное значение (т.е. ссылку), но непонятно, с каким параметром это одалживание связано — с x или y. Более того, компилятор даже предлагает способ починить объявление функции, введя параметр времени жизни (lifetime parameter). Обобщённые параметры времени жизни Итак, нам нужно разметить сигнатуру функции специальным образом, чтобы компилятор Rust "понял", с чем связано возвращаемое значение (у нас связано со всеми параметрами). Это делается при помощи generic lifetime parameters: fn longest<'a>(x: &'a String, y: &'a String) -> &'a String {
Такое описание означает, что результат может быть связан как с x, так и с y, и после изменения примера компилятор ожидаемо в ожидаемом месте сообщает, что: 6 | r_longest = longest(&short_string, &long_string);
| ^^^^^^^^^^^^ borrowed value does not live long enough Враг пойман, пилоты молчат, смотрят на звезды, задание выполнено успешно. Все улыбаются. Но возникают два вопроса. Первый — а как сделать так, чтобы время жизни результата было связано, например, только со вторым параметром? Вот так: fn longest<'a>(x: &String, y: &'a String) -> &'a String {
Но при этом функция перестанет компилироваться, так как мы в одной из веток if пытаемся возвратить x со временем жизни, не совпадающим с таковым для возвращаемого значения — как видим, машинерия Rust исправно работает. Второй вопрос (риторический) — а нельзя было "засахарить" синтаксис, вводить <'a> неявно и применять его по умолчанию ко всем параметрам и результатам, а у кого есть специальная нужда, тот пусть и указывает другие времена? С учетом уникальности имен параметров можно было бы вообще сделать как-то так (но это не точно): fn longest(x: &String, y: &String) -> &'y String {
Сразу было бы ясно, что результат связан с параметром y и не имеет права этот параметр пережить ("outlive")... Впрочем, процесс засахаривания Rust идет, применительно к теме его результаты называются Lifetime Elision. Правила неявного выведения времен жизни (Lifetime Elision) Первое правило бойцовского клуба: Для каждого параметра-ссылки с неуказанным временем жизни заводится свой неявный параметр: Вместо (возвращаемое значение связано со вторым параметром): fn myfunc<'a, 'b>(x: &'a String, y: &'b String) -> &'b String {
Пишем: fn myfunc<'a>(x: &String, y: &'a String) -> &'a String {
Ну, и на том спасибо. Второе правило. Если в параметрах только одна ссылка, то ее время жизни переносится на все возвращаемые значения: Вместо: fn myfunc<'a>(p1: &'a str, p2: int, p3: int) -> (out1: &'a str, out2: &'a str) {
Пишем: fn myfunc(p1: &str, p2: int, p3: int) -> (out1: &str, out2: &str) {
Вот это хорошо, конечно. Третье правило. Если в параметрах много ссылок, но одна из них &self или &mut self, то ее время жизни переносится на все возвращаемые значения Для методов все шикарно. Видимо, нас как бы подталкивают в сторону ООП (которого в Rust "по-настоящему" и нет, хе-хе). Под занавес надо разрешить загадку из первой части. Время жизни 'static Напомню, вот такой пример не компилируется: fn dangling_reference() -> &str {
let s = String::from("hello"); return &s } Результат: error[E0106]: missing lifetime specifier
--> src/lib.rs:1:28 | 1 | fn dangling_reference() -> &str { | ^ expected named lifetime parameter | = help: this function's return type contains a borrowed value, but there is no value for it to be borrowed from help: consider using the `'static` lifetime | 1 | fn dangling_reference() -> &'static str { | ^^^^^^^^ ^^^^^^^^ Компилятор закономерно жалуется, мол, неоткуда занять возвращаемое значение, все умерло. И опять предлагает решение! — обозначить время жизни возвращаемого значения как 'static. Делается так: static STATIC_STR: &str = "I am STATIC_STR";
fn static_reference() -> &'static str { return STATIC_STR } fn main(){ println!("static_reference: {}", static_reference()); }
=========== Источник: habr.com =========== Похожие новости:
|
|
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 23-Ноя 13:20
Часовой пояс: UTC + 5