Learn Rust Coding · บทเรียน

การกำหนดและนำ Traits ไปใช้

เชี่ยวชาญ traits สำหรับกำหนดพฤติกรรมร่วมระหว่างชนิดข้อมูลต่าง ๆ ซึ่งคล้ายกับอินเทอร์เฟซในภาษาอื่น

บทเรียน 2 จาก 311 ขั้นตอน

การกำหนดและนำ 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 ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ

บทเรียนทั้งหมดในหลักสูตรนี้

  1. การเขียนโค้ดทั่วไปใน Rust
  2. การกำหนดและนำ Traits ไปใช้
  3. การใช้ Trait ขั้นสูง: ชนิดข้อมูลที่เกี่ยวข้อง
← กลับไปที่ Learn Rust Coding