Ключевое слово as
Приведение между типами
Ключевое слово as чаще всего используется для преобразования примитивных типов в другие примитивные типы, но также имеет и другие применения, включая преобразование указателей в адреса, адресов в указатели и указателей в другие указатели.
#![allow(unused)] fn main() { let thing1: u8 = 89.0 as u8; assert_eq!('B' as u32, 66); assert_eq!(thing1 as char, 'Y'); let thing2: f32 = thing1 as f32 + 10.5; assert_eq!(true as u8 + thing2 as u8, 100); }
В общем случае, любое приведение, которое может быть выполнено через указание типа, также может быть сделано с помощью as. Таким образом, вместо написания let x: u32 = 123 можно написать let x = 123 as u32 (примечание: в этой ситуации лучше использовать let x: u32 = 123). Однако обратное неверно; явное использование as позволяет несколько больше приведений, которые не разрешены неявно, таких как изменение типа сырого указателя или преобразование замыканий в сырые указатели.
as можно рассматривать как примитивный аналог From и Into: as работает только с примитивами (u8, bool, str, указатели, …), тогда как From и Into также работают с типами вроде String или Vec.
as также может использоваться с заполнителем _, когда целевой тип может быть выведен. Обратите внимание, что это может нарушить вывод типов, и обычно в таком коде следует использовать явный тип для ясности и стабильности. Это наиболее полезно при преобразовании указателей с помощью as *const _ или as *mut _, хотя метод cast рекомендуется вместо as *const _, и то же самое для as *mut _: эти методы делают намерение более понятным.
Переименование импортов
as также используется для переименования импортов в операторах use и extern crate:
#![allow(unused)] fn main() { use std::{mem as memory, net as network}; // Теперь можно использовать имена `memory` и `network` для ссылки на `std::mem` и `std::net`. }
Квалификация путей
С From и Into, как и со всеми трейтами, as используется для полностью квалифицированного пути — средства устранения неоднозначности связанных элементов, то есть функций, констант и типов. Например, если у вас есть тип, который реализует два трейта с одинаковыми именами методов (например, Into::<u32>::into и Into::<u64>::into), вы можете уточнить, какой метод использовать с помощью <MyThing as Into<u32>>::into(my_thing)¹. Это довольно многословно, но, к счастью, вывод типов в Rust обычно избавляет от необходимости в этом, хотя иногда это необходимо, особенно с методами, которые возвращают обобщённый тип, такие как Into::into, или методы, которые не принимают self. Это более распространено в макросах, где это может обеспечить необходимую гигиену.
Дальнейшее чтение
Для получения дополнительной информации о возможностях as см. Справочник по:
- выражениям приведения типов,
- переименованию импортированных сущностей,
- переименованию внешних крейтов,
- квалифицированным путям.
¹ Вероятно, вам никогда не следует использовать этот синтаксис с Into, а вместо этого писать T::from(my_thing). Просто так сложилось, что в стандартной библиотеке нет хороших примеров для этого синтаксиса. Также на момент написания компилятор обычно предлагает полностью квалифицированные пути для исправления неоднозначных вызовов Into::into, так что пример должен быть знаком.
Ключевое слово as в Rust: основные способы применения
1. Приведение числовых типов
fn main() { // Целочисленные преобразования let x: i32 = 42; let y: u64 = x as u64; println!("i32 {} -> u64 {}", x, y); // С плавающей точкой let float_num: f64 = 3.14; let int_num: i32 = float_num as i32; // Дробная часть отбрасывается println!("f64 {} -> i32 {}", float_num, int_num); // Беззнаковые в знаковые let unsigned: u8 = 200; let signed: i8 = unsigned as i8; // Может привести к переполнению println!("u8 {} -> i8 {}", unsigned, signed); // Между целыми разного размера let large: i128 = 1000; let small: i8 = large as i8; // Обрезание битов println!("i128 {} -> i8 {}", large, small); }
2. Преобразования с плавающей точкой
fn main() { // Float <-> Integer let pi: f32 = 3.14159; let pi_int = pi as i32; println!("f32 {} -> i32 {}", pi, pi_int); // 3 // Обратное преобразование let count: i32 = 42; let count_float = count as f64; println!("i32 {} -> f64 {}", count, count_float); // Между float типами let float32: f32 = 2.5; let float64: f64 = float32 as f64; println!("f32 {} -> f64 {}", float32, float64); }
3. Работа с указателями
fn main() { let x = 42; // Создание сырых указателей let raw_ptr: *const i32 = &x as *const i32; println!("Raw pointer: {:?}", raw_ptr); // Преобразование между типами указателей let mut y = 100; let const_ptr: *const i32 = &y as *const i32; let mut_ptr: *mut i32 = &mut y as *mut i32; // Преобразование указателей разных типов let byte_ptr = const_ptr as *const u8; println!("i32 pointer: {:?} -> u8 pointer: {:?}", const_ptr, byte_ptr); // Приведение к usize для арифметики указателей let addr = const_ptr as usize; println!("Pointer as usize: 0x{:x}", addr); }
4. Преобразования с enum
fn main() { // Enum без данных -> целые числа enum Color { Red = 0xff0000, Green = 0x00ff00, Blue = 0x0000ff, } let color_value = Color::Red as i32; println!("Color Red as i32: 0x{:x}", color_value); // C-like enums #[repr(u8)] enum Status { Ok = 0, Error = 1, Loading = 2, } let status_code = Status::Error as u8; println!("Status code: {}", status_code); // Bool -> integer let true_as_int = true as i32; // 1 let false_as_int = false as i32; // 0 println!("true as i32: {}, false as i32: {}", true_as_int, false_as_int); }
5. Работа с массивами и срезами
fn main() { // Приведение ссылок на массивы let arr: [i32; 4] = [1, 2, 3, 4]; let slice: &[i32] = &arr as &[i32]; println!("Array as slice: {:?}", slice); // Байтовые представления let value: u32 = 0x12345678; let bytes: &[u8] = unsafe { std::slice::from_raw_parts( &value as *const u32 as *const u8, std::mem::size_of::<u32>() ) }; println!("u32 0x{:x} as bytes: {:?}", value, bytes); // Приведение размеров срезов (осторожно!) let int_slice: &[i32] = &[1, 2, 3, 4]; let byte_slice: &[u8] = unsafe { std::slice::from_raw_parts( int_slice.as_ptr() as *const u8, int_slice.len() * std::mem::size_of::<i32>() ) }; println!("i32 slice as u8 slice: {:?}", byte_slice); }
6. Преобразования в трейт-объекты
trait Animal { fn speak(&self); } struct Dog; struct Cat; impl Animal for Dog { fn speak(&self) { println!("Woof!"); } } impl Animal for Cat { fn speak(&self) { println!("Meow!"); } } fn main() { let dog = Dog; let cat = Cat; // Создание трейт-объектов let animal1: &dyn Animal = &dog as &dyn Animal; let animal2: &dyn Animal = &cat as &dyn Animal; animal1.speak(); animal2.speak(); // Boxed trait objects let boxed_animal: Box<dyn Animal> = Box::new(Dog) as Box<dyn Animal>; boxed_animal.speak(); }
7. Явное приведение типов в выражениях
fn main() { // Для разрешения неоднозначности let result1 = 10 as f64 / 3.0; let result2 = 10 / 3 as f64; println!("10 as f64 / 3.0 = {}", result1); // 3.333... println!("10 / 3 as f64 = {}", result2); // 3.0 // В арифметических операциях let a: u8 = 100; let b: u8 = 200; let sum = a as u16 + b as u16; // Предотвращаем переполнение println!("{} + {} = {} (as u16)", a, b, sum); // При работе с индексами let arr = [10, 20, 30, 40]; let index: i32 = 2; let element = arr[index as usize]; // Приведение для индексации println!("arr[{}] = {}", index, element); }
8. Преобразования с char
fn main() { // Char <-> числовые типы let letter = 'A'; let letter_code = letter as u8; println!("'{}' as u8: {}", letter, letter_code); let number = 66u8; let character = number as char; println!("{} as char: '{}'", number, character); // Unicode символы let heart = '💖'; let heart_code = heart as u32; println!("'{}' as u32: 0x{:x}", heart, heart_code); // Осторожно: не все числовые значения - валидные char let invalid_char = 0xD800 as char; // Это surrogate pair, не валидный char! println!("Invalid char: {:?}", invalid_char); // Может привести к неопределенному поведению }
9. Приведение в замыканиях и функциях
fn main() { // Приведение типов функций fn normal_function(x: i32) -> i32 { x * 2 } let function_ptr: fn(i32) -> i32 = normal_function as fn(i32) -> i32; println!("Function result: {}", function_ptr(21)); // Замыкания (требуют осторожности) let closure = |x: i32| x + 1; let closure_ptr: fn(i32) -> i32 = closure as fn(i32) -> i32; println!("Closure result: {}", closure_ptr(10)); // Приведение к указателям на функции type MathFn = fn(i32, i32) -> i32; fn add(a: i32, b: i32) -> i32 { a + b } fn multiply(a: i32, b: i32) -> i32 { a * b } let operations: [MathFn; 2] = [ add as MathFn, multiply as MathFn, ]; for op in operations.iter() { println!("Operation result: {}", op(5, 3)); } }
10. Ограничения и безопасность
fn main() { // НЕЛЬЗЯ приводить между произвольными типами struct Point { x: i32, y: i32 } // let p = Point { x: 1, y: 2 }; // let invalid = p as String; // Ошибка компиляции! // Осторожно с потерями точности let large: u64 = u64::MAX; let small: u32 = large as u32; // Потеря данных! println!("u64::MAX {} -> u32 {}", large, small); // Приведение с насыщением (альтернатива) let saturated = large.min(u32::MAX as u64) as u32; println!("Saturated conversion: {}", saturated); }
Важные замечания:
asне выполняет проверки - это простое битовое преобразование- Может терять данные при числовых преобразованиях
- Небезопасно для некоторых преобразований указателей
- Альтернативы:
.try_into(),.into()для безопасных преобразований - Для сложных преобразований используйте
std::convert
Используйте as осторожно и только когда понимаете последствия преобразования!