LoginSignup
94
78

More than 3 years have passed since last update.

Rustのstruct、traitの使い方

Last updated at Posted at 2015-06-23

Rustのstruct/traitが大分手に馴染んできた気がするのでメモ
ここではx,yを持つPoint型と言うのを例に基本的な使い方を紹介する。

Structの定義

まず構造体を定義する。

pub struct Point<T> {
    pub x: T,
    pub y: T
}

一番基本的な形は以下だが、

struct Point<T> {
    x: T,
    y: T
}

pubというキーワードでメンバをpublicにするのと、
structを外部から選べるようにしてる。

コンストラクタ・スタティックメソッド

コンストラクタはC++のように専用のものがあるわけではなく、
スタティックメソッドとして実現する。

impl<T> Point<T> {
    pub fn new(p:(T,T)) -> Point<T> {
        Point{x:p.0,y:p.1}
    }
}

これはそんなに難しくないと思う
T型のタプルを引数にとってそれをx,yに割り当てて(暗黙の)returnしてる。

使い方

こういう風に使う

fn foo(){
    let p = Point::new((0u32,0));
    println!("{}",p.x);
}

ここでタプルの2つ目の要素の型は型推論によって自動的にu32と推定してくれる。

またはこう

fn foo(){
    Point::<u32>::new((0,0));
    println!("{}",p.x);
}

インスタンスメソッド

インスタンスメソッドは以下のようにselfで自身を参照する

impl<T:Mul+Div+Copy> Point<T> where <T as Mul>::Output:Add {

    pub fn div(&self, d:T) -> Point<<T as Div>::Output> {
        Point{x:self.x/d,y:self.y/d}
    }

    pub fn dot(&self, r: Point<T>) -> <<T as Mul>::Output as Add>::Output {
        self.x * r.x + self.y * r.y
    }

}

型制約

型制約の部分がいきなり複雑になった印象があるかもしれないが、
すこしずつ見ていけばそんなに難しくない。

impl<T:Mul+Div+Copy>この部分でPoint型の引数としてとるT
MulというtraitとDivというtraitとCopyというtraitを持つ事を要求してる。

そしてさらに複雑な型制約をwhere句以降に書いている。
型制約の部分は以下の様にwhere句の後にまとめることもできる。

impl<T> Point<T> where T:Mul+Div+Copy,<T as Mul>::Output:Add {

メソッドの部分を見ていく。

    pub fn div(self, d:T) -> Point<<T as Div>::Output> {
        Point{x:self.x/d,y:self.y/d}
    }

これはd:Tを受け取ってx,yをそれぞれ割るメソッドとして受け取る。

返り値の型は Point<T> と書きたくなるが、
T:Divをdivした値はTとは限らない(大概はTだが)ので
DivのtraitではT::Outputとして別に型を持っていてそちらを
型引数として持つPointとなる

また、/演算子はCopytraitを要求するのでTにはCopytraitも必要。

演算子による演算の結果として型が変わる様な例は
ベクトルの内積のようなものを思い浮かべるといいだろう
rustはそのようなケースでも柔軟に記述できるようになっている。

より複雑なケース

    pub fn dot(self, r: Point<T>) -> <<T as Mul>::Output as Add>::Output {
        self.x * r.x + self.y * r.y
    }

これはまさに内積の計算だが、アウトプットは
mulした際のOutputaddしたOutputとなってる。
Point<<<T as Mul>::Output as Add>::Output>ではなく
 <<T as Mul>::Output as Add>::Output

この計算をする上で先ほどwhere句で指定した
<T as Mul>::Output:Addという型指定が活きてくる。

Rustのこの辺りの型指定の柔軟さはすごくかっこいいと思う。

既存のtraitを埋め込む

Point同士の加算を演算子で表現したいとかいう要件がある。
例えば

fn foo(){
    let p = Point::new((0u32,0));
    let q = Point::new((0u32,0));
    println!("{},{}",(p+q).x,(p+q).y);
}

これを実現するにはAddtraitの実装を書く

impl<T:Add> Add for Point<T> {
    type Output = Point<<T as Add>::Output>;
    fn add(self, r: Point<T>) -> Point<<T as Add>::Output> {
        Point{x:self.x + r.x , y:self.y + r.y}
    }
}

殆ど説明は必要ないと思うけど、pub修飾子はいらないらしい。

これで、Addtrait自体の埋め込みは終わるが、
add関数は参照ではなく値を要求するので
一度add演算子を使った時点でmoveされる事になってしまい実用的ではない。
そのため、Point<T>自体にCopytraitを実装する必要が有る。
Copytraitの実装はわざわざ自分で書く必要はなく
struct定義の前に#[derive(Copy, Clone)]をつけるだけでやってくれる。(!)

#[derive(Copy, Clone)]
pub struct Point<T> {
    pub x: T,
    pub y: T
}

debug print

println!("{:?}",p)とかで表示したい気持ちが生まれてくると思うが、
その場合はstd::fmt::Debugtraitを実装しておく。

impl<T> fmt::Debug for Point<T> where T:fmt::Debug {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "({:?},{:?})", self.x, self.y)
    }
}

use std::fmt;が必要

まとめ

Rustのstruct/traitの基本的な使い方をまとめた。
ここで書いたことくらいがあれば特にc++ユーザなんかは
rustのstruct/traitを実用的に使っていけるのではないかと思う。

94
78
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
94
78