0Pricing
Learn Rust Coding · レッスン

トレイトの定義と実装

異なる型に共通の振る舞いを定義するトレイトを使いこなします。他言語のインターフェースに似た仕組みです。

「トレイトの定義と実装」はCoddyKit上の無料Learn Rust Codingレッスンです。 これはレッスン2/3です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これは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 構文を使います。その後、トレイトで定義された各メソッドの具体的な実装を記述します。

ここでは、NewsArticle 構造体に 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)
  }
}

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を実装する型を返したいものの、その具体的な型を公開したくない場合に便利です。

関数シグネチャを簡潔にし、APIの柔軟性を保つことができます。

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());
}

Supertraitの活用

あるtraitが別のtraitに論理的に依存する場合があります。Rustではsupertraitを定義できます。これは、あるtraitを実装する型に対して、別の特定のtraitも実装することを要求できるという意味です。

これは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();
}

Traitの知識を確認しましょう

次のRustコードを見てください。traitとtrait境界について、次のうち正しい説明はどれでしょうか。

Trait:振る舞いを定義するツールキット

Rustの強力なtraitシステムについて学んできました。

  • Traitは型が実装できるメソッドの集合を定義し、インターフェースのように機能します。
  • impl Trait for Typeを使用して、特定の型にtraitを実装します。
  • Trait境界(T: Trait)を使うと、ジェネリックな関数や構造体で、指定されたtraitを実装する任意の型を扱えます。
  • Traitにはデフォルト実装を定義でき、複数のtrait境界(T: Trait1 + Trait2)やsupertraitも使用できます。
  • impl Traitは戻り値の位置でも使用でき、具体的な型を隠せます。

Traitは、柔軟で再利用しやすく、安全なRustコードを書くための基盤です。

よくある質問

「トレイトの定義と実装」レッスンは無料ですか?

はい。「トレイトの定義と実装」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、Learn Rust Codingコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 Learn Rust Codingコースには全3レッスンが含まれています。

「トレイトの定義と実装」で何を学びますか?

異なる型に共通の振る舞いを定義するトレイトを使いこなします。他言語のインターフェースに似た仕組みです。 ブラウザで直接実行するハンズオンコードでLearn Rust Codingを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。

Learn Rust Codingを始めるのに経験は必要ですか?

事前経験は必要ありません。CoddyKitのLearn Rust Codingは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン2/3です。

「トレイトの定義と実装」レッスンにはどのくらい時間がかかりますか?

ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。

このLearn Rust Codingレッスンでコードを書いて実行できますか?

はい。すべてのLearn Rust Codingレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。

このコースのすべてのレッスン

  1. Rustでジェネリックコードを書く
  2. トレイトの定義と実装
  3. 高度なトレイトの利用:関連型
← Learn Rust Codingに戻る