Rust のトレイト
基本概要
-
型に対して実装すべきメソッドを定義したのがトレイト
- Java で言うところのインタフェース
// デカルト座標#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default)]pub struct CartesianCoord {pub x: f64,pub y: f64,}// 極座標pub struct PolarCoord {pub r: f64,pub theta: f64,}// 座標pub trait Coordinates {// 関数の本体は書かないfn to_cartesian(self) -> CartesianCoord;fn from_cartesian(cart: CartesianCoord) -> Self;}// デカルト座標系はそのままimpl Coordinates for CartesianCoord {fn to_cartesian(self) -> CartesianCoord {self}fn from_cartesian(cart: CartesianCoord) -> Self {cart}}// 極座標系は変換が必要impl Coordinates for PolarCoord {fn to_cartesian(self) -> CartesianCoord {CartesianCoord {x: self.r * self.theta.cos(),y: self.r * self.theta.sin(),}}fn from_cartesian(cart: CartesianCoord) -> Self {PolarCoord {r: (cart.x * cart.x + cart.y * cart.y).sqrt(),theta: (cart.y / cart.x).atan(),}}}// タプルにもトレイトを実装できるimpl Coordinates for (f64, f64) {fn to_cartesian(self) -> CartesianCoord {CartesianCoord {x: self.0,y: self.1,}}fn from_cartesian(cart: CartesianCoord) -> Self {(cart.x, cart.y)}} -
ジェネリクスで受け取る型に境界(「トレイトを実装している型」というような境界)をトレイト境界という
-
下の例だと、パラメータ
P
の後にトレイト名を付けることで、to_cartesian()
を実装している型しか受け取れない、と強制できるfn print_point<P: Coordinates>(point: P) {let p = point.to_cartesian();println!("({}, {})", p.x, p.y)} -
関数の型の後に
where<トレイト境界>
という書き方も OKfn print_point<P>(point: P)whereP: Coordinates,{let p = point.to_cartesian();println!("({}, {})", p.x, p.y)} -
impl Trait
構文で書く方法もある- 関数の引数の型の位置に
impl トレイト名
を書くことで、トレイト境界を指定する。 - この構文を使うと、型パラメータや具体的な型名に言及せずにトレイト境界を書くことができる
fn print_point(point: impl Coordinates) {let p = point.to_cartesian();println!("({}, {})", p.x, p.y)} - 関数の引数の型の位置に
-
トレイトを継承することも可能
-
トレイトのメソッドにはデフォルト実装をもたせることができる
-
トレイトで定義した関数は、トレイトとそれを実装する型が可視であれば、他のモジュールからアクセスできる
- 関数ごとに
pub
を付ける必要はない
- 関数ごとに
-
トレイト実装のルール:あるトレイト
Trait
をある型Type
に実装するためには、トレイトTrait
または型Type
の少なくともどちらか一方の定義のあるクレートで実装しなければならない型:自クレート 型:他クレート トレイト:自クレート ○ ○ トレイト:他クレート ○ × -
いくつかの標準ライブラリのトレイトは
#[derive(XXX)]
アトリビュートを使うことで型定義時に自動で実装できる
トレイトのジェネリクス
-
trait トレイト名<型パラメータ>
で宣言できるtrait Init<T> {fn init(t: T) -> Self;} -
実装も今まで通り
- 型パラメータを導入する箇所は、トレイト名より前であることに注意
impl<T> Init<T> for Box<T> {// 内部では`T`でパラメータの型を参照するfn init(t: T) -> Self {Box::new(t)}}
トレイトを実装する際に、列挙されてないメソッドも定義する
- クラスベースの言語から来た身としては、変に悩んでしまったので…
- rust-jp.rs の 日本語コミュニティ(Slack)の方々に回答いただいた。
やりたいこと:Java での実装例
-
タイトルだけでは何がしたいのかさっぱりわからないと思うので、実現したいことを Java で書くと以下のような感じ:
import java.util.*;interface Service {int ID = 123;void displayId();}class Module implements Service {public void displayId() {System.out.println(getId());}private int getId() {return this.ID;}}public class Main {public static void main(String[] args) {Module module = new Module();module.displayId();}} -
要は、以下の2点:
- インタフェース(Rust ではトレイト)で public なメソッ ドを1つ列挙しておきたい
- 実装クラス(Rust では impl)の中で、 private なメソッドを定義して利用したい
Rust で実現:失敗例
-
これを Rust で実現しようとこのように書くとコンパイルエラーが出て怒られる:
trait Service {fn display(&self);}struct Module {id: u64,}impl Service for Module {fn display(&self) {print!("{}", get_id());}fn get_id(&self) -> u64 {self.id}}fn main() {let module = Module{id: 12,};module.display();} -
エラーメッセージは、
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 で実現:成功例
-
ということで、以下のようにするとコンパイラに怒られずに済む:
trait Service {fn display(&self);}struct Module {id: u64,}impl Service for Module {fn display(&self) {print!("{}", self.get_id());}}impl Module {fn get_id(&self) -> u64 {self.id}}fn main() {let module = Module{id: 12,};module.display();} -
直したのは、
Service
トレイトの実装Module
をトレイトの実装部分(impl Service for Module
の箇所)と Module 独自のメソッドを定義する部分(impl Module
の箇所)に分けて書く、という点。