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