Ép kiểu trong Rust
Rust không tự động chuyển một kiểu thành kiểu khác cho bạn — nếu bạn trộn một i32 với một f64 trong cùng một biểu thức, code sẽ không biên dịch được. Bạn phải chuyển đổi một cách tường minh. Đây là chủ ý: việc chuyển đổi ngầm là nguồn gốc phổ biến của lỗi ở các ngôn ngữ khác.
Chuyển đổi giữa các kiểu số bằng as
Phần tiêu đề “Chuyển đổi giữa các kiểu số bằng as”Dùng từ khóa as để ép (cast) một kiểu số thành kiểu số khác:
fn main() { let x: i32 = 10; let y: f64 = x as f64; // i32 -> f64
println!("{y}");}Điều này quan trọng hơn vẻ ngoài của nó — hãy thử chia hai số nguyên mà không ép kiểu, bạn sẽ nhận được phép chia số nguyên thay vì kết quả thập phân mà có lẽ bạn mong muốn:
fn main() { let a = 10; let b = 3;
println!("{}", a / b); // 3 (chia số nguyên) println!("{}", a as f64 / b as f64); // 3.3333333333333335}Ép xuống kiểu nhỏ hơn có thể mất dữ liệu
Phần tiêu đề “Ép xuống kiểu nhỏ hơn có thể mất dữ liệu”Ép từ một kiểu lớn sang một kiểu nhỏ hơn (như i32 sang u8) không báo lỗi — nó chỉ cắt bớt, và kết quả có thể khiến bạn bất ngờ:
fn main() { let big: i32 = 300; let small = big as u8; // u8 chỉ chứa được 0-255 println!("{small}"); // 44, không phải 300 — nó đã quay vòng}Ép số thực, ký tự và boolean
Phần tiêu đề “Ép số thực, ký tự và boolean”as dùng được với nhiều kiểu hơn chứ không chỉ số nguyên. Mỗi loại có một quy tắc đáng biết:
fn main() { println!("{}", 3.99_f64 as i32); // 3: phần thập phân bị cắt bỏ println!("{}", -3.99_f64 as i32); // -3: cắt về phía 0, không phải làm tròn xuống println!("{}", 3.99_f64.round() as i32); // 4: làm tròn trước nếu bạn muốn vậy println!("{}", 300.0_f64 as u8); // 255: quá lớn, nên dừng ở giá trị tối đa println!("{}", -5.0_f64 as u8); // 0: quá nhỏ, nên dừng ở giá trị tối thiểu println!("{}", 'A' as u32); // 65: con số đứng sau một char println!("{}", 97_u8 as char); // a: và chuyển ngược lại println!("{}", true as i32); // 1 (false sẽ là 0)}Kết quả:
3-34255065a1Vài điều cần ghi nhớ:
- Số thực sang số nguyên bỏ đi mọi thứ sau dấu thập phân. Nó không làm tròn. Hãy gọi
.round()trước nếu bạn muốn số nguyên gần nhất. - Số thực vượt phạm vi bị kẹp về giá trị gần nhất vừa với kiểu đích (
300.0thành255trong mộtu8). Điều này khác với số nguyên, vốn quay vòng (300đã thành44ở trên). - Ký tự có một con số đứng sau (mã Unicode của nó).
'A' as u32cho65. Chỉu8mới ép thẳng ngược vềcharđược; với số lớn hơn bạn dùngchar::from_u32, vì không phải con số nào cũng là một ký tự hợp lệ. - Boolean trở thành
1hoặc0. Chiều ngược lại thì không được:1 as boolkhông biên dịch được. Hãy dùng một phép so sánh nhưn != 0thay thế.
Chuyển chuỗi thành số
Phần tiêu đề “Chuyển chuỗi thành số”Ép kiểu bằng as chỉ dùng được giữa các kiểu số. Để biến văn bản thành một con số — chẳng hạn thứ người dùng gõ vào — hãy dùng .parse():
fn main() { let text = "42"; let number: i32 = text.parse().expect("không phải số hợp lệ");
println!("{}", number + 8);}Kết quả:
50.parse() có thể thất bại (lỡ văn bản hoàn toàn không phải số thì sao?), nên nó trả về một Result thay vì trả thẳng con số. Chúng ta chưa học Result — hiện tại, .expect("thông báo") là cách đơn giản để nói “nếu thất bại thì dừng chương trình với thông báo này”. Bạn sẽ thấy cách xử lý đúng đắn ở bài Result.
Chuyển số thành chuỗi
Phần tiêu đề “Chuyển số thành chuỗi”Chiều ngược lại đơn giản hơn — gần như mọi kiểu đều có thể chuyển thành String bằng .to_string():
fn main() { let number = 42; let text = number.to_string(); println!("{}", text + "!");}Luyện tập
Phần tiêu đề “Luyện tập”1. Điểm trung bình
Phần tiêu đề “1. Điểm trung bình”Lưu ba điểm kiểm tra dưới dạng giá trị i32: 7, 8 và 10. In tổng của chúng, rồi in điểm trung bình với hai chữ số thập phân. Gợi ý: nếu chia hai số nguyên bạn sẽ được 8, không phải 8.33, nên hãy ép tổng sang f64 trước. {:.2} in ra hai chữ số thập phân.
Xem lời giải
fn main() { let a: i32 = 7; let b: i32 = 8; let c: i32 = 10;
let sum = a + b + c; let average = sum as f64 / 3.0; // 25.0 / 3.0, không phải 25 / 3
println!("tổng: {sum}"); println!("trung bình: {:.2}", average);}Kết quả:
tổng: 25trung bình: 8.332. Giá từ văn bản
Phần tiêu đề “2. Giá từ văn bản”Một cửa hàng đưa cho bạn giá dưới dạng văn bản, "4.50", và số lượng dưới dạng i32, 3. Hãy biến văn bản thành f64, nhân với số lượng, rồi in ra Tổng: 13.5. Gợi ý: bạn cần cả hai công cụ trong trang này: .parse() cho văn bản và as f64 cho số lượng.
Xem lời giải
fn main() { let price_text = "4.50"; let quantity: i32 = 3;
let price: f64 = price_text.parse().expect("giá không hợp lệ"); let total = price * quantity as f64; // f64 * f64
println!("Tổng: {total}");}Kết quả:
Tổng: 13.5Không có as f64, Rust sẽ từ chối nhân một f64 với một i32, vì nó không bao giờ tự trộn các kiểu số cho bạn.
Tóm tắt
Phần tiêu đề “Tóm tắt”- Rust không bao giờ tự động chuyển kiểu — bạn luôn phải ép kiểu một cách tường minh.
- Dùng
asđể chuyển giữa các kiểu số:x as f64,x as u8. - Ép xuống kiểu nhỏ hơn sẽ lặng lẽ cắt bớt — nó không báo lỗi, nên hãy cẩn thận.
- Số thực sang số nguyên cắt bỏ phần thập phân (dùng
.round()trước để làm tròn).charvàboolcũng có thể ép sang số. - Dùng
.parse()để biến văn bản thành số, và.to_string()cho chiều ngược lại.
Kiểm tra nhanh
Phần tiêu đề “Kiểm tra nhanh”Kiểm tra nhanh
1. Chuyện gì xảy ra khi bạn chia hai số nguyên như 10 / 3 trong Rust mà không ép kiểu?
2. Chuyện gì xảy ra khi bạn ép 300_i32 as u8?
3. Vì việc chuyển đổi có thể thất bại, .parse() trả về gì khi chuyển chuỗi thành số?
Điểm: 0 / 3
Đọc thêm
Phần tiêu đề “Đọc thêm”- Casting — Rust by Example (tiếng Anh): rất nhiều phép ép
aschạy được, kèm chú thích về chính xác kết quả của từng cái. - Type cast expressions — The Rust Reference (tiếng Anh): bảng đầy đủ những phép ép được phép và quy tắc chính xác cho từng cái.
- To and from Strings — Rust by Example (tiếng Anh):
.to_string()và.parse()chi tiết hơn một chút. TryFrom— standard library docs (tiếng Anh): phép chuyển đổi có kiểm tra (u8::try_from(...)) dùng khi một giá trị có thể không vừa.- Numeric Types — The Rust Reference (tiếng Anh): kích thước và phạm vi của mọi kiểu số nguyên và số thực.