Hàm tổng quát
Thuộc phần Lập trình hướng đối tượng trong hành trình Rust của Coddy. Bài học 27 trên 61.
Generics không chỉ giới hạn ở các struct. Bạn cũng có thể viết các hàm độc lập hoạt động với mọi kiểu dữ liệu. Cách này hữu ích khi bạn cần một hàm tiện ích không thuộc về một struct cụ thể nhưng vẫn cần linh hoạt.
Cú pháp tương tự như những gì bạn đã thấy với struct. Khai báo tham số generic trong dấu ngoặc nhọn góc sau tên hàm, rồi dùng nó trong các tham số và kiểu trả về:
fn identity<T>(value: T) -> T {
value
}
Hàm identity này nhận một giá trị thuộc bất kỳ kiểu dữ liệu nào và trả về giá trị đó không thay đổi. <T> khai báo kiểu tổng quát, value: T nhận giá trị đó làm tham số, và -> T chỉ định kiểu trả về. Khi bạn gọi hàm, Rust tự suy luận kiểu cụ thể:
let num = identity(42); // T là i32
let text = identity("hello"); // T là &str
Bạn cũng có thể dùng nhiều tham số tổng quát trong các hàm, giống như với struct:
fn make_pair<T, U>(first: T, second: U) -> (T, U) {
(first, second)
}
let pair = make_pair(10, "ten"); // trả về (i32, &str)
Các hàm tổng quát (generic function) giúp bạn viết logic có thể tái sử dụng một lần rồi áp dụng cho nhiều kiểu dữ liệu, giảm trùng lặp code mà vẫn đảm bảo an toàn kiểu.
Thử thách
DễHãy tạo một module tiện ích với các hàm tổng quát có thể hoạt động với mọi kiểu dữ liệu! Bạn sẽ tạo các hàm độc lập để minh họa cách generics giúp code linh hoạt và có thể tái sử dụng mà không bị ràng buộc với một struct cụ thể.
Bạn sẽ sắp xếp code trong hai tệp:
utils.rs: Tạo một tập hợp các hàm tiện ích tổng quát công khai:wrap_in_pair<T>: nhận một giá trị và trả về một tuple chứa giá trị đó hai lần:(value, value). Kiểu dữ liệu cần triển khaiClone, vì vậy hãy dùng<T: Clone>swap<T, U>: nhận hai giá trị có thể thuộc các kiểu dữ liệu khác nhau và trả về chúng theo thứ tự đảo ngược trong một tuple(U, T)
main.rs: Nhập module tiện ích và minh họa cách các hàm tổng quát này hoạt động với nhiều kiểu dữ liệu. Cho thấy cùng một định nghĩa hàm có thể xử lý số nguyên, số thực và chuỗi một cách dễ dàng.
Trong tệp chính, hãy minh họa các hàm tiện ích bằng cách:
- Dùng
wrap_in_pairvới một số nguyên (đầu vào thứ nhất, được phân tích thànhi32) và in ra cả hai phần tử - Dùng
wrap_in_pairvới một chuỗi (đầu vào thứ hai) và in ra cả hai phần tử - Dùng
swapvới một số nguyên (đầu vào thứ ba, được phân tích thànhi32) và một chuỗi (đầu vào thứ tư), sau đó in ra kết quả đã hoán đổi
Đầu ra của bạn cần theo định dạng này:
Pair of ints: ({value}, {value})
Pair of strings: ({value}, {value})
Swapped: ({string}, {int})Ví dụ, với các đầu vào 5, hello, 42 và world:
Pair of ints: (5, 5)
Pair of strings: (hello, hello)
Swapped: (world, 42)Hãy chú ý cách wrap_in_pair hoạt động giống hệt nhau với cả số nguyên và chuỗi, còn swap xử lý hai kiểu dữ liệu hoàn toàn khác nhau. Đó chính là sự linh hoạt của các hàm tổng quát!
Bạn sẽ nhận bốn đầu vào: một số nguyên, một chuỗi, một số nguyên khác và một chuỗi khác.
Tự làm thử
mod utils;
use std::io;
fn main() {
// Đọc bốn đầu vào
let mut input1 = String::new();
io::stdin().read_line(&mut input1).expect("Failed to read line");
let num1: i32 = input1.trim().parse().expect("Invalid integer");
let mut input2 = String::new();
io::stdin().read_line(&mut input2).expect("Failed to read line");
let str1 = input2.trim().to_string();
let mut input3 = String::new();
io::stdin().read_line(&mut input3).expect("Failed to read line");
let num2: i32 = input3.trim().parse().expect("Invalid integer");
let mut input4 = String::new();
io::stdin().read_line(&mut input4).expect("Failed to read line");
let str2 = input4.trim().to_string();
// TODO: Dùng wrap_in_pair với num1 rồi in kết quả
// Định dạng: Pair of ints: ({value}, {value})
// TODO: Dùng wrap_in_pair với str1 rồi in kết quả
// Định dạng: Pair of strings: ({value}, {value})
// TODO: Dùng swap với num2 và str2, rồi in kết quả sau khi hoán đổi
// Định dạng: Swapped: ({string}, {int})
}
Bài học này có một bài trắc nghiệm ngắn. Bắt đầu bài học để trả lời và theo dõi tiến độ.
Tất cả bài học trong Lập trình hướng đối tượng
1Phương thức và hành vi
Nhập môn khối triển khaiTham số selfPhương thức có thể thay đổiHàm liên kếtNhiều khối triển khaiNối chuỗi phương thứcÔn tập: thao tác với hình chữ nhật4Dự án: Thú cưng ảo
Định nghĩa thú cưngCho thú cưng ăn7Các trait tiêu chuẩn
Trait DebugTrait DisplayClone và CopyCác trait so sánh bằngÔn tập: Điểm có thể in ra2Tính đóng gói và module
Kiến thức cơ bản về moduleTừ khóa publicTrường riêng tưHàm lấy giá trịHàm thiết lập giá trịÔn tập: tủ khóa an toàn5Kiểu tổng quát
Cấu trúc tổng quátPhương thức tổng quátNhiều kiểu tổng quátHàm tổng quátÔn tập: điểm tọa độTự luyện tập: Trình biên dịch Rust online