Что такое try_into и когда его следует использовать в Rust?

В Rust метод try_into() используется для безопасного преобразования одного типа в другой, когда такое преобразование может завершиться ошибкой. Он является частью стандартизированного трейт-подхода к fallible conversions — т.е. таким конвертациям, которые могут не сработать в каких-то случаях.

Основан try_into() на трейте TryInto, который, в свою очередь, требует реализации трейта TryFrom.

Основная идея

Метод try_into() предоставляет способ превратить значение одного типа в другой с возможной ошибкой, возвращая тип Result.

use std::convert::TryInto;
let x: i32 = 150;
let y: u8 = x.try_into()?; // возможна ошибка, если x > 255

В этом примере i32 преобразуется в u8, но поскольку диапазон u8 ограничен 0–255, преобразование может завершиться с ошибкой (если значение x окажется вне допустимого диапазона). Поэтому результат — это Result<u8, E>.

Разница между into() и try_into()

into() — для гарантированных (инфаллибильных) преобразований. Работает только тогда, когда компилятор уверен, что преобразование всегда безопасно.

<br/>let a: i32 = 42;
let b: i64 = a.into(); // всегда безопасно: i32 помещается в i64
try_into()  для **потенциально ошибочных** (фоллабильных) преобразований.  
<br/><br/>let a: i32 = -1;
let b: u32 = a.try_into()?; // сработает ошибка: отрицательное значение не может стать u32

Где используется try_into()

Преобразования чисел разного размера или знака:

let x: i16 = -10;
let y: u16 = x.try_into()?; // Result&lt;u16, \_&gt;
  1. Преобразования строк в фиксированные массивы:
let s = "Hi!";
let arr: \[u8; 3\] = s.as_bytes().try_into()?; // преобразование slice в фиксированный массив
  1. В конструкторских функциях, которые принимают параметры ограниченного диапазона:
struct Percentage(u8);
impl TryFrom&lt;i32&gt; for Percentage {
type Error = &'static str;
fn try_from(value: i32) -> Result&lt;Self, Self::Error&gt; {
if value >= 0 && value <= 100 {
Ok(Percentage(value as u8))
} else {
Err("Процент должен быть от 0 до 100")
}
}
}
let percent = Percentage::try_from(150)?; // вернёт Err
  1. Вызов 150.try_into() тоже сработает, так как за кулисами он вызывает Percentage::try_from(150).

Требования к использованию try_into()

Для использования try_into() тип, из которого вы конвертируете, должен реализовывать TryInto<TargetType>, что означает: в целевом типе должен быть реализован TryFrom<SourceType>. Вот почему чаще всего вы реализуете TryFrom, а try_into() становится доступным автоматически.

Сигнатура трейта TryFrom

trait TryFrom&lt;T&gt;: Sized {
type Error;
fn try_from(value: T) -> Result&lt;Self, Self::Error&gt;;
}

После этого TryInto реализуется автоматически:

impl&lt;T, U&gt; TryInto&lt;U&gt; for T
where
U: TryFrom&lt;T&gt;,
{
type Error = U::Error;
fn try_into(self) -> Result&lt;U, Self::Error&gt; {
U::try_from(self)
}
}

Использование с ?

Так как try_into() возвращает Result, его удобно использовать с оператором ?, особенно в функциях, которые тоже возвращают Result:

fn parse_and_convert(input: &str) -> Result&lt;u8, String&gt; {
let number: i32 = input.parse().map_err(|\_| "Невозможно распарсить")?;
let value: u8 = number.try_into().map_err(|\_| "Выход за границы диапазона")?;
Ok(value)
}

Преимущества try_into()

  • Безопасность: исключает паники при ошибочных преобразованиях.

  • Явность: явно видно, что преобразование может не удаться.

  • Гибкость: удобно реализовать пользовательские правила преобразования с собственной логикой ошибок.