Я читал главу о жизни в книге Rust и наткнулся на этот пример для именованного / явного времени жизни:
struct Foo<'a> {
x: &'a i32,
}
fn main() {
let x; // -+ x goes into scope
// |
{ // |
let y = &5; // ---+ y goes into scope
let f = Foo { x: y }; // ---+ f goes into scope
x = &f.x; // | | error here
} // ---+ f and y go out of scope
// |
println!("{}", x); // |
} // -+ x goes out of scope
Мне совершенно ясно, что ошибка, которую предотвращает компилятор, это использование-после-освобождения ссылки, назначенной x
: после того, как внутренняя область действия завершена, f
и, следовательно, &f.x
становится недействительной и не должна была быть назначена x
.
Моя проблема в том, что проблему можно было легко проанализировать, не используя явное 'a
время жизни, например, сделав неверное присвоение ссылки на более широкий контекст ( x = &f.x;
).
В каких случаях требуется явное время жизни для предотвращения ошибок использования после освобождения (или какого-либо другого класса?)?
reference
rust
static-analysis
lifetime
Corazza
источник
источник
Ответы:
Все остальные ответы имеют существенные моменты ( конкретный пример fjh, где требуется явное время жизни ), но не хватает одного ключевого момента: зачем нужны явные времена жизни, когда компилятор скажет вам, что вы ошиблись ?
На самом деле это тот же вопрос, что и «зачем нужны явные типы, когда компилятор может их определить». Гипотетический пример:
Конечно, компилятор может видеть, что я возвращаю a
&'static str
, так почему программист должен его печатать?Основная причина в том, что хотя компилятор может видеть, что делает ваш код, он не знает, каково было ваше намерение.
Функции - это естественная граница брандмауэра для эффектов изменения кода. Если бы мы позволили полностью проверить время жизни из кода, то невинно выглядящее изменение может повлиять на время жизни, что может привести к ошибкам в функции на большом расстоянии. Это не гипотетический пример. Насколько я понимаю, у Haskell есть такая проблема, когда вы полагаетесь на вывод типов для функций верхнего уровня. Руст пресек эту конкретную проблему в зародыше.
Компилятор также выигрывает в эффективности - необходимо анализировать сигнатуры только функций, чтобы проверить типы и время жизни. Что еще более важно, это имеет преимущество в эффективности для программиста. Если у нас не было явного времени жизни, что делает эта функция:
Невозможно сказать, не изучив источник, что противоречило бы огромному количеству лучших практик кодирования.
Области - это время жизни, по сути. Чуть более ясно, время жизни
'a
- это общий параметр времени жизни, который может быть специализирован с определенной областью действия во время компиляции на основе сайта вызова.Не за что. Время жизни необходимо для предотвращения ошибок, но явное время жизни необходимо для защиты того, что имеют маленькие здравомыслящие программисты.
источник
f x = x + 1
без сигнатуры типа, которую вы используете в другом модуле. Если позднее вы измените определение наf x = sqrt $ x + 1
, его тип изменится сNum a => a -> a
наFloating a => a -> a
, что приведет к ошибкам типов на всех сайтах вызовов, гдеf
вызывается, например, сInt
аргументом. Наличие сигнатуры типа гарантирует, что ошибки происходят локально.sqrt $
, только локальная ошибка произошла бы после изменения, а не много ошибок в других местах (что намного лучше, если бы мы не не хотите изменить фактический тип)?Давайте посмотрим на следующий пример.
Здесь важны явные времена жизни. Это компилируется, потому что результат
foo
имеет то же время жизни, что и его первый аргумент ('a
), поэтому он может пережить свой второй аргумент. Это выражается именами времени жизни в подписиfoo
. Если бы вы переключили аргументы в вызовеfoo
на компилятор, вы бы пожаловались, чтоy
не живут достаточно долго:источник
Время жизни аннотации в следующей структуре:
указывает, что
Foo
экземпляр не должен переживать содержащуюся в нем ссылку (x
поле).Пример вы наткнулись в книге Ржавчина не иллюстрирует это потому , что
f
иy
переменные выходят из области видимости , в то же время.Лучший пример будет следующим:
Теперь
f
действительно переживает переменную, на которую указываетf.x
.источник
Обратите внимание, что в этом фрагменте кода нет явных времен жизни, кроме определения структуры. Компилятор прекрасно умеет определять время жизни в
main()
.Однако в определениях типов явные времена жизни неизбежны. Например, здесь есть двусмысленность:
Должны ли они быть разными жизнями или они должны быть одинаковыми? Это имеет значение с точки зрения использования,
struct RefPair<'a, 'b>(&'a u32, &'b u32)
очень отличается отstruct RefPair<'a>(&'a u32, &'a u32)
.Теперь, для простых случаев, таких как тот, который вы предоставили, компилятор может теоретически исключить время жизни, как это происходит в других местах, но такие случаи очень ограничены и не стоят дополнительной сложности в компиляторе, и этот выигрыш в ясности будет на наименее сомнительный.
источник
'static
,'static
их можно использовать везде, где можно использовать локальные времена жизни, поэтому в вашем примереp
его параметр времени жизни будет выведен как локальное время жизниy
.RefPair<'a>(&'a u32, &'a u32)
означает, что'a
это будет пересечение обоих входных времен жизни, т.е. в этом случае время жизниy
.Корпус из книги очень прост по дизайну. Тема жизней считается сложной.
Компилятор не может легко определить время жизни функции с несколькими аргументами.
Кроме того, у моего собственного дополнительного ящика есть
OptionBool
тип сas_slice
методом, сигнатура которого на самом деле:Компилятор никак не мог понять это.
источник
Я нашел другое отличное объяснение здесь: http://doc.rust-lang.org/0.12.0/guide-lifetimes.html#returning-references .
источник
Если функция получает две ссылки в качестве аргументов и возвращает ссылку, то реализация функции может иногда возвращать первую ссылку, а иногда и вторую. Невозможно предсказать, какая ссылка будет возвращена для данного вызова. В этом случае невозможно определить время жизни возвращаемой ссылки, поскольку каждая ссылка на аргумент может ссылаться на другую привязку переменной с другим временем жизни. Явные времена жизни помогают избежать или прояснить такую ситуацию.
Аналогично, если структура содержит две ссылки (как два поля-члена), то функция-член структуры может иногда возвращать первую ссылку, а иногда и вторую. Снова явные времена жизни предотвращают такие неясности.
В нескольких простых ситуациях есть выбор времени жизни, когда компилятор может определить время жизни.
источник
Причина, по которой ваш пример не работает, заключается в том, что Rust имеет только локальное время жизни и вывод типа. То, что вы предлагаете, требует глобального вывода. Всякий раз, когда у вас есть ссылка, срок жизни которой нельзя исключить, она должна быть аннотирована.
источник
Как новичок в Rust, я понимаю, что явные времена жизни служат двум целям.
Размещение явной аннотации времени жизни для функции ограничивает тип кода, который может появляться внутри этой функции. Явные времена жизни позволяют компилятору гарантировать, что ваша программа делает то, что вы хотели.
Если вы (компилятор) хотите (и) проверить, является ли фрагмент кода допустимым, вам (компилятору) не придется итеративно заглядывать внутрь каждой вызываемой функции. Достаточно взглянуть на аннотации функций, которые напрямую вызываются этим фрагментом кода. Это делает вашу программу намного проще для вас (компилятор) и делает время компиляции управляемым.
В пункте 1. Рассмотрим следующую программу, написанную на Python:
который напечатает
Такое поведение всегда меня удивляет. То, что происходит, - это то, что
df
разделяет памятьar
, поэтому, когда некоторыеdf
изменения в ней происходятwork
, это изменение также поражаетar
. Тем не менее, в некоторых случаях это может быть именно тем, что вы хотите, из соображений эффективности памяти (без копирования). Настоящая проблема в этом коде состоит в том, что функцияsecond_row
возвращает первую строку вместо второй; удачи в отладке этого.Рассмотрим вместо этого похожую программу, написанную на Rust:
Составив это, вы получите
На самом деле вы получаете две ошибки, есть еще одна с ролями
'a
и'b
взаимозаменяемость. Глядя на аннотациюsecond_row
, мы находим, что выходные данные должны быть&mut &'b mut [i32]
, т. Е. Выходные данные должны быть ссылками на ссылку с временем жизни'b
(временем жизни второй строкиArray
). Однако, поскольку мы возвращаем первую строку (у которой есть время жизни'a
), компилятор жалуется на несоответствие времени жизни. В нужном месте. В нужное время. Отладка на одном дыхании.источник
Я думаю о пожизненной аннотации как о договоре о данном рефере, который действителен только в области получения, пока он остается в силе источника. Объявление большего количества ссылок в одном и том же виде жизни объединяет области, что означает, что все ссылки на источники должны удовлетворять этому контракту. Такая аннотация позволяет компилятору проверять выполнение договора.
источник