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
となる
また、/
演算子はCopy
traitを要求するのでTにはCopy
traitも必要。
演算子による演算の結果として型が変わる様な例は
ベクトルの内積のようなものを思い浮かべるといいだろう
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);
}
これを実現するにはAdd
traitの実装を書く
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
修飾子はいらないらしい。
これで、Add
trait自体の埋め込みは終わるが、
add
関数は参照ではなく値を要求するので
一度add
演算子を使った時点でmoveされる事になってしまい実用的ではない。
そのため、Point<T>
自体にCopy
traitを実装する必要が有る。
Copy
traitの実装はわざわざ自分で書く必要はなく
struct定義の前に#[derive(Copy, Clone)]
をつけるだけでやってくれる。(!)
#[derive(Copy, Clone)]
pub struct Point<T> {
pub x: T,
pub y: T
}
debug print
println!("{:?}",p)
とかで表示したい気持ちが生まれてくると思うが、
その場合はstd::fmt::Debug
traitを実装しておく。
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を実用的に使っていけるのではないかと思う。