Что такое 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<u16, \_>
- Преобразования строк в фиксированные массивы:
let s = "Hi!";
let arr: \[u8; 3\] = s.as_bytes().try_into()?; // преобразование slice в фиксированный массив
- В конструкторских функциях, которые принимают параметры ограниченного диапазона:
struct Percentage(u8);
impl TryFrom<i32> for Percentage {
type Error = &'static str;
fn try_from(value: i32) -> Result<Self, Self::Error> {
if value >= 0 && value <= 100 {
Ok(Percentage(value as u8))
} else {
Err("Процент должен быть от 0 до 100")
}
}
}
let percent = Percentage::try_from(150)?; // вернёт Err
- Вызов 150.try_into() тоже сработает, так как за кулисами он вызывает Percentage::try_from(150).
Требования к использованию try_into()
Для использования try_into() тип, из которого вы конвертируете, должен реализовывать TryInto<TargetType>, что означает: в целевом типе должен быть реализован TryFrom<SourceType>. Вот почему чаще всего вы реализуете TryFrom, а try_into() становится доступным автоматически.
Сигнатура трейта TryFrom
trait TryFrom<T>: Sized {
type Error;
fn try_from(value: T) -> Result<Self, Self::Error>;
}
После этого TryInto реализуется автоматически:
impl<T, U> TryInto<U> for T
where
U: TryFrom<T>,
{
type Error = U::Error;
fn try_into(self) -> Result<U, Self::Error> {
U::try_from(self)
}
}
Использование с ?
Так как try_into() возвращает Result, его удобно использовать с оператором ?, особенно в функциях, которые тоже возвращают Result:
fn parse_and_convert(input: &str) -> Result<u8, String> {
let number: i32 = input.parse().map_err(|\_| "Невозможно распарсить")?;
let value: u8 = number.try_into().map_err(|\_| "Выход за границы диапазона")?;
Ok(value)
}
Преимущества try_into()
-
Безопасность: исключает паники при ошибочных преобразованиях.
-
Явность: явно видно, что преобразование может не удаться.
-
Гибкость: удобно реализовать пользовательские правила преобразования с собственной логикой ошибок.