Rust のトレイト
基本概要#
型に対して実装すべきメソッドを定義したのがトレイト
- Java で言うところのインタフェース
1// デカルト座標 2#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default)] 3pub struct CartesianCoord { 4 pub x: f64, 5 pub y: f64, 6} 7 8// 極座標 9pub struct PolarCoord { 10 pub r: f64, 11 pub theta: f64, 12} 13 14// 座標 15pub trait Coordinates { 16 // 関数の本体は書かない 17 fn to_cartesian(self) -> CartesianCoord; 18 fn from_cartesian(cart: CartesianCoord) -> Self; 19} 20 21// デカルト座標系はそのまま 22impl Coordinates for CartesianCoord { 23 fn to_cartesian(self) -> CartesianCoord { 24 self 25 } 26 fn from_cartesian(cart: CartesianCoord) -> Self { 27 cart 28 } 29} 30 31// 極座標系は変換が必要 32impl Coordinates for PolarCoord { 33 fn to_cartesian(self) -> CartesianCoord { 34 CartesianCoord { 35 x: self.r * self.theta.cos(), 36 y: self.r * self.theta.sin(), 37 } 38 } 39 fn from_cartesian(cart: CartesianCoord) -> Self { 40 PolarCoord { 41 r: (cart.x * cart.x + cart.y * cart.y).sqrt(), 42 theta: (cart.y / cart.x).atan(), 43 } 44 } 45} 46 47// タプルにもトレイトを実装できる 48impl Coordinates for (f64, f64) { 49 fn to_cartesian(self) -> CartesianCoord { 50 CartesianCoord { 51 x: self.0, 52 y: self.1, 53 } 54 } 55 fn from_cartesian(cart: CartesianCoord) -> Self { 56 (cart.x, cart.y) 57 } 58}ジェネリクスで受け取る型に境界(「トレイトを実装している型」というような境界)をトレイト境界という
下の例だと、パラメータ
Pの後にトレイト名を付けることで、to_cartesian()を実装している型しか受け取れない、と強制できる関数の型の後に
where<トレイト境界>という書き方も OKimpl Trait構文で書く方法もある- 関数の引数の型の位置に
impl トレイト名を書くことで、トレイト境界を指定する。 - この構文を使うと、型パラメータや具体的な型名に言及せずにトレイト境界を書くことができる
- 関数の引数の型の位置に
トレイトを継承することも可能
トレイトのメソッドにはデフォルト実装をもたせることができる
トレイトで定義した関数は、トレイトとそれを実装する型が可視であれば、他のモジュールからアクセスできる
- 関数ごとに
pubを付ける必要はない
- 関数ごとに
トレイト実装のルール:あるトレイト
Traitをある型Typeに実装するためには、トレイトTraitまたは型Typeの少なくともどちらか一方の定義のあるクレートで実装しなければならない型:自クレート 型:他クレート トレイト:自クレート ○ ○ トレイト:他クレート ○ × いくつかの標準ライブラリのトレイトは
#[derive(XXX)]アトリビュートを使うことで型定義時に自動で実装できる
トレイトのジェネリクス#
trait トレイト名<型パラメータ>で宣言できる実装も今まで通り
- 型パラメータを導入する箇所は、トレイト名より前であることに注意
トレイトを実装する際に、列挙されてないメソッドも定義する#
- クラスベースの言語から来た身としては、変に悩んでしまったので…
- rust-jp.rs の 日本語コミュニティ(Slack)の方々に回答いただいた。
やりたいこと:Java での実装例#
タイトルだけでは何がしたいのかさっぱりわからないと思うので、実現したいことを Java で書くと以下のような感じ:
1import java.util.*; 2 3interface Service { 4 int ID = 123; 5 void displayId(); 6} 7 8class Module implements Service { 9 public void displayId() { 10 System.out.println(getId()); 11 } 12 13 private int getId() { 14 return this.ID; 15 } 16} 17 18public class Main { 19 public static void main(String[] args) { 20 Module module = new Module(); 21 module.displayId(); 22 } 23}要は、以下の2点:
- インタフェース(Rust ではトレイト)で public なメソッドを1つ列挙しておきたい
- 実装クラス(Rust では impl)の中で、 private なメソッドを定義して利用したい
Rust で実現:失敗例#
これを Rust で実現しようとこのように書くとコンパイルエラーが出て怒られる:
エラーメッセージは、
error[E0407]: method get_id is not a member of trait Serviceとerror[E0425]: cannot find function get_id in this scopeというもの。get_idというメソッドはServiceトレイトで列挙していないので、書けないよ と怒られてしまう。
Rust で実現:成功例#
ということで、以下のようにするとコンパイラに怒られずに済む:
1trait Service { 2 fn display(&self); 3} 4 5struct Module { 6 id: u64, 7} 8 9impl Service for Module { 10 fn display(&self) { 11 print!("{}", self.get_id()); 12 } 13} 14 15impl Module { 16 fn get_id(&self) -> u64 { 17 self.id 18 } 19} 20 21fn main() { 22 let module = Module{ 23 id: 12, 24 }; 25 module.display(); 26}直したのは、
Serviceトレイトの実装Moduleをトレイトの実装部分(impl Service for Moduleの箇所)と Module 独自のメソッドを定義する部分(impl Moduleの箇所)に分けて書く、という点。
ここに掲載する記事はまだありません。
