- vừa được xem lúc

Tìm hiểu về Traits trong Rust

0 0 30

Người đăng: Le Thanh Cong

Theo Viblo Asia

1. Giới thiệu

Traits là một khái niệm trong Rust, tương tự như interface ở các ngôn ngữ lập trình khác, mặc dù có số số điểm khác biệt.

Mỗi kiểu dữ liệu sẽ có một số phương thức được định nghĩa và chỉ biến thuộc kiểu dữ liệu đó mới có thể gọi. Nếu muốn chia sẻ một phương thức mà nhiều kiểu có thể dùng chung, tùy biến lại theo từng kiểu thì chúng ta sẽ cần dùng đến traits.

Ở đây chúng ta có 2 struct là NewArticleTweet có các trường dữ liệu khác nhau, cả 2 kiểu này đều đang cần một phương thức summarize để tóm tắt dữ liệu.

pub struct NewsArticle { pub headline: String, pub location: String, pub author: String, pub content: String,
} pub struct Tweet { pub username: String, pub content: String, pub reply: bool, pub retweet: bool,
}
pub trait Summary { fn summarize(&self) -> String;
}

Chúng ta định nghĩa traits với từ khóa trait, theo sau là Summary (tên của trait). Hàm summarize trả về kiểu String. Cũng như các hàm trong interface ở 1 số ngôn ngữ khác, hàm trong traits kết thúc bởi dấu ; và không có thân hàm. Bởi nó chỉ là nguyên mẫu để các kiểu dữ liệu khác implement mà thôi.

2. Implement Trait

Implement trait với kiểu dữ liệu

Giờ là lúc chúng ta triển khai phương thức summarize trên 2 struct NewsArticleTweet.

// src/lib.rs
pub trait Summary { fn summarize(&self) -> String;
} pub struct NewsArticle { pub headline: String, pub location: String, pub author: String, pub content: String,
} impl Summary for NewsArticle { fn summarize(&self) -> String { // trả về chuỗi gồm 3 thông tin: headline, author và localtion format!("{}, by {} ({})", self.headline, self.author, self.location) }
} pub struct Tweet { pub username: String, pub content: String, pub reply: bool, pub retweet: bool,
} impl Summary for Tweet { fn summarize(&self) -> String { // trả về chuỗi gồm 2 thông tin: username và content format!("{}: {}", self.username, self.content) }
}

Implement một trait cho một kiểu dữ liệu cũng tương tự như cách implement một method thông thường cho kiểu dữ liệu đó. Chỉ khác chút là sau từ khóa impl sẽ là tên của trait bạn muốn sử dụng rồi kèm theo từ khóa for.

Bây giờ thư viện đã triển khai đặc điểm Tóm tắt trên NewsArticle và Tweet, người dùng của thùng có thể gọi các phương thức đặc điểm trên các phiên bản NewsArticle và Tweet giống như cách chúng ta gọi các phương thức thông thường. Sự khác biệt duy nhất là người dùng phải đưa đặc điểm vào phạm vi cũng như các loại. Dưới đây là một ví dụ về cách thùng nhị phân có thể sử dụng thùng thư viện tổng hợp của chúng tôi:

Chúng ta vừa triển khai việc implement Summary trait cho 2 kiểu NewsArticleTweet. Bây giờ, chúng ta có thể gọi phương thức summarize từ các instance của NewsArticle hay Tweet như các phương thức thông thường. Tất nhiên là tùy theo instace đó thuộc kiểu NewsArticle hay Tweet mà hàm summarize tương ứng sẽ được thực thi.

use aggregator::{Summary, Tweet}; fn main() { let tweet = Tweet { username: String::from("horse_ebooks"), content: String::from( "of course, as you probably already know, people", ), reply: false, retweet: false, }; println!("1 new tweet: {}", tweet.summarize()); // 1 new tweet: horse_books: of course, as you probably already know, people
}

Implement mặc định (Default Implementations)

Đôi khi sẽ hữu ích khi có hành vi mặc định cho một số hoặc tất cả các phương thức trong một đặc điểm thay vì yêu cầu triển khai cho tất cả các phương thức trên mọi loại. Sau đó, khi chúng tôi triển khai đặc điểm trên một loại cụ thể, chúng tôi có thể giữ hoặc ghi đè hành vi mặc định của mỗi phương thức.

Đôi khi chúng ta sẽ cần một phương thức mặc định chung có các kiểu thay vì phải triển khai các phương thức trên từng kiểu khác nhau. Sau đó, nếu muốn thay đổi tùy theo kiểu dữ liệu, chúng ta có thể lựa chọn phương án ghi đè (override).

pub trait Summary { fn summarize(&self) -> String { String::from("(Read more...)") }
} pub struct NewsArticle { pub headline: String, pub location: String, pub author: String, pub content: String,
} // Sử dụng phương thức summarize mặc định của trait Summary
impl Summary for NewsArticle {} pub struct Tweet { pub username: String, pub content: String, pub reply: bool, pub retweet: bool,
} impl Summary for Tweet { // Ghi đè phương thức summarize mặc định fn summarize(&self) -> String { format!("{}: {}", self.username, self.content) }
}
use aggregator::{self, NewsArticle, Summary}; fn main() { let article = NewsArticle { headline: String::from("Penguins win the Stanley Cup Championship!"), location: String::from("Pittsburgh, PA, USA"), author: String::from("Iceburgh"), content: String::from( "The Pittsburgh Penguins once again are the best \ hockey team in the NHL.", ), }; println!("New article available! {}", article.summarize()); // New article available! (Read more...)
}

Phương thức mặc định có thể gọi các phương thức khác cùng trait với nó, ngay cả khi phương thức đó chỉ ở dạng nguyên mẫu (prototype). Tính năng này sẽ cung cấp nhiều thứ hữu ích, chúng ta cùng xem qua ví dụ dưới đây:

pub trait Summary { fn summarize_author(&self) -> String; // phương thức summarize gọi phương thức summarize_author để in ra kết quả fn summarize(&self) -> String { format!("(Read more from {}...)", self.summarize_author()) }
} pub struct Tweet { pub username: String, pub content: String, pub reply: bool, pub retweet: bool,
} impl Summary for Tweet { // Triển khai phương thức summarize_author in ra @ + tên tác giả fn summarize_author(&self) -> String { format!("@{}", self.username) }
}
use aggregator::{self, Summary, Tweet}; fn main() { let tweet = Tweet { username: String::from("horse_ebooks"), content: String::from( "of course, as you probably already know, people", ), reply: false, retweet: false, }; println!("1 new tweet: {}", tweet.summarize()); // 1 new tweet: (Read more from @horse_ebooks...)
}

Thay vì trả về 1 chuỗi cố định như ở ví dụ trước, thì phương thức summarize bằng cách gọi summarize_author đã tùy biến được tên tác giả Tweet trong kết quả trả về 😄

Trait làm tham số

Chúng ta đã biết cách xác định và triển khai trait ở phần trên. Ngoài ra, trait cũng có thể đóng vai trò làm tham số trong hàm khi được định nghĩa với từ khóa impl như sau:

pub fn notify(item: &impl Summary) { println!("Breaking news! {}", item.summarize());
}

Thay vì định nghĩa kiểu dữ liệu struct cần truyền vào NewArticle hay Tweet chẳng hạn, ta sẽ thay bằng &impl. Tham số đầu vào của hàm lúc đó sẽ nhận tất cả kiểu dữ liệu đã implement trait Summary. Nếu truyền vào 1 kiểu dữ liệu chưa implement trait, trình biên dịch sẽ báo lỗi.

pub trait Summary { fn summarize(&self) -> String;
} pub struct NewsArticle { pub headline: String, pub location: String, pub author: String, pub content: String,
} impl Summary for NewsArticle { fn summarize(&self) -> String { format!("{}, by {} ({})", self.headline, self.author, self.location) }
} pub struct Tweet { pub username: String, pub content: String, pub reply: bool, pub retweet: bool,
} impl Summary for Tweet { fn summarize(&self) -> String { format!("{}: {}", self.username, self.content) }
} pub fn notify(item: &impl Summary) { println!("Breaking news! {}", item.summarize());
} fn main() { let tweet = Tweet { username: String::from("Pool and Billiards for Dummies"), content: String::from( "Billards Pool", ), reply: false, retweet: false, }; let article = NewsArticle { headline: String::from("Penguins win the Stanley Cup Championship!"), location: String::from("Pittsburgh, PA, USA"), author: String::from("Iceburgh"), content: String::from( "The Pittsburgh Penguins once again are the best \ hockey team in the NHL.", ), }; notify(&tweet); // Breaking news! Pool and Billiards for Dummies: Billards Pool notify(&article); // Breaking news! Penguins win the Stanley Cup Championship!, by Iceburgh (Pittsburgh, PA, USA)
}

Trait Bound Syntax

Ngày cách định nghĩa trait là tham số của hàm như trên, chúng ta có thể sử dụng cú pháp theo kiểu generic như sau:

pub fn notify<T: Summary>(item: &T) { println!("Breaking news! {}", item.summarize());
}

Trong trường hợp hàm có nhiều tham số trait, các này sẽ giống code trông ngắn gọn hơn hẳn 😄

pub fn notify(item1: &impl Summary, item2: &impl Summary) { } pub fn notify<T: Summary>(item1: &T, item2: &T) { }

<T: Summary> ám chỉ rằng T chỉ nhận các kiểu dữ liệu cho implement trait Summary

Sử dụng toán tử +

Khi muốn tham số đầu vào là 1 kiểu dữ liệu implement cùng lúc nhiều trait thì sao nhỉ 🤔 ? Chúng ta sẽ sử dụng đến phép toán + 😄

pub fn notify(item: &(impl Summary + Display)) { } // hoặc pub fn notify<T: Summary + Display>(item: &T) { }

Kiểu dữ liệu truyền vào yêu cầu phải implement cả 2 trait

Sử dụng từ khóa where

Khi định nghĩa nhiều kiểu dữ liệu generic cộng với mỗi kiểu lại cần implement nhiều trait thì cú pháp định nghĩa sử dụng dấu + có phần khá cồng kềnh khi kéo dài lình thình dòng định nghĩa hàm.

fn some_function<T: Display + Clone, U: Clone + Debug>(t: &T, u: &U) -> i32 {

Thay vào đó, chúng ta có thể sử dụng từ khóa where, phần định nghĩa các trait cần thiết sẽ được đưa xuống dòng thứ 2, giúp code trông tường mình, gọn gàng hơn ở ví dụ trên.

fn some_function<T, U>(t: &T, u: &U) -> i32 where T: Display + Clone, U: Clone + Debug
{

3. Trả về kiểu trait

Trait có thể được định nghĩa là kiểu trả về trong 1 hàm, chúng ta cùng xem ví dụ dưới đây.

fn returns_summarizable() -> impl Summary { Tweet { username: String::from("horse_ebooks"), content: String::from( "of course, as you probably already know, people", ), reply: false, retweet: false, }
}

Tất nhiên kiểu dữ liệu trả về cần phải implement trait Summary 😄

Tuy nhiên, bạn chỉ có thể sử dụng Impl Trait nếu bạn đang trả lại một kiểu duy nhất. Ví dụ dưới đây trả về NewsArticle hoặc Tweet sẽ bị báo lỗi 😕

fn returns_summarizable(switch: bool) -> impl Summary { if switch { NewsArticle { headline: String::from( "Penguins win the Stanley Cup Championship!", ), location: String::from("Pittsburgh, PA, USA"), author: String::from("Iceburgh"), content: String::from( "The Pittsburgh Penguins once again are the best \ hockey team in the NHL.", ), } } else { Tweet { username: String::from("horse_ebooks"), content: String::from( "of course, as you probably already know, people", ), reply: false, retweet: false, } }
}

Tài liệu tham khảo

Traits: Defining Shared Behavior

Bình luận

Bài viết tương tự

- vừa được xem lúc

Chuyện cái comment

Chuyện cái comment. Chuyện rằng, có một ông bạn nọ có cái blog ở trên mạng, cũng có dăm.

0 0 31

- vừa được xem lúc

Đừng đánh nhau với borrow checker

Đừng đánh nhau với borrow checker. TL;DR: Đừng bao giờ đánh nhau với borrow checker, nó được sinh ra để bạn phải phục tùng nó .

0 0 28

- vừa được xem lúc

Chuyện biểu diễn ma trận trên máy tính

Chuyện biểu diễn ma trận trên máy tính. Cách đây mấy hôm mình có share cái screenshot trên Facebook, khoe linh tinh vụ mình đang viết lại cái CHIP-8 emulator bằng Rust.

0 0 43

- vừa được xem lúc

Rust và Lập trình Web

Rust và Lập trình Web. Bài viết được dịch lại từ bản gốc "Rust for the Web" đăng tại phiên bản blog tiếng Anh của mình.

0 0 36

- vừa được xem lúc

Viết ứng dụng đọc tin HackerNews bằng Rust

Viết ứng dụng đọc tin HackerNews bằng Rust. Dạo này mình toàn viết bài linh tinh, lâu rồi chưa thấy viết bài kĩ thuật nào mới nên hôm nay mình viết trở lại, mất công các bạn lại bảo mình không biết co

0 0 24

- vừa được xem lúc

Cài đặt Rust trên Arch Linux

Cài đặt Rust trên Arch Linux. Việc cài đặt Rust trên môi trường Arch Linux khá là đơn giản.

0 0 40