Введение: почему преобразование чисел в Rust — это не шутка?
Вы когда-нибудь писали код на Python и спокойно делали int_var = 3.14, а потом float_var = int_var? В Python это работает, потому что типы динамические. Но в Rust, как в суровом часовщике, каждая шестерёнка должна быть своего размера. И если вы попытаетесь вставить u8 (0–255) в u16 — это одно, а если наоборот — может случиться "тихий" сброс данных или паника.
На Stack Overflow вопрос «How do I convert between numeric types safely and idiomatically?» собрал тысячи просмотров. И это не случайно: новички часто теряются между as, From и TryFrom. В этой статье мы разложим по полочкам все способы преобразования чисел в Rust, объясним, когда какой использовать, и покажем примеры, которые вы сможете сразу применить.
1. Простое приведение с as: быстро, но опасно
Самый простой способ — оператор as. Он работает, но с подводными камнями.
let big: u32 = 300;
let small: u8 = big as u8; // 300 не помещается в u8!
println!("{}", small); // Вывод: 44 (300 mod 256)
Что произошло? Rust просто взял младшие 8 бит числа 300 (0b1_0010_1100) и отбросил всё лишнее, оставив 0b0010_1100 = 44. Это называется усечением (truncation). Для u8 это может быть неожиданностью.
as безопасен только при расширении типа (например, u8 → u16), когда значение точно помещается. Для сужения — это бомба замедленного действия.
Когда использовать as? Только когда вы абсолютно уверены, что значение не выйдет за пределы целевого типа. Например, при работе с битовыми масками или низкоуровневыми протоколами.
2. Идиоматичный способ: трейты From и Into
Rust — язык, где ошибки должны быть явными. Поэтому стандартная библиотека предоставляет трейты From и Into для безопасного преобразования, которое никогда не падает.
Правило простое: если преобразование всегда успешно (нет потери данных), трейт реализован. Например:
let small: u8 = 42;
let big: u16 = small.into(); // Into автоматически вызывает From
// или
let big: u16 = u16::from(small);
println!("{}", big); // 42
Внимание: From реализован только для пар, где преобразование гарантированно безопасно. Например, u8 → u16, i32 → f64, bool → i32. Но u16 → u8 — нет, потому что может быть переполнение.
Попробуйте скомпилировать такой код:
let big: u16 = 256;
let small: u8 = big.into(); // Ошибка компиляции!
Компилятор скажет: "the trait `From
3. Когда преобразование может не получиться: TryFrom и TryInto
А что, если мы всё-таки хотим попробовать преобразовать u16 в u8, но с проверкой? На помощь приходят TryFrom и TryInto. Они возвращают Result, который нужно обработать.
use std::convert::TryFrom;
let big: u16 = 255;
match u8::try_from(big) {
Ok(small) => println!("Успех: {}", small),
Err(e) => println!("Ошибка: {}", e),
}
// Вывод: Успех: 255
let big: u16 = 256;
match u8::try_from(big) {
Ok(small) => println!("Успех: {}", small),
Err(e) => println!("Ошибка: {}", e),
}
// Вывод: Ошибка: TryFromIntError(())
Это идиоматичный Rust: вы явно обрабатываете возможную ошибку. Можно использовать unwrap(), если уверены на 100%, но лучше — match или ? в функциях, возвращающих Result.
Когда использовать TryFrom? Всегда, когда вы конвертируете из большего типа в меньший или из знакового в беззнаковый (например, i32 → u32).
4. Преобразование с плавающей точкой: f64 в f32 и обратно
С плавающими числами та же история. as сработает, но с потерей точности:
let precise: f64 = 3.141592653589793;
let approx: f32 = precise as f32;
println!("{}", approx); // 3.1415927 (потеря точности)
From для f32 → f64 реализован (расширение), а обратно — нет. Используйте TryFrom? Нет, для float нет TryFrom в стандартной библиотеке. Только as или ручная проверка.
Совет: Если вам нужно округление или контроль над потерей точности, используйте f64::round() и другие методы, а затем as.
5. Преобразование между целыми и плавающими
Здесь тоже свои нюансы. Преобразование f64 → i32 через as обрезает дробную часть (как trunc):
let x: f64 = 3.99;
let y: i32 = x as i32;
println!("{}", y); // 3
Если вам нужно округление, используйте round():
let y: i32 = x.round() as i32; // 4
Обратное преобразование (целое → плавающее) через From безопасно для i32 → f64, но i64 → f64 может потерять точность (не все 64-битные целые представимы в f64). Поэтому для i64 → f64 трейт From не реализован — используйте as и помните о возможной потере.