Menu
CoddyTech

Cú pháp ràng buộc trait

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 40 trên 61.

Đến giờ, bạn đã học cách định nghĩa trait và triển khai chúng cho các struct. Bạn cũng đã dùng generic để viết code linh hoạt. Bây giờ là lúc kết hợp các khái niệm này: dùng trait để giới hạn những kiểu dữ liệu mà một hàm generic có thể chấp nhận.

Xét một hàm tổng quát cần gọi một phương thức cụ thể trên tham số của nó. Nếu không có ràng buộc nào, Rust không đảm bảo rằng kiểu T có phương thức đó:

fn print_info<T>(item: T) {
    println!("{}", item.summarize());  // Lỗi! T có thể không có phương thức summarize()
}

Lời giải là một ràng buộc trait. Khi thêm : TraitName sau tham số tổng quát, bạn cho Rust biết rằng T phải triển khai trait đó:

trait Summary {
    fn summarize(&self) -> String;
}

fn print_info<T: Summary>(item: T) {
    println!("{}", item.summarize());  // Giờ Rust biết phương thức (method) này tồn tại
}

Cú pháp <T: Summary> có nghĩa là “T là bất kỳ kiểu nào triển khai Summary”. Giờ đây, hàm chỉ chấp nhận những kiểu có hành vi bắt buộc. Nếu bạn thử truyền một kiểu không triển khai Summary, trình biên dịch sẽ từ chối kiểu đó và đưa ra lỗi rõ ràng.

Mẫu này rất hữu ích vì cho phép bạn viết đoạn code dùng chung mà vẫn truy cập được chức năng cụ thể. Hàm (function) của bạn vẫn linh hoạt và hoạt động với mọi kiểu dữ liệu, nhưng chỉ những kiểu dữ liệu cung cấp hành vi bạn cần.

challenge icon

Thử thách

Dễ

Hãy xây dựng một hệ thống thông báo dùng giới hạn trait để đảm bảo chỉ có thể gửi những tin nhắn được định dạng đúng! Bạn sẽ tạo một hàm tổng quát chấp nhận mọi kiểu dữ liệu triển khai một trait cụ thể, qua đó minh họa cách giới hạn trait ràng buộc các tham số tổng quát.

Bạn sẽ sắp xếp code trong hai tệp:

  • notification.rs: Định nghĩa một trait Notify công khai có phương thức message nhận &self và trả về một String. Sau đó, tạo hai struct công khai triển khai trait này:
    • Email: có thuộc tính subject công khai (String). Phương thức message sẽ trả về Email: {subject}
    • SMS: có thuộc tính content công khai (String). Phương thức message sẽ trả về SMS: {content}
    Cuối cùng, tạo một hàm tổng quát công khai tên là send_notification, chấp nhận mọi kiểu dữ liệu T triển khai trait Notify. Hàm này sẽ in ra kết quả khi gọi message() trên đối tượng.
  • main.rs: Đưa module thông báo vào và dùng các đầu vào được cung cấp để tạo một instance Email và một instance SMS. Gọi send_notification với từng instance để chứng minh hàm tổng quát của bạn hoạt động với mọi kiểu dữ liệu triển khai Notify.

Điểm mấu chốt là send_notification không biết kiểu dữ liệu cụ thể mà nó nhận được. Hàm chỉ biết kiểu dữ liệu đó có thể tạo ra một tin nhắn. Giới hạn trait T: Notify đảm bảo khả năng này.

Đầu ra của bạn sẽ hiển thị cả hai thông báo đã được gửi:

Email: {subject}
SMS: {content}

Ví dụ, với đầu vào Meeting Tomorrow và On my way!:

Email: Meeting Tomorrow
SMS: On my way!

Bạn sẽ nhận hai đầu vào: tiêu đề email và nội dung SMS.

Tự làm thử

mod notification;

use notification::{Email, SMS, send_notification};

fn main() {
    // Đọc dữ liệu đầu vào
    let mut subject = String::new();
    std::io::stdin().read_line(&mut subject).expect("Failed to read line");
    let subject = subject.trim().to_string();
    
    let mut content = String::new();
    std::io::stdin().read_line(&mut content).expect("Failed to read line");
    let content = content.trim().to_string();
    
    // TODO: Tạo một instance Email với subject
    
    // TODO: Tạo một instance SMS với content
    
    // TODO: Gọi send_notification với email
    
    // TODO: Gọi send_notification với sms
}
quiz iconTự kiểm tra

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

Tự luyện tập: Trình biên dịch Rust online