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