Rust

Rustのstruct、traitの使い方

More than 3 years have passed since last update.

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を実用的に使っていけるのではないかと思う。