Generics let you write code that works over multiple types without duplication. Instead of writing separate functions for i32 and f64, you write one function with a type parameter T.
Generic cho phép bạn viết code hoạt động trên nhiều kiểu dữ liệu khác nhau mà không cần trùng lặp. Thay vì viết hàm riêng biệt cho i32 và f64, bạn viết một hàm duy nhất với tham số kiểu T.
1// Without generics — duplicated for each type2fn largest_i32(list: &[i32]) -> &i32 {3 let mut largest = &list[0];4 for item in list {5 if item > largest { largest = item; }6 }7 largest8}910// With generics — one function for any PartialOrd type11fn largest<T: PartialOrd>(list: &[T]) -> &T {12 let mut largest = &list[0];The type parameter T is declared in angle brackets <T> after the function name. The PartialOrd trait bound tells the compiler that T must support comparison.
Tham số kiểu T được khai báo trong ngoặc góc <T> sau tên hàm. Ràng buộc trait PartialOrd nói với trình biên dịch rằng T phải hỗ trợ phép so sánh.
Structs and enums can also be parameterized by type. You have already used generic types like Option<T> and Result<T, E> — they are defined this way.
Struct và enum cũng có thể được tham số hóa bởi kiểu. Bạn đã sử dụng các kiểu generic như Option<T> và Result<T, E> rồi — chúng được định nghĩa theo cách này.
1// Generic struct with one type parameter2#[derive(Debug)]3struct Point<T> {4 x: T,5 y: T,6}78// Generic struct with two type parameters9#[derive(Debug)]10struct Pair<T, U> {11 first: T,12 second: U,When implementing methods on a generic struct you must re-declare the type parameter after the impl keyword. You can also implement methods only for specific types using bounds.
Khi triển khai phương thức trên struct generic, bạn phải khai báo lại tham số kiểu sau từ khóa impl. Bạn cũng có thể triển khai các phương thức chỉ cho các kiểu cụ thể bằng cách sử dụng ràng buộc.
1#[derive(Debug)]2struct Point<T> {3 x: T,4 y: T,5}67// Methods available for all T8impl<T> Point<T> {9 fn new(x: T, y: T) -> Self {10 Point { x, y }11 }12A trait defines functionality that a type can share with other types. They are similar to interfaces in other languages but with some important differences.
Trait định nghĩa chức năng mà một kiểu có thể chia sẻ với các kiểu khác. Chúng tương tự như interface trong các ngôn ngữ khác, nhưng với một số điểm khác biệt quan trọng.
1// Define a trait2trait Summary {3 // Required method — implementors must provide this4 fn summarize(&self) -> String;56 // Required method with signature only7 fn author(&self) -> String;8}910trait Greet {11 fn name(&self) -> &str;12You can implement a trait for any type you define, and you can also implement standard library traits for your types. The orphan rule: at least one of the trait or the type must belong to your crate.
Bạn có thể triển khai trait cho bất kỳ kiểu nào bạn định nghĩa, và cũng có thể triển khai trait của thư viện chuẩn cho kiểu của bạn. Quy tắc mồ côi: ít nhất trait hoặc kiểu phải thuộc về crate của bạn.
1use std::fmt;23struct Matrix([[f64; 2]; 2]);45// Implement Display for our custom type6impl fmt::Display for Matrix {7 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {8 write!(9 f,10 "[{:.2} {:.2}]11[{:.2} {:.2}]",12 self.0[0][0], self.0[0][1],Traits can provide default implementations for some or all methods. Types implementing the trait can keep or override the defaults.
Trait có thể cung cấp các triển khai mặc định cho một số hoặc tất cả các phương thức. Các kiểu triển khai trait có thể giữ hoặc ghi đè các triển khai mặc định.
1trait Describable {2 fn name(&self) -> &str;34 // Default implementation that calls required method5 fn describe(&self) -> String {6 format!("I am a {}", self.name())7 }89 // Default with more logic10 fn verbose_describe(&self) -> String {11 let base = self.describe();12 format!("{base} [described at runtime]")Trait bounds specify that a generic type parameter must implement certain traits. This ensures you can call that trait's methods on the value.
Ràng buộc trait chỉ định rằng một tham số kiểu generic phải triển khai các trait nhất định. Điều này đảm bảo rằng bạn có thể gọi các phương thức của trait đó trên giá trị.
1use std::fmt::{Display, Debug};23// Syntax 1: inline bound with colon4fn print_item<T: Display>(item: T) {5 println!("{item}");6}78// Multiple bounds with +9fn print_debug_and_display<T: Display + Debug>(item: T) {10 println!("Display: {item}");11 println!("Debug: {item:?}");12}When you have many complex trait bounds, where clauses make function signatures more readable by moving the bounds to a separate location.
Khi bạn có nhiều ràng buộc trait phức tạp, mệnh đề where giúp chữ ký hàm dễ đọc hơn bằng cách di chuyển các ràng buộc ra một vị trí riêng biệt.
1use std::fmt::{Debug, Display};2use std::ops::Add;34// Hard to read with inline bounds5fn complex_inline<T: Display + Debug + Clone, U: Display + Add<Output = U>>(6 t: T,7 u: U,8) -> String {9 format!("{t:?}")10}1112// Easier to read with where clauseThe impl Trait syntax in return position lets you return some type that implements a trait without naming the concrete type. The compiler infers the actual type.
Cú pháp impl Trait trong vị trí trả về cho phép bạn trả về một kiểu nào đó triển khai một trait nhất định mà không cần đặt tên cho kiểu cụ thể. Trình biên dịch suy ra kiểu thực tế.
1// Return "some type that implements Iterator<Item = i32>"2fn make_counter(start: i32, end: i32) -> impl Iterator<Item = i32> {3 start..=end4}56// Return "some type that implements Fn(i32) -> i32"7fn make_adder(x: i32) -> impl Fn(i32) -> i32 {8 move |n| n + x9}1011trait Animal {12 fn sound(&self) -> &str;When you need to store or return values of different concrete types that all implement the same trait, use trait objects (dyn Trait). This uses dynamic dispatch through a vtable, unlike the static dispatch of generics.
Khi bạn cần lưu trữ hoặc trả về các giá trị của nhiều kiểu khác nhau đều triển khai cùng một trait, hãy dùng đối tượng trait (dyn Trait). Điều này sử dụng dispatch động (dynamic dispatch) thông qua vtable, khác với dispatch tĩnh (static dispatch) của generics.
1trait Draw {2 fn draw(&self);3}45struct Circle { radius: f64 }6struct Rectangle { width: f64, height: f64 }78impl Draw for Circle {9 fn draw(&self) { println!("Circle r={}", self.radius); }10}11impl Draw for Rectangle {12 fn draw(&self) { println!("Rect {}x{}", self.width, self.height); }Dùng generics (impl Trait) khi các kiểu đã biết tại thời điểm biên dịch — hiệu suất tốt hơn. Dùng dyn Trait khi bạn cần các kiểu khác nhau trong cùng một tập hợp — linh hoạt hơn.
- Display — user-friendly formatting, used by println!("{}")
Display — định dạng thân thiện với người dùng, dùng cho println!("{}")
- Debug — developer formatting, used by println!("{:?}"), derivable
Debug — định dạng dành cho lập trình viên, dùng cho println!("{:?}"), có thể derive
- Clone — explicit value duplication, commonly derived
Clone — nhân bản giá trị tường minh, thường được derive
- Copy — implicit duplication for small stack types (i32, bool, etc.)
Copy — nhân bản ngầm định cho các kiểu nhỏ trên stack (i32, bool, v.v.)
- PartialEq, Eq — equality comparison (== and !=)
PartialEq, Eq — so sánh bình đẳng (== và !=)
- PartialOrd, Ord — ordering comparison (<, >, <=, >=)
PartialOrd, Ord — so sánh thứ tự (<, >, <=, >=)
- Hash — required to use a type as a HashMap key
Hash — cần thiết để dùng kiểu làm khóa HashMap
- Default — provides a default value (commonly derived)
Default — cung cấp giá trị mặc định (thường được derive)
- Iterator — the core iteration protocol, requires next()
Iterator — giao thức lặp cốt lõi, yêu cầu phương thức next()
- From, Into — infallible type conversion
From, Into — chuyển đổi kiểu không thể thất bại
- TryFrom, TryInto — fallible type conversion, returns Result
TryFrom, TryInto — chuyển đổi kiểu có thể thất bại, trả về Result
1// Many traits can be derived automatically2#[derive(Debug, Clone, PartialEq, Eq, Hash, Default)]3struct Color {4 r: u8,5 g: u8,6 b: u8,7}89use std::fmt;10impl fmt::Display for Color {11 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {12 write!(f, "#{:02X}{:02X}{:02X}", self.r, self.g, self.b)Key Takeaways
Điểm Chính
- Generics let you write code that works with multiple typesGeneric cho phép viết code hoạt động với nhiều kiểu dữ liệu
- Trait bounds constrain generics to types with specific capabilitiesRàng buộc trait giới hạn generic cho các kiểu có khả năng cụ thể
- impl Trait uses static dispatch; dyn Trait uses dynamic dispatchimpl Trait dùng dispatch tĩnh; dyn Trait dùng dispatch động
- Default trait methods provide fallback implementationsPhương thức mặc định của trait cung cấp triển khai dự phòng
Practice
Test your understanding of this chapter
What does the PartialOrd trait bound in fn largest<T: PartialOrd> guarantee about T?
Ràng buộc trait PartialOrd trong fn largest<T: PartialOrd> đảm bảo điều gì về T?
What is the key behavioral difference between impl Trait and Box<dyn Trait> in return position?
Sự khác biệt hành vi chính giữa impl Trait và Box<dyn Trait> ở vị trí trả về là gì?
A trait's default method implementation can be overridden by any type that implements the trait.
Triển khai phương thức mặc định của trait có thể được ghi đè bởi bất kỳ kiểu nào triển khai trait đó.
The orphan rule allows you to implement any external trait for any external type, as long as you do it inside your own crate.
Quy tắc mồ côi cho phép bạn triển khai bất kỳ trait bên ngoài nào cho bất kỳ kiểu bên ngoài nào, miễn là bạn làm điều đó trong crate của mình.
Add a where clause with multiple bounds
Thêm mệnh đề where với nhiều ràng buộc
use std::fmt::{Debug, Display}; fn print_item<T>(item: T) where T: Display + , { println!("{item} / {item:?}"); }