Трейты From и Into в Rust
Что вы узнаете: трейты преобразования типов в Rust —
From<T>иInto<T>для преобразований, которые не могут завершиться ошибкой, иTryFromиTryIntoдля преобразований, которые могут. РеализуйтеFromи получитеIntoбесплатно. Заменяют операторы преобразования и конструкторы C++.
FromиInto— взаимодополняющие трейты для преобразования типов- Обычно типы реализуют трейт
From.String::from("Rust")преобразует&strвString. Когда существуетFrom<T> for U, Rust также предоставляетInto<U> for T, поэтомуlet s: String = "Rust".into();тоже работает.
struct Point {x: u32, y: u32}
// Создаём Point из кортежа
impl From<(u32, u32)> for Point {
fn from(xy : (u32, u32)) -> Self {
Point {x : xy.0, y: xy.1} // Создаём Point из элементов кортежа
}
}
fn main() {
let s = String::from("Rust");
let x = u32::from(true);
let p = Point::from((40, 42));
// let p : Point = (40,42).into(); // Альтернативная запись выражения выше
println!("s: {s} x:{x} p.x:{} p.y {}", p.x, p.y);
}
Упражнение: From и Into
- Реализуйте трейт
FromдляPointс преобразованием в типTransposePoint.TransposePointменяет местами элементыxиyуPoint
Решение (нажмите, чтобы развернуть)
struct Point { x: u32, y: u32 }
struct TransposePoint { x: u32, y: u32 }
impl From<Point> for TransposePoint {
fn from(p: Point) -> Self {
TransposePoint { x: p.y, y: p.x }
}
}
fn main() {
let p = Point { x: 10, y: 20 };
let tp = TransposePoint::from(p);
println!("Transposed: x={}, y={}", tp.x, tp.y); // x=20, y=10
// Через .into() — работает автоматически, когда реализован From
let p2 = Point { x: 3, y: 7 };
let tp2: TransposePoint = p2.into();
println!("Transposed: x={}, y={}", tp2.x, tp2.y); // x=7, y=3
}
// Вывод:
// Transposed: x=20, y=10
// Transposed: x=7, y=3
Трейт Default в Rust
Defaultможно использовать, чтобы задать значения по умолчанию для типа- Типы могут использовать макрос
DeriveсDefaultили предоставить собственную реализацию
- Типы могут использовать макрос
#[derive(Default, Debug)]
struct Point {x: u32, y: u32}
#[derive(Debug)]
struct CustomPoint {x: u32, y: u32}
impl Default for CustomPoint {
fn default() -> Self {
CustomPoint {x: 42, y: 42}
}
}
fn main() {
let x = Point::default(); // Создаёт Point{0, 0}
println!("{x:?}");
let y = CustomPoint::default();
println!("{y:?}");
}
Применение трейта Default
- Трейт
Defaultприменяется в нескольких случаях, в том числе- Частичное копирование с инициализацией остальных полей значениями по умолчанию
- Значение по умолчанию для типов
Optionв методах вродеunwrap_or_default()
#[derive(Debug)]
struct CustomPoint {x: u32, y: u32}
impl Default for CustomPoint {
fn default() -> Self {
CustomPoint {x: 42, y: 42}
}
}
fn main() {
let x = CustomPoint::default();
// Переопределяем y, а остальные поля оставляем по умолчанию
let y = CustomPoint {y: 43, ..CustomPoint::default()};
println!("{x:?} {y:?}");
let z : Option<CustomPoint> = None;
// Попробуйте заменить unwrap_or_default() на unwrap()
println!("{:?}", z.unwrap_or_default());
}
Другие преобразования типов в Rust
- Rust не поддерживает неявных преобразований типов, а для
явныхпреобразований используетсяas asследует использовать с осторожностью, потому что он может приводить к потере данных при сужении и т. п. В общем случае предпочтительнее использоватьinto()илиfrom(), где это возможно
fn main() {
let f = 42u8;
// let g : u32 = f; // Не скомпилируется
let g = f as u32; // Работает, но не рекомендуется. Подчиняется правилам сужения
let g : u32 = f.into(); // Предпочтительная форма; не может завершиться ошибкой, проверяется компилятором
// let k : u8 = g.into(); // Не скомпилируется; сужение может привести к потере данных
// Для операции сужения нужно использовать try_into
if let Ok(k) = TryInto::<u8>::try_into(g) {
println!("{k}");
}
}