RustのTraitへの深い探求:相続、合成、および多形
Takashi Yamamoto
Infrastructure Engineer · Leapcell

Traitとは?
Rustでは、traitは共有の振る舞いを定義する方法です。これにより、型が実装しなければならないメソッドを指定し、ポリモーフィズムとインターフェースの抽象化を可能にします。
Printable
という名前のtraitを定義する簡単な例を以下に示します。これにはprint
というメソッドが含まれます。
trait Printable { fn print(&self); }
Traitの定義と実装
Traitを定義するには、trait
キーワードを使用し、その後にtrait名と一対の波括弧を付けます。波括弧の中に、traitに含まれるメソッドを定義します。
Traitを実装するには、impl
キーワードを使用し、その後にtrait名、for
キーワード、およびtraitを実装する型を付けます。波括弧の中には、traitで定義されたすべてのメソッドの実装を提供する必要があります。
以下は、以前に定義したPrintable
traitをi32
型に実装する方法を示す例です。
impl Printable for i32 { fn print(&self) { println!("{}", self); } }
この例では、Printable
traitをi32
型に実装し、print
メソッドの簡単な実装を提供しました。
Traitの継承と合成
Rustでは、継承と合成を通じて既存のtraitを拡張できます。継承により、新しいtrait内で親traitで定義されたメソッドを再利用でき、合成により、新しいtraitで複数の異なるtraitを使用できます。
以下は、継承を使用してPrintable
traitを拡張する方法を示す例です。
trait PrintableWithLabel: Printable { fn print_with_label(&self, label: &str) { print!("{}: ", label); self.print(); } }
この例では、PrintableWithLabel
という名前の新しいtraitを定義します。これはPrintable
traitから継承します。これは、PrintableWithLabel
を実装するすべての型がPrintable
も実装する必要があることを意味します。さらに、値を印刷する前にラベルを印刷する新しいメソッドprint_with_label
を提供します。
以下は、合成を使用して新しいtraitを定義する方法を示す別の例です。
trait DisplayAndDebug: Display + Debug {}
この例では、標準ライブラリの2つのtraitであるDisplay
とDebug
で構成される新しいtraitDisplayAndDebug
を定義します。これは、DisplayAndDebug
を実装するすべての型がDisplay
とDebug
の両方を実装する必要があることを意味します。
パラメータおよび戻り値としてのTrait
Rustでは、関数シグネチャでtraitをパラメータおよび戻り値として使用できるため、コードがよりジェネリックで柔軟になります。
以下は、PrintableWithLabel
traitを関数のパラメータとして使用する方法を示す例です。
fn print_twice<T: PrintableWithLabel>(value: T) { value.print_with_label("First"); value.print_with_label("Second"); }
この例では、ジェネリックパラメータT
を受け取るprint_twice
という名前の関数を定義します。パラメータはPrintableWithLabel
traitを実装する必要があります。関数本体内では、パラメータのprint_with_label
メソッドを呼び出します。
以下は、traitを関数の戻り値として使用する方法を示す例です。
fn get_printable() -> impl Printable { 42 }
ただし、fn get_printable() -> impl Printable { 42 }
は正しくありません。これは、42
が整数であり、Printable
traitを実装していないためです。
正しい方法は、Printable
traitを実装する型を返すことです。たとえば、Printable
をi32
型に実装する場合、次のように記述できます。
impl Printable for i32 { fn print(&self) { println!("{}", self); } } fn get_printable() -> impl Printable { 42 }
この例では、Printable
traitをi32
型に実装し、print
メソッドの簡単な実装を提供します。次に、get_printable
関数で、i32
値42
を返します。i32
型はPrintable
traitを実装するため、このコードは正しいです。
Traitオブジェクトと静的ディスパッチ
Rustでは、ポリモーフィズムを静的ディスパッチと動的ディスパッチの2つの方法で実現できます。
- 静的ディスパッチは、ジェネリクスを使用して実現されます。ジェネリックパラメータを使用すると、コンパイラは可能な型ごとに個別のコードを生成します。これにより、関数呼び出しをコンパイル時に決定できます。
- 動的ディスパッチは、traitオブジェクトを使用して実現されます。traitオブジェクトを使用すると、コンパイラはtraitを実装する任意の型を処理できる汎用コードを生成します。これにより、関数呼び出しを実行時に決定できます。
以下は、静的ディスパッチと動的ディスパッチの両方を使用する方法を示す例です。
fn print_static<T: Printable>(value: T) { value.print(); } fn print_dynamic(value: &dyn Printable) { value.print(); }
この例では、
print_static
は、Printable
traitを実装する必要がある**ジェネリックパラメータT
**を使用します。この関数が呼び出されると、コンパイラは渡される型ごとに個別のコードを生成します(静的ディスパッチ)。print_dynamic
は、パラメータとして**traitオブジェクト(&dyn Printable
)**を使用します。これにより、動的ディスパッチが可能になり、関数はPrintable
traitを実装する任意の型を処理できます。
関連型とジェネリック制約
Rustでは、関連型とジェネリック制約を使用して、より複雑なtraitを定義できます。
関連型
関連型を使用すると、特定のtraitに関連付けられた型を定義できます。これは、関連型に依存するメソッドを定義するのに役立ちます。
以下は、関連型を使用してAdd
という名前のtraitを定義する例です。
trait Add<RHS = Self> { type Output; fn add(self, rhs: RHS) -> Self::Output; }
この例では、
Add
という名前のtraitを定義します。- これには、
add
メソッドの戻り値の型を表す関連型Output
が含まれています。 RHS
ジェネリックパラメータは、加算演算の右辺を指定し、デフォルトはSelf
です。
ジェネリック制約
ジェネリック制約を使用すると、ジェネリックパラメータが特定の条件(特定のtraitの実装など)を満たす必要があることを指定できます。
以下は、SummableIterator
という名前のtraitでジェネリック制約を使用する方法を示す例です。
use std::iter::Sum; trait SummableIterator: Iterator where Self::Item: Sum, { fn sum(self) -> Self::Item { self.fold(Self::Item::zero(), |acc, x| acc + x) } }
この例では、
- 標準の
Iterator
traitを拡張するSummableIterator
traitを定義します。 - ジェネリック制約(
where Self::Item: Sum
)を使用して、イテレータのItem
型がSum
traitを実装する必要があることを指定します。 sum
メソッドは、イテレータ内のすべての要素の合計を計算します。
例:Traitを使用したポリモーフィズムの実装
以下は、PrintableWithLabel
traitを使用してポリモーフィズムを実現する方法を示す例です。
struct Circle { radius: f64, } impl Printable for Circle { fn print(&self) { println!("Circle with radius {}", self.radius); } } impl PrintableWithLabel for Circle {} struct Square { side: f64, } impl Printable for Square { fn print(&self) { println!("Square with side {}", self.side); } } impl PrintableWithLabel for Square {} fn main() { let shapes: Vec<Box<dyn PrintableWithLabel>> = vec![ Box::new(Circle { radius: 1.0 }), Box::new(Square { side: 2.0 }), ]; for shape in shapes { shape.print_with_label("Shape"); } }
この例では、
- 2つの構造体
Circle
とSquare
を定義します。 - 両方の構造体が
Printable
traitとPrintableWithLabel
traitを実装します。 main
関数では、traitオブジェクト(Box<dyn PrintableWithLabel>
)を格納するベクターshapes
を作成します。shapes
ベクターを反復処理し、各図形のprint_with_label
を呼び出します。
Circle
とSquare
の両方がPrintableWithLabel
を実装しているため、traitオブジェクトとしてベクターに格納できます。print_with_label
を呼び出すと、コンパイラはオブジェクトの実際の型に基づいて呼び出すメソッドを動的に決定します。
これが、traitがRustでポリモーフィズムを可能にする方法です。この記事がtraitをよりよく理解するのに役立つことを願っています。
Rustプロジェクトのホスティングに最適なLeapcellをご覧ください。
Leapcellは、Webホスティング、非同期タスク、Redis向けの次世代サーバーレスプラットフォームです
多言語サポート
- Node.js、Python、Go、またはRustで開発。
無制限のプロジェクトを無料でデプロイ
- 使用量に対してのみ支払い — リクエストや料金は一切かかりません。
比類のない費用対効果
- アイドル料金なしの従量課金制。
- 例:25ドルで、平均応答時間60ミリ秒で694万リクエストをサポートします。
合理化された開発者エクスペリエンス
- 簡単なセットアップのための直感的なUI。
- 完全に自動化されたCI/CDパイプラインとGitOps統合。
- 実用的な洞察を得るためのリアルタイムメトリックとロギング。
簡単なスケーラビリティと高性能
- 簡単な高並行性を処理するための自動スケーリング。
- 運用上のオーバーヘッドはゼロ — 構築に集中するだけです。
ドキュメントで詳細をご覧ください
Xでフォローしてください:@LeapcellHQ