Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Приведения типов

Приведения типов — это неявные операции, которые изменяют тип значения. Они происходят автоматически в определённых местах и сильно ограничены в том, какие типы фактически могут приводиться.

Любые преобразования, разрешённые приведением, также могут быть явно выполнены с помощью оператора приведения типа, 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 T
    • Box<T>

    и где U может быть получен из T с помощью неразмерного приведения.

  • Типы функциональных элементов к указателям на функции (fn pointers)
  • Замыкания без захвата к указателям на функции (fn pointers)
  • ! к любому 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.