Приведения типов
Приведения типов — это неявные операции, которые изменяют тип значения. Они происходят автоматически в определённых местах и сильно ограничены в том, какие типы фактически могут приводиться.
Любые преобразования, разрешённые приведением, также могут быть явно выполнены с помощью
оператора приведения типа, as.
Приведения были первоначально определены в RFC 401 и расширены в RFC 1558.
Места приведения
Приведение может происходить только в определённых местах программы; это обычно места, где желаемый тип явный или может быть выведен путём распространения от явных типов (без вывода типа). Возможные места приведения:
-
Операторы
let, где указан явный тип.Например,
&mut 42приводится к типу&i8в следующем:#![allow(unused)] fn main() { let _: &i8 = &mut 42; }
- Объявления статических и константных элементов (аналогично операторам
let).
-
Аргументы вызовов функций
Приводимое значение — это фактический параметр, и оно приводится к типу формального параметра.
Например,
&mut 42приводится к типу&i8в следующем:fn bar(_: &i8) { } fn main() { bar(&mut 42); }Для вызовов методов тип получателя (параметр
self) приводится по-другому, смотрите документацию по method-call expressions для подробностей.
-
Инициализации полей структур, объединений или вариантов перечислений
Например,
&mut 42приводится к типу&i8в следующем:struct Foo<'a> { x: &'a i8 } fn main() { Foo { x: &mut 42 }; }
-
Результаты функций — либо последняя строка блока, если она не завершена точкой с запятой, либо любое выражение в операторе
returnНапример,
xприводится к типу&dyn Displayв следующем:#![allow(unused)] fn main() { use std::fmt::Display; fn foo(x: &u32) -> &dyn Display { x } }
Если выражение в одном из этих мест приведения является распространяющим приведение выражением, то соответствующие подвыражения в этом выражении также являются местами приведения. Распространение рекурсивно продолжается от этих новых мест приведения. Распространяющие выражения и их соответствующие подвыражения:
- Литералы массивов, где массив имеет тип
[U; n]. Каждое подвыражение в литерале массива является местом приведения к типуU.
- Литералы массивов с синтаксисом повторения, где массив имеет тип
[U; n]. Повторяющееся подвыражение является местом приведения к типуU.
- Кортежи, где кортеж является местом приведения к типу
(U_0, U_1, ..., U_n). Каждое подвыражение является местом приведения к соответствующему типу, например, нулевое подвыражение является местом приведения к типуU_0.
- Подвыражения в круглых скобках (
(e)): если выражение имеет типU, то подвыражение является местом приведения кU.
- Блоки: если блок имеет тип
U, то последнее выражение в блоке (если оно не завершено точкой с запятой) является местом приведения кU. Это включает блоки, которые являются частью операторов управления потоком, таких какif/else, если блок имеет известный тип.
Типы приведений
Приведение разрешено между следующими типами:
TкU, еслиTявляется подтипомU(рефлексивный случай)
-
T_1кT_3, гдеT_1приводится кT_2иT_2приводится кT_3(транзитивный случай)Обратите внимание, что это ещё не полностью поддерживается.
&mut Tк&T
*mut Tк*const T
&Tк*const T
&mut Tк*mut T
-
&Tили&mut Tк&U, еслиTреализуетDeref<Target = U>. Например:use std::ops::Deref; struct CharContainer { value: char, } impl Deref for CharContainer { type Target = char; fn deref<'a>(&'a self) -> &'a char { &self.value } } fn foo(arg: &char) {} fn main() { let x = &mut CharContainer { value: 'y' }; foo(x); // &mut CharContainer приводится к &char. }
&mut Tк&mut U, еслиTреализуетDerefMut<Target = U>.
-
TyCtor(
T) к TyCtor(U), где TyCtor(T) является одним из&T&mut T*const T*mut TBox<T>
и где
Uможет быть получен изTс помощью неразмерного приведения.
- Типы функциональных элементов к указателям на функции (
fnpointers)
- Замыкания без захвата к указателям на функции (
fnpointers)
!к любомуT
Неразмерные приведения
Следующие приведения называются неразмерными приведениями, поскольку они
связаны с преобразованием типов в неразмерные типы, и разрешены в нескольких
случаях, где другие приведения не разрешены, как описано выше. Они всё ещё могут происходить
в любом другом месте, где возможно приведение.
Два трейта, Unsize и CoerceUnsized, используются
для помощи в этом процессе и предоставления его для использования в библиотеках. Следующие
приведения являются встроенными, и если T может быть приведён к U с помощью одного из них, то
будет предоставлена реализация Unsize<U> для T:
[T; n]к[T].
Tкdyn U, когдаTреализуетU + Sized, иUявляется dyn compatible.
dyn Tкdyn U, когдаUявляется одним из супертрейтовT.- Это позволяет отбрасывать автоматические трейты, т.е. приведение
dyn T + Autoкdyn Uразрешено. - Это позволяет добавлять автоматические трейты, если главный трейт имеет автоматический трейт как супертрейт, т.е. при
trait T: U + Send {}, приведенияdyn Tкdyn T + Sendили кdyn U + Sendразрешены.
- Это позволяет отбрасывать автоматические трейты, т.е. приведение
Foo<..., T, ...>кFoo<..., U, ...>, когда:Fooявляется структурой.TреализуетUnsize<U>.- Последнее поле
Fooимеет тип, включающийT. - Если это поле имеет тип
Bar<T>, тоBar<T>реализуетUnsize<Bar<U>>. Tне является частью типа любых других полей.
Дополнительно, тип Foo<T> может реализовать CoerceUnsized<Foo<U>>, когда T
реализует Unsize<U> или CoerceUnsized<Foo<U>>. Это позволяет ему предоставлять
неразмерное приведение к Foo<U>.
Note
Хотя определение неразмерных приведений и их реализация были стабилизированы, сами трейты ещё не стабильны и поэтому не могут использоваться напрямую в стабильной Rust.
Приведения к наименьшей верхней границе
В некоторых контекстах компилятор должен приводить несколько типов друг к другу, чтобы найти наиболее общий тип. Это называется приведением к “Наименьшей Верхней Границе” (LUB). LUB-приведение используется и используется только в следующих ситуациях:
- Чтобы найти общий тип для серии ветвей if.
- Чтобы найти общий тип для серии ветвей match.
- Чтобы найти общий тип для элементов массива.
- Чтобы найти тип возвращаемого значения замыкания с несколькими операторами return.
- Чтобы проверить тип возвращаемого значения функции с несколькими операторами return.
В каждом таком случае есть набор типов T0..Tn, которые должны быть взаимно приведены
к некоторому целевому типу T_t, который изначально неизвестен.
Вычисление LUB-приведения
выполняется итеративно. Целевой тип T_t начинается как тип T0.
Для каждого нового типа Ti мы рассматриваем, может ли
- Если
Tiможет быть приведён к текущему целевому типуT_t, то изменений не вносится.
- Иначе, проверяется, может ли
T_tбыть приведён кTi; если да, тоT_tизменяется наTi. (Эта проверка также обусловлена тем, имеют ли все рассмотренные до сих пор исходные выражения неявные приведения.)
- Если нет, пытаетсяcя вычислить взаимный супертип
T_tиTi, который станет новым целевым типом.
Примеры:
#![allow(unused)] fn main() { let (a, b, c) = (0, 1, 2); // Для ветвей if let bar = if true { a } else if false { b } else { c }; // Для ветвей match let baw = match 42 { 0 => a, 1 => b, _ => c, }; // Для элементов массива let bax = [a, b, c]; // Для замыкания с несколькими операторами return let clo = || { if true { a } else if false { b } else { c } }; let baz = clo(); // Для проверки типа функции с несколькими операторами return fn foo() -> i32 { let (a, b, c) = (0, 1, 2); match 42 { 0 => a, 1 => b, _ => c, } } }
В этих примерах типы ba* находятся с помощью LUB-приведения. И компилятор
проверяет, является ли результат LUB-приведения a, b, c типом i32 в
процессе обработки функции foo.
Предостережение
Это описание, очевидно, неформально. Уточнение этого описания ожидается в рамках общей работы по более точной спецификации системы типов Rust.