Display
Trait Display cho phép bạn định nghĩa cách một type được hiển thị khi sử dụng format string {}. Đây là trait quan trọng để tạo ra output thân thiện với người dùng.
Display vs Debug
Rust có hai trait chính cho việc formatting:
| Trait | Format Specifier | Mục đích |
|---|---|---|
Debug | {:?} hoặc {:#?} | Development, debugging (technical output) |
Display | {} | User-facing output (human-readable) |
#[derive(Debug)] struct Point { x: i32, y: i32, } fn main() { let p = Point { x: 3, y: 4 }; // Debug - technical output println!("{:?}", p); // Point { x: 3, y: 4 } // Display - compile error! Display không được derive tự động // println!("{}", p); // ❌ Error: `Point` doesn't implement `Display` }
Implement Display
Để implement Display, bạn cần định nghĩa method fmt:
use std::fmt; struct Point { x: i32, y: i32, } impl fmt::Display for Point { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "({}, {})", self.x, self.y) } } fn main() { let p = Point { x: 3, y: 4 }; println!("{}", p); // In ra: (3, 4) }
Ví dụ với Enum
use std::fmt; enum Status { Active, Inactive, Pending, } impl fmt::Display for Status { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Status::Active => write!(f, "Đang hoạt động"), Status::Inactive => write!(f, "Không hoạt động"), Status::Pending => write!(f, "Đang chờ xử lý"), } } } fn main() { let status = Status::Active; println!("Trạng thái: {}", status); // In ra: Trạng thái: Đang hoạt động }
Ví dụ với Struct phức tạp
use std::fmt; struct Person { name: String, age: u32, email: String, } impl fmt::Display for Person { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!( f, "{} (tuổi {}) - Email: {}", self.name, self.age, self.email ) } } fn main() { let person = Person { name: String::from("Nguyễn Văn A"), age: 25, email: String::from("nguyenvana@example.com"), }; println!("{}", person); // In ra: Nguyễn Văn A (tuổi 25) - Email: nguyenvana@example.com }
Display với nhiều format options
Bạn có thể sử dụng các format options từ Formatter:
use std::fmt; struct Currency { amount: f64, symbol: &'static str, } impl fmt::Display for Currency { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { // Sử dụng precision từ format string if let Some(precision) = f.precision() { write!(f, "{}{:.precision$}", self.symbol, self.amount, precision = precision) } else { write!(f, "{}{:.2}", self.symbol, self.amount) } } } fn main() { let price = Currency { amount: 1234.5678, symbol: "$" }; println!("{}", price); // $1234.57 (mặc định 2 chữ số) println!("{:.0}", price); // $1235 (không có phần thập phân) println!("{:.4}", price); // $1234.5678 (4 chữ số thập phân) }
Display với Vec và collection
use std::fmt; struct NumberList { numbers: Vec<i32>, } impl fmt::Display for NumberList { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let numbers_str: Vec<String> = self.numbers .iter() .map(|n| n.to_string()) .collect(); write!(f, "[{}]", numbers_str.join(", ")) } } fn main() { let list = NumberList { numbers: vec![1, 2, 3, 4, 5], }; println!("{}", list); // In ra: [1, 2, 3, 4, 5] }
Kết hợp Debug và Display
Thường thì bạn implement cả hai:
use std::fmt; #[derive(Debug)] struct User { id: u32, username: String, is_active: bool, } impl fmt::Display for User { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "@{}", self.username) } } fn main() { let user = User { id: 1, username: String::from("duyet"), is_active: true, }; // Display - user-facing println!("User: {}", user); // In ra: User: @duyet // Debug - developer-facing println!("Debug: {:?}", user); // In ra: Debug: User { id: 1, username: "duyet", is_active: true } }
Error handling với Display
Display thường được sử dụng khi implement custom error types:
use std::fmt; use std::error::Error; #[derive(Debug)] enum MyError { NotFound(String), InvalidInput(String), NetworkError, } impl fmt::Display for MyError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { MyError::NotFound(item) => write!(f, "Không tìm thấy: {}", item), MyError::InvalidInput(msg) => write!(f, "Dữ liệu không hợp lệ: {}", msg), MyError::NetworkError => write!(f, "Lỗi kết nối mạng"), } } } impl Error for MyError {} fn main() { let error = MyError::NotFound(String::from("user.txt")); println!("Lỗi: {}", error); // In ra: Lỗi: Không tìm thấy: user.txt }
Display với nested types
use std::fmt; struct Address { street: String, city: String, } impl fmt::Display for Address { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}, {}", self.street, self.city) } } struct Company { name: String, address: Address, } impl fmt::Display for Company { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{} tại {}", self.name, self.address) } } fn main() { let company = Company { name: String::from("Rust Corp"), address: Address { street: String::from("123 Nguyễn Huệ"), city: String::from("TP.HCM"), }, }; println!("{}", company); // In ra: Rust Corp tại 123 Nguyễn Huệ, TP.HCM }
So sánh với các ngôn ngữ khác
Python
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __str__(self): # Tương đương Display
return f"({self.x}, {self.y})"
def __repr__(self): # Tương đương Debug
return f"Point(x={self.x}, y={self.y})"
p = Point(3, 4)
print(p) # Gọi __str__
print(repr(p)) # Gọi __repr__
Java
class Point {
private int x, y;
public Point(int x, int y) {
this.x = x;
this.y = y;
}
@Override
public String toString() { // Tương đương Display
return String.format("(%d, %d)", x, y);
}
}
Point p = new Point(3, 4);
System.out.println(p); // Gọi toString()
Rust - Type Safe
Ưu điểm của Rust:
- Compiler bắt buộc phải implement Display nếu muốn dùng
{} - Không thể vô tình in ra kiểu dữ liệu chưa implement Display
- Phân biệt rõ ràng giữa Debug (technical) và Display (user-facing)
Best Practices
1. Display cho user, Debug cho developer
#![allow(unused)] fn main() { // ✅ Tốt impl fmt::Display for User { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.username) // Simple, user-friendly } } // ❌ Tránh impl fmt::Display for User { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "User {{ id: {}, username: {} }}", self.id, self.username) // Quá technical, nên dùng Debug } } }
2. Concise và meaningful
#![allow(unused)] fn main() { // ✅ Tốt write!(f, "Error: File not found") // ❌ Tránh - quá dài dòng write!(f, "An error has occurred during the file reading operation: The specified file could not be found in the filesystem") }
3. Consistent formatting
#![allow(unused)] fn main() { // ✅ Tốt - consistent format cho cùng type impl fmt::Display for Date { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{:04}-{:02}-{:02}", self.year, self.month, self.day) } } }
Tổng kết
Display trait là công cụ mạnh mẽ để:
- Tạo output thân thiện với người dùng
- Implement custom formatting cho types
- Tích hợp với error handling
- Cung cấp API nhất quán cho printing
Khi implement Display:
- Giữ output đơn giản, dễ đọc
- Dùng cho user-facing messages
- Kết hợp với Debug cho developer output
- Follow consistent formatting conventions