การกำหนดและนำ Traits ไปใช้
เชี่ยวชาญ traits สำหรับกำหนดพฤติกรรมร่วมระหว่างชนิดข้อมูลต่าง ๆ ซึ่งคล้ายกับอินเทอร์เฟซในภาษาอื่น
การกำหนดและนำ Traits ไปใช้ เป็นบทเรียน Learn Rust Coding ฟรีบน CoddyKit นี่คือบทเรียนที่ 2 จากทั้งหมด 3 บทเรียน คุณสามารถอ่านบทเรียนทั้งหมดด้านล่างฟรี — จากนั้นลองปฏิบัติด้วยตัวคุณเองในเบราว์เซอร์พร้อมตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7 บทเรียนนี้เป็นส่วนหนึ่งของเส้นทางการเรียน Learn Rust Coding และความก้าวหน้าของคุณจะซิงค์ข้ามเว็บและแอป CoddyKit คอร์ส Learn Rust Coding มีบทเรียนทั้งหมด 3 บทเรียน
ทรีตใน Rust คืออะไร
ใน Rust ทรีตเป็นวิธีอันทรงพลังในการกำหนดพฤติกรรมร่วมกันระหว่างชนิดข้อมูลต่าง ๆ ลองคิดว่าทรีตมีลักษณะคล้ายส่วนติดต่อในภาษาอื่น
ทรีตบอก Rust ว่าชนิดข้อมูลหนึ่งมีความสามารถบางอย่าง หากชนิดข้อมูลนำทรีตไปใช้ ก็หมายความว่าชนิดข้อมูลนั้นมีเมธอดที่ทรีตกำหนดไว้
- ทรีตช่วยให้เกิดพหุสัณฐาน: ทำงานกับชนิดข้อมูลต่าง ๆ ด้วยวิธีที่เป็นแบบเดียวกัน
- ทรีตเป็นองค์ประกอบสำคัญของระบบชนิดข้อมูลและนามธรรมที่ปลอดภัยของ Rust
ประกาศทรีตแรกของคุณ
คุณกำหนดทรีตโดยใช้คีย์เวิร์ด trait ภายในทรีต ให้แสดงรายการลายเซ็นเมธอด (ชื่อ พารามิเตอร์ และชนิดค่าที่ส่งคืน) ที่ชนิดข้อมูลใดก็ตามซึ่งนำทรีตไปใช้จะต้องจัดเตรียม
เรามากำหนดทรีต Summary อย่างง่ายสำหรับรายการที่สามารถสรุปได้
trait Summary {
fn summarize(&self) -> String;
}
fn main() {
// Traits are definitions, not directly executable.
// We will implement and use them in later scenes!
println!("Trait 'Summary' defined.");
}การนำทรีตไปใช้กับชนิดข้อมูล
หากต้องการให้ชนิดข้อมูลใช้ทรีต ให้ใช้ไวยากรณ์ impl Trait for Type จากนั้นจัดเตรียมการนำไปใช้จริงสำหรับแต่ละเมธอดที่กำหนดไว้ในทรีต
ในที่นี้ เรานำ Summary ไปใช้กับโครงสร้าง NewsArticle
trait Summary {
fn summarize(&self) -> String;
}
struct NewsArticle {
headline: String,
location: String,
author: String,
content: String,
}
impl Summary for NewsArticle {
fn summarize(&self) -> String {
format!("{}, by {} ({})", self.headline, self.author, self.location)
}
}
fn main() {
let article = NewsArticle {
headline: String::from("Penguins win Stanley Cup!"),
location: String::from("Pittsburgh, PA"),
author: String::from("Iceburgh"),
content: String::from("The Pittsburgh Penguins have won..."),
};
println!("New article summary: {}", article.summarize());
}การใช้ฟังก์ชันที่มีขอบเขตของทรีต
เมื่อชนิดข้อมูลนำทรีตไปใช้แล้ว คุณสามารถเขียนฟังก์ชันที่รับชนิดข้อมูลใดก็ได้ซึ่งนำทรีตนั้นไปใช้ วิธีนี้ช่วยให้โค้ดยืดหยุ่นและเป็นเจเนอริก
ฟังก์ชัน notify สามารถรับชนิดข้อมูลใดก็ได้ที่นำ Summary ไปใช้
trait Summary {
fn summarize(&self) -> String;
}
struct NewsArticle {
headline: String,
location: String,
author: String,
content: String,
}
impl Summary for NewsArticle {
fn summarize(&self) -> String {
format!("{}, by {} ({})", self.headline, self.author, self.location)
}
}
struct Tweet {
username: String,
content: String,
reply: bool,
retweet: bool,
}
impl Summary for Tweet {
fn summarize(&self) -> String {
format!("{}: {}", self.username, self.content)
}
}
fn notify(item: &impl Summary) { // impl Summary is syntax sugar for a trait bound
println!("Breaking news! {}", item.summarize());
}
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,
};
let article = NewsArticle {
headline: String::from("Rust is awesome!"),
location: String::from("Internet"),
author: String::from("Rustacean"),
content: String::from("Rust's type system is amazing."),
};
notify(&tweet);
notify(&article);
}ทรีตที่มีพฤติกรรมเริ่มต้น
ทรีตยังสามารถจัดเตรียมการนำเมธอดไปใช้แบบเริ่มต้นได้ ซึ่งหมายความว่าชนิดข้อมูลไม่จำเป็นต้องนำทุกเมธอดไปใช้ หากมีการกำหนดการนำไปใช้แบบเริ่มต้นไว้
ชนิดข้อมูลเหล่านั้นเลือกใช้ค่าเริ่มต้น หรือแทนที่ด้วยตรรกะของตนเองก็ได้
trait Summary {
fn summarize_author(&self) -> String; // New required method
fn summarize(&self) -> String { // Default implementation
format!("(Read more from {})", self.summarize_author())
}
}
struct Tweet {
username: String,
content: String,
reply: bool,
retweet: bool,
}
impl Summary for Tweet {
fn summarize_author(&self) -> String { // Must implement required method
format!("@{}", self.username)
}
// We are using the default summarize() method here!
}
fn main() {
let tweet = Tweet {
username: String::from("dog_lover"),
content: String::from("My dog is the best!"),
reply: false,
retweet: false,
};
println!("Tweet summary: {}", tweet.summarize());
}ฟังก์ชันเจเนอริกที่ใช้ทรีต
เมื่อเขียนฟังก์ชันหรือโครงสร้างข้อมูลเจเนอริก คุณสามารถใช้ขอบเขตของทรีตเพื่อระบุว่าพารามิเตอร์ชนิดข้อมูลเจเนอริก T ต้องนำทรีตบางอย่างไปใช้
วิธีนี้ทำให้มั่นใจว่าเมธอดจากทรีตนั้นพร้อมใช้งานสำหรับ T
trait Displayable {
fn display(&self);
}
struct Point<T> {
x: T,
y: T,
}
// Implement Displayable for Point<i32>
impl Displayable for Point<i32> {
fn display(&self) {
println!("Point: ({}, {})", self.x, self.y);
}
}
// Generic function that works for any type T that implements Displayable
fn print_item<T: Displayable>(item: T) {
item.display();
}
fn main() {
let p = Point { x: 10, y: 20 };
print_item(p);
// This function only works for types that implement Displayable
// let s = String::from("Hello");
// print_item(s); // This would cause a compile-time error
}การรวมขอบเขตของทรีตหลายรายการ
บางครั้งชนิดข้อมูลเจเนอริกต้องนำทรีตมากกว่าหนึ่งรายการไปใช้ คุณสามารถระบุขอบเขตของทรีตหลายรายการโดยใช้ตัวดำเนินการ +
สำหรับกรณีที่ซับซ้อนยิ่งขึ้น โดยเฉพาะเมื่อมีพารามิเตอร์เจเนอริกจำนวนมาก ส่วนคำสั่ง where จะช่วยให้อ่านโค้ดได้ง่ายขึ้น
use std::fmt::Debug;
trait Printable {
fn print_info(&self);
}
struct Book {
title: String,
pages: u32,
}
impl Printable for Book {
fn print_info(&self) {
println!("Book: '{}' ({} pages)", self.title, self.pages);
}
}
impl Debug for Book { // Book also implements Debug trait
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Book {{ title: {}, pages: {} }}", self.title, self.pages)
}
}
// A function that requires its argument to be both Printable and Debug
fn process_item<T: Printable + Debug>(item: T) {
item.print_info();
println!("Debug info: {:?}", item);
}
fn main() {
let my_book = Book {
title: String::from("The Rust Book"),
pages: 600,
};
process_item(my_book);
}การส่งคืนชนิดข้อมูลด้วย `impl Trait`
ไวยากรณ์ impl Trait ไม่ได้ใช้ได้เฉพาะกับพารามิเตอร์ของฟังก์ชันเท่านั้น แต่ยังใช้ในตำแหน่งค่าที่ส่งคืนได้ด้วย ซึ่งมีประโยชน์เมื่อคุณต้องการส่งคืนชนิดข้อมูลที่นำเทรตหนึ่งไปใช้ แต่ไม่ต้องการเปิดเผยชนิดข้อมูลรูปธรรมที่แน่นอนของมัน
วิธีนี้ช่วยให้ลายเซ็นของฟังก์ชันเรียบง่ายขึ้น และทำให้ส่วนติดต่อการใช้งานของคุณยืดหยุ่นมากขึ้น
trait Greeter {
fn greet(&self) -> String;
}
struct FriendlyGreeter;
impl Greeter for FriendlyGreeter {
fn greet(&self) -> String {
String::from("Hello there!")
}
}
struct FormalGreeter;
impl Greeter for FormalGreeter {
fn greet(&self) -> String {
String::from("Greetings and salutations.")
}
}
// This function returns *some* type that implements Greeter
// The caller doesn't need to know if it's FriendlyGreeter or FormalGreeter
fn get_greeter(formal: bool) -> impl Greeter {
if formal {
FormalGreeter
} else {
FriendlyGreeter
}
}
fn main() {
let casual_greet = get_greeter(false);
println!("{}", casual_greet.greet());
let formal_greet = get_greeter(true);
println!("{}", formal_greet.greet());
}การต่อยอดด้วยซูเปอร์เทรต
บางครั้งเทรตหนึ่งอาจขึ้นต่อเทรตอีกอันหนึ่งตามหลักตรรกะ Rust อนุญาตให้คุณกำหนด ซูเปอร์เทรต ซึ่งหมายความว่าเทรตหนึ่งสามารถกำหนดให้ชนิดข้อมูลใดก็ตามที่นำเทรตนั้นไปใช้ ต้องนำเทรตเฉพาะอีกอันหนึ่งไปใช้ด้วย
การทำเช่นนี้ใช้ไวยากรณ์ trait SubTrait: SuperTrait ซึ่งมีความหมายคล้ายกับการกล่าวว่า “หากคุณทำ X ได้ คุณต้องทำ Y ได้ด้วย”
trait Printable {
fn print_content(&self);
}
// Debug is a supertrait of PrintableDebug
// Any type implementing PrintableDebug must also implement Debug
trait PrintableDebug: Printable + std::fmt::Debug {
fn print_debug_and_content(&self) {
self.print_content();
println!("Debug info: {:?}", self);
}
}
struct Item {
id: u32,
name: String,
}
impl Printable for Item {
fn print_content(&self) {
println!("Item ID: {}, Name: {}", self.id, self.name);
}
}
impl std::fmt::Debug for Item {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Item {{ id: {}, name: {} }}", self.id, self.name)
}
}
// Now we can implement PrintableDebug because Item implements both Printable and Debug
impl PrintableDebug for Item {}
fn main() {
let my_item = Item {
id: 101,
name: String::from("Widget A"),
};
my_item.print_debug_and_content();
}ทดสอบความรู้เรื่องเทรต
พิจารณาโค้ด Rust ต่อไปนี้ ข้อความใดต่อไปนี้เกี่ยวกับเทรตและขอบเขตของเทรตเป็นจริง?
เทรต: ชุดเครื่องมือสำหรับพฤติกรรม
เราได้สำรวจระบบ เทรต อันทรงพลังของ Rust แล้ว
- เทรตกำหนดชุดของเมธอดที่ชนิดข้อมูลสามารถนำไปใช้ได้ โดยทำหน้าที่คล้ายส่วนติดต่อ
- คุณสามารถนำเทรตไปใช้กับชนิดข้อมูลเฉพาะได้ด้วย
impl Trait for Type - ขอบเขตของเทรต (
T: Trait) ทำให้ฟังก์ชันและโครงสร้างข้อมูลแบบทั่วไปทำงานกับชนิดข้อมูลใดก็ได้ที่นำเทรตที่กำหนดไปใช้ - เทรตสามารถมี การนำไปใช้เริ่มต้น และคุณสามารถระบุ ขอบเขตของเทรตหลายรายการ (
T: Trait1 + Trait2) หรือใช้ ซูเปอร์เทรต ได้ - นอกจากนี้ยังใช้
impl Traitในตำแหน่งค่าที่ส่งคืนเพื่อซ่อนชนิดข้อมูลรูปธรรมได้
เทรตเป็นพื้นฐานสำคัญในการเขียนโค้ด Rust ที่ยืดหยุ่น นำกลับมาใช้ใหม่ได้ และปลอดภัย
เรียนรู้ Rust ด้วย AI tutor — ฟรี
เขียนและเรียกใช้โค้ดจริงในเบราว์เซอร์ของคุณ รับความช่วยเหลือทันทีจาก AI tutor 24/7 และเรียนรู้ต่อจากที่คุณหยุดบนเว็บหรือในแอป
- คอร์ส
- 39
- บทเรียน
- 144
คำถามที่พบบ่อย
บทเรียน “การกำหนดและนำ Traits ไปใช้” ฟรีหรือไม่
ใช่ — ข้อความเต็มของ “การกำหนดและนำ Traits ไปใช้” ฟรีให้อ่านที่นี่บนเว็บ เพื่อปฏิบัติแบบโต้ตอบ (ตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7) และปลดล็อคส่วนที่เหลือของคอร์ส Learn Rust Coding ให้อัปเกรดเป็น CoddyKit PRO คอร์ส Learn Rust Coding มีบทเรียนทั้งหมด 3 บทเรียน
คุณจะเรียนรู้อะไรในบทเรียน “การกำหนดและนำ Traits ไปใช้”
เชี่ยวชาญ traits สำหรับกำหนดพฤติกรรมร่วมระหว่างชนิดข้อมูลต่าง ๆ ซึ่งคล้ายกับอินเทอร์เฟซในภาษาอื่น คุณปฏิบัติ Learn Rust Coding ด้วยโค้ดที่ใช้งานได้จริงที่คุณเรียกใช้โดยตรงในเบราว์เซอร์ และติวเตอร์ AI ตลอด 24/7 ตอบคำถามของคุณขณะที่คุณไปผ่านบทเรียน
คุณต้องมีประสบการณ์ก่อนที่จะเริ่มเรียน Learn Rust Coding หรือไม่
ไม่จำเป็นต้องมีประสบการณ์มาก่อน Learn Rust Coding บน CoddyKit ออกแบบมาสำหรับผู้เริ่มต้นไปจนถึงผู้เรียนขั้นสูง คุณสามารถเริ่มต้นที่นี่หรือเริ่มจากตัวแรกและเรียนด้วยความเร็วของคุณเอง นี่คือบทเรียนที่ 2 จากทั้งหมด 3 บทเรียน
บทเรียน “การกำหนดและนำ Traits ไปใช้” ใช้เวลานานแค่ไหน
บทเรียน CoddyKit ส่วนใหญ่ใช้เวลาประมาณ 5–10 นาที แต่ละบทเรียนจึงสั้นและเป็นแบบโต้ตอบ คุณสามารถก้าวหน้าอย่างต่อเนื่องและกลับมาเรียนต่อจากตรงที่เพิ่งหยุดบนเว็บและแอปได้เลย
ฉันเขียนและรันโค้ดในบทเรียน Learn Rust Coding นี้ได้ไหม
ได้ บทเรียน Learn Rust Coding ทุกบทมีตัวแก้ไขโค้ดในตัว คุณจึงเขียนและรันโค้ดจริงได้เลยในเบราว์เซอร์ และได้รับข้อเสนอแนะจาก AI ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ
บทเรียนทั้งหมดในหลักสูตรนี้
- การเขียนโค้ดทั่วไปใน Rust
- การกำหนดและนำ Traits ไปใช้
- การใช้ Trait ขั้นสูง: ชนิดข้อมูลที่เกี่ยวข้อง