Язык программирования Rust
Скачать 7.02 Mb.
|
Анализатор заимствований Компилятор Rust имеет в своём составе анализатор заимствований, который сравнивает области видимости для определения, являются ли все заимствования действительными. В листинге 10-17 показан тот же код, что и в листинге 10-16, но с аннотациями, показывающими времена жизни переменных. $ cargo run Compiling chapter10 v0.1.0 (file:///projects/chapter10) error[E0597]: `x` does not live long enough --> src/main.rs:6:13 | 6 | r = &x; | ^^ borrowed value does not live long enough 7 | } | - `x` dropped here while still borrowed 8 | 9 | println!("r: {}", r); | - borrow later used here For more information about this error, try `rustc --explain E0597`. error: could not compile `chapter10` due to previous error Пример 10-17: Аннотация времён жизни переменных r и x , с помощью идентификаторов времени жизни 'a и 'b , соответственно Здесь мы описали время жизни для r с помощью 'a и время жизни x с помощью 'b Как видите, время жизни 'b внутреннего блока гораздо меньше, чем время жизни 'a внешнего блока. Во время компиляции Rust сравнивает продолжительность двух времён жизни и видит, что r имеет время жизни 'a , но ссылается на память со временем жизни 'b . Программа отклоняется, потому что 'b короче, чем 'a : объект ссылки не живёт так же долго, как сама ссылка. Листинг 10-18 исправляет код, чтобы в нём не было повисшей ссылки, и компилируется без ошибок. Листинг 10-18: Ссылка корректна, так как данные имеют более продолжительное время жизни, чем ссылка на эти данные Здесь переменная x имеет время жизни 'b , которое больше, чем время жизни 'a . Это означает, что переменная r может ссылаться на переменную x потому что Rust знает, что ссылка в переменной r будет всегда действительной до тех пор, пока переменная x является валидной. После того, как мы на примерах рассмотрели времена жизни ссылок и обсудили как Rust их анализирует, давайте поговорим об обобщённых временах жизни входных параметров и возвращаемых значений функций. Обобщённые времена жизни в функциях fn main () { let r; // ---------+-- 'a // | { // | let x = 5 ; // -+-- 'b | r = &x; // | | } // -+ | // | println! ( "r: {}" , r); // | } // ---------+ fn main () { let x = 5 ; // ----------+-- 'b // | let r = &x; // --+-- 'a | // | | println! ( "r: {}" , r); // | | // --+ | } // ----------+ Напишем функцию, которая возвращает более длинный из двух срезов строки. Эта функция принимает два среза строки и возвращает один срез строки. После того как мы реализовали функцию longest , код в листинге 10-19 должен вывести The longest string is abcd Файл: src/main.rs Листинг 10-19: Функция main вызывает функцию longest для поиска наибольшего из двух срезов строки Обратите внимание, что мы хотим чтобы функция принимала строковые срезы, которые являются ссылками, а не строки, потому что мы не хотим, чтобы функция longest забирала во владение свои параметры. Обратитесь к разделу "Строковые срезы как параметры" Главы 4 для более подробного обсуждения того, почему параметры используемые в листинге 10-19 выбраны именно таким образом. Если мы попробуем реализовать функцию longest так, как это показано в листинге 10- 20, программа не скомпилируется: Файл: src/main.rs Листинг 10-20: Реализация функции longest , которая возвращает наибольший срез строки, но пока не компилируется Вместо этого мы получим следующую ошибку, говорящую о временах жизни: fn main () { let string1 = String ::from( "abcd" ); let string2 = "xyz" ; let result = longest(string1.as_str(), string2); println! ( "The longest string is {}" , result); } fn longest (x: & str , y: & str ) -> & str { if x.len() > y.len() { x } else { y } } Текст ошибки показывает, что возвращаемому типу нужен обобщённый параметр времени жизни, потому что Rust не может определить, относится ли возвращаемая ссылка к x или к y . На самом деле, мы тоже не знаем, потому что блок if в теле функции возвращает ссылку на x , а блок else возвращает ссылку на y ! Когда мы определяем эту функцию, мы не знаем конкретных значений, которые будут в неё передаваться. Поэтому мы не знаем какая из ветвей оператора if или else будет выполнена. Мы также не знаем конкретных времён жизни ссылок, которые будут переданы в функцию, поэтому мы не можем посмотреть на их области видимости, как мы делали в примерах 10-17 и 10-18, чтобы определить, будет ли возвращаемая нами ссылка корректной во всех случаях. Анализатор заимствований также не может этого определить, потому что он не знает как времена жизни переменных x и y соотносятся с временем жизни возвращаемого значения. Чтобы исправить эту ошибку, мы добавим обобщённый параметр времени жизни, который определит отношения между ссылками таким образом, чтобы анализатор заимствований мог провести свой анализ. Синтаксис аннотации времени жизни Аннотации времени жизни не меняют срок, как долго живёт та или иная ссылка. Они скорее описывают, как соотносятся между собой времена жизни нескольких ссылок, не влияя на само время жизни. Точно так же, как функции могут принимать любой тип, когда в сигнатуре указан параметр обобщённого типа, функции могут принимать ссылки с любым временем жизни, указанным с помощью параметра обобщённого времени жизни. Аннотации времени жизни имеют немного необычный синтаксис: имена параметров времени жизни должны начинаться с апострофа ( ' ), пишутся маленькими буквами, и обычно очень короткие, как и имена обобщённых типов. Большинство людей использует имя 'a в качестве первой аннотации времени жизни. Аннотации $ cargo run Compiling chapter10 v0.1.0 (file:///projects/chapter10) error[E0106]: missing lifetime specifier --> src/main.rs:9:33 | 9 | fn longest(x: &str, y: &str) -> &str { | ---- ---- ^ 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 | 9 | fn longest<'a>(x: &'a str, y: &'a str) -> &'a str { | ++++ ++ ++ ++ For more information about this error, try `rustc --explain E0106`. error: could not compile `chapter10` due to previous error параметров времени жизни следуют после символа & и отделяются пробелом от названия ссылочного типа. Приведём несколько примеров: у нас есть ссылка на i32 без указания времени жизни, ссылка на i32 , с временем жизни имеющим имя 'a и изменяемая ссылка на i32 , которая также имеет время жизни 'a Одна аннотация времени жизни сама по себе не имеет большого значения, поскольку аннотации предназначены для того, чтобы проинформировать Rust о том, как времена жизни нескольких ссылок соотносятся между собой. Давайте рассмотрим, как аннотации времени жизни связаны друг с другом в контексте функции longest Аннотации времени жизни в сигнатурах функций Чтобы использовать аннотации времени жизни в сигнатурах функций, нам нужно объявить параметры обобщённого времени жизни внутри угловых скобок между именем функции и списком параметров, как мы это делали с параметрами обобщённого типа . Мы хотим, чтобы сигнатура отражала следующее ограничение: возвращаемая ссылка будет действительна до тех пор, пока валидны оба параметра. Это связь между временами жизни параметров и возвращаемого значения. Мы назовём это время жизни 'a , а затем добавим его к каждой ссылке, как показано в листинге 10-21. Файл: src/main.rs Листинг 10-21: В определении функции longest указано, что все ссылки должны иметь одинаковое время жизни, обозначенное как 'a Этот код должен компилироваться и давать желаемый результат, когда мы вызовем его в функции main листинга 10-19. Сигнатура функции теперь сообщает Rust, что для некоторого времени жизни 'a функция принимает два параметра, оба из которых являются срезами строк, которые живут не меньше, чем время жизни 'a . Сигнатура функции также сообщает Rust, что срез строки, возвращаемый функцией, будет жить как минимум столько, сколько длится & i32 // ссылка & 'a i32 // ссылка с явным временем жизни & 'a mut i32 // изменяемая ссылка с явным временем жизни fn longest < 'a >(x: & 'a str , y: & 'a str ) -> & 'a str { if x.len() > y.len() { x } else { y } } время жизни 'a . На практике это означает, что время жизни ссылки, возвращаемой функцией longest , равно меньшему времени жизни передаваемых в неё ссылок. Мы хотим, чтобы Rust использовал именно такие отношения при анализе этого кода. Помните, когда мы указываем параметры времени жизни в этой сигнатуре функции, мы не меняем время жизни каких-либо переданных или возвращённых значений. Скорее, мы указываем, что анализатор заимствований должен отклонять любые значения, которые не соответствуют этим ограничениям. Обратите внимание, что самой функции longest не нужно точно знать, как долго будут жить x и y , достаточно того, что некоторая область может быть заменена на 'a , которая будет удовлетворять этой сигнатуре. При аннотировании времён жизни функций, аннотации помещаются в сигнатуру функции, а не в тело функции. Аннотации времени жизни становятся частью контракта функции, как и типы в сигнатуре. Наличие сигнатур функций, содержащих контракт времени жизни, означает, что анализ который выполняет компилятор Rust, может быть проще. Если есть проблема с тем, как функция аннотируется или как она вызывается, ошибки компилятора могут указать на часть нашего кода и ограничения более точно. Если бы вместо этого компилятор Rust сделал больше предположений о том, какие отношения времён жизни мы хотели получить, компилятор смог бы указать только на использование нашего кода за много шагов от источника проблемы. Когда мы передаём конкретные ссылки в функцию longest , конкретным временем жизни, которое будет заменено на 'a , является часть области видимости x , которая пересекается с областью видимости y . Другими словами, обобщённое время жизни 'a получит конкретное время жизни, равное меньшему из времён жизни x и y . Так как мы аннотировали возвращаемую ссылку тем же параметром времени жизни 'a , то возвращённая ссылка также будет действительна на протяжении меньшего из времён жизни x и y Давайте посмотрим, как аннотации времени жизни ограничивают функцию longest путём передачи в неё ссылок, которые имеют разные конкретные времена жизни. Листинг 10-22 является очевидным примером. Файл: src/main.rs Листинг 10-22: Использование функции longest со ссылками на значения типа String , имеющими разное время жизни fn main () { let string1 = String ::from( "long string is long" ); { let string2 = String ::from( "xyz" ); let result = longest(string1.as_str(), string2.as_str()); println! ( "The longest string is {}" , result); } } В этом примере переменная string1 действительна до конца внешней области, string2 действует до конца внутренней области видимости и result ссылается на что-то, что является действительным до конца внутренней области видимости. Запустите этот код, и вы увидите что анализатор заимствований разрешает такой код; он скомпилирует и напечатает The longest string is long string is long Теперь, давайте попробуем пример, который показывает, что время жизни ссылки result должно быть меньшим временем жизни одного из двух аргументов. Мы переместим объявление переменной result за пределы внутренней области видимости, но оставим присвоение значения переменной result в области видимости string2 . Затем мы переместим println! , который использует result за пределы внутренней области видимости, после того как внутренняя область видимости закончилась. Код в листинге 10-23 не скомпилируется. Файл: src/main.rs Листинг 10-23: Попытка использования result , после того как string2 вышла из области видимости При попытке скомпилировать этот код, мы получим такую ошибку: Эта ошибка говорит о том, что если мы хотим использовать result в println! , переменная string2 должна бы быть действительной до конца внешней области видимости. Rust знает об этом, потому что мы аннотировали параметры функции и её возвращаемое значение одинаковым временем жизни 'a fn main () { let string1 = String ::from( "long string is long" ); let result; { let string2 = String ::from( "xyz" ); result = longest(string1.as_str(), string2.as_str()); } println! ( "The longest string is {}" , result); } $ cargo run Compiling chapter10 v0.1.0 (file:///projects/chapter10) error[E0597]: `string2` does not live long enough --> src/main.rs:6:44 | 6 | result = longest(string1.as_str(), string2.as_str()); | ^^^^^^^^^^^^^^^^ borrowed value does not live long enough 7 | } | - `string2` dropped here while still borrowed 8 | println!("The longest string is {}", result); | ------ borrow later used here For more information about this error, try `rustc --explain E0597`. error: could not compile `chapter10` due to previous error Будучи людьми, мы можем посмотреть на этот код и увидеть, что string1 живёт дольше, чем string2 и, следовательно, result будет содержать ссылку на string1 . Поскольку string1 ещё не вышла из области видимости, ссылка на string1 будет все ещё действительной в выражении println! . Однако компилятор не видит, что ссылка в этом случае валидна. Мы сказали Rust, что время жизни ссылки, возвращаемой из функции longest , равняется меньшему из времён жизни переданных в неё ссылок. Таким образом, анализатор заимствований запрещает код в листинге 10-23, как возможно имеющий недействительную ссылку. Попробуйте провести больше экспериментов с различными значениями и временами жизни ссылок, передаваемых в функцию longest , а также с тем, как используется возвращаемое значение Перед компиляцией делайте предположения о том, пройдёт ли ваш код анализ заимствований, а затем проверяйте, насколько вы были правы. Мышление в терминах времён жизни В зависимости от того, что делает ваша функция, следует использовать разные способы указания параметров времени жизни. Например, если мы изменим реализацию функции longest таким образом, чтобы она всегда возвращала свой первый аргумент вместо самого длинного среза строки, то время жизни для параметра y можно совсем не указывать. Этот код скомпилируется: Файл: src/main.rs Мы указали параметр времени жизни 'a для параметра x и возвращаемого значения, но не для параметра y , поскольку время жизни параметра y никак не соотносится с временем жизни параметра x или возвращаемого значения. При возврате ссылки из функции, параметр времени жизни для возвращаемого типа должен соответствовать параметру времени жизни одного из аргументов. Если возвращаемая ссылка не ссылается на один из параметров, она должна ссылаться на значение, созданное внутри функции. Однако, это приведёт к недействительной ссылке, поскольку значение, на которое она ссылается, выйдет из области видимости в конце функции. Посмотрите на попытку реализации функции longest , которая не скомпилируется: Файл: src/main.rs fn longest < 'a >(x: & 'a str , y: & str ) -> & 'a str { x } fn longest < 'a >(x: & str , y: & str ) -> & 'a str { let result = String ::from( "really long string" ); result.as_str() } Здесь, несмотря на то, что мы указали параметр времени жизни 'a для возвращаемого типа, реализация не будет скомпилирована, потому что время жизни возвращаемого значения никак не связано с временем жизни параметров. Получаем сообщение об ошибке: Проблема заключается в том, что result выходит за область видимости и очищается в конце функции longest . Мы также пытаемся вернуть ссылку на result из функции. Мы не можем указать параметры времени жизни, которые могли бы изменить недействительную ссылку, а Rust не позволит нам создать недействительную ссылку. В этом случае лучшим решением будет вернуть владеющий тип данных, а не ссылку: в этом случае вызывающая функция будет нести ответственность за очистку полученного ею значения. В конечном итоге, синтаксис времён жизни реализует связывание времён жизни различных аргументов и возвращаемых значений функций. Описывая времена жизни, мы даём Rust достаточно информации, чтобы разрешить безопасные операции с памятью и запретить операции, которые могли бы создать недействительные ссылки или иным способом нарушить безопасность памяти. Определение времён жизни при объявлении структур До сих пор мы объявляли структуры, которые всегда содержали владеющие типы данных. Структуры могут содержать и ссылки, но при этом необходимо добавить аннотацию времени жизни для каждой ссылки в определении структуры. Листинг 10-24 описывает структуру ImportantExcerpt , содержащую срез строки: Файл: src/main.rs $ cargo run Compiling chapter10 v0.1.0 (file:///projects/chapter10) error[E0515]: cannot return reference to local variable `result` --> src/main.rs:11:5 | 11 | result.as_str() | ^^^^^^^^^^^^^^^ returns a reference to data owned by the current function For more information about this error, try `rustc --explain E0515`. error: could not compile `chapter10` due to previous error Листинг 10-25. Структура, содержащая ссылку, требует аннотации времени жизни У структуры имеется одно поле part , хранящее срез строки, который сам по себе является ссылкой. Как и в случае с обобщёнными типами данных, мы объявляем имя обобщённого параметра времени жизни в внутри угловых скобок после имени структуры, чтобы иметь возможность использовать его внутри определения структуры. Данная аннотация означает, что экземпляр ImportantExcerpt не может пережить ссылку, которую он содержит в своём поле part Функция main здесь создаёт экземпляр структуры ImportantExcerpt , который содержит ссылку на первое предложение типа String принадлежащее переменной novel Данные в novel существуют до создания экземпляра ImportantExcerpt . Кроме того, novel не выходит из области видимости до тех пор, пока ImportantExcerpt не выйдет за область видимости, поэтому ссылка в внутри экземпляра ImportantExcerpt является действительной. |