100日後にRustをちょっと知ってる人になる: [Day 16]演算子 とおまけにクロージャ
Day 16 のテーマ
Rust の仕様をいろいろ見ながら学んでいますが、プログラムの記述に関して Rust の演算子とかを覚えきれていなくて都度都度リファレンスを調べてしまったりしています。というわけで、今日は自分の頭の中の整理も兼ねてまとめておこうと思います。
演算子
演算子 | 説明 | 例および備考 |
---|---|---|
! | マクロの展開 | println!() |
! | ビットの反転や論理反転 | !0 => -1 |
!= | 非等価比較 | foo != bar |
% | 剰余演算 | 5 % 3 |
%= | 剰余演算後に代入 | |
& | 借用 | 所有権を持たずに値を利用すること |
&& | 論理AND | foo && bar |
* | 掛け算 | 5 * 3 |
* | 参照外し | 参照から実データにアクセスする, let f = &mut foo, let bar = *f; |
*= | 掛け算後に代入 | |
+ | 加算演算 | 5 + 3 |
+= | 加算演算語に代入 | |
- | 減算演算 | |
-= | 減算演算後に代入 | |
-> | 関数とクロージャの戻り値型 | fn apply_twice(x: i32, f: fn(i32) -> i32) -> i32 { f(f(x)) } |
.. | 未満指定 | for x in 1 .. 101 // 1 から 100 まで |
.. | 「残り全部」パターン束縛 | let numbers = (2, 4, 8, 16, 32); match numbers { (first, .., last) => {} |
... | 値の範囲 | 1...5 |
/ | 割り算 | 5 / 3 |
/= | 割り算後に代入 | |
: | 型制約 | let i: i32 |
: | ループのラベル | 'outer: loop { println!("外側のループ"); 'inner: loop { println!("内側のループ"); |
<< | 左シフト | |
<<= | 左シフト後に代入 | |
=> | match のアーム記法 | match x { 1 => println!("one") |
>> | 右シフト | |
>> | 右シフト後に代入 | |
@ | パターンマッチした値を束縛 | if let Some(i @ 0...5) = n { println!("{}", i); } |
^ | ビットXOR | |
^= | ビットXOR後に代入 | |
| | OR | foo | bar |
| | クロージャ | let c = |x: i32| -> i32 { x + 1 }; |
|| | 論理OR | foo || bar |
? | エラー移譲 | let f = File::open(filename)?; |
クロージャ
クロージャは使いこなすととても便利なので、演算子だけでなくクロージャ自体についても少しまとめておこうと思います。
クロージャとは一言でいうと、匿名関数や無名関数とよばれる名前をもたない関数で、変数に対して束縛したり、関数の引数として渡すことができる表現です。
クロージャの記述は、先の演算子のまとめでも記載したように ||
で定義します。引数がある場合は、|引数1, 引数2|
のように ||
の間に挿入して表現します。
この ||
の後に波括弧 {}
でクロージャでの処理内容本体を記述します。
本体に記述する内容が、式1つの場合は {}
を省略可能です。
次の記述は全て同じ処理内容を表しています:
- 関数として定義
fn add(x: i32) -> i32 { x + 1}
- クロージャとして定義 (型制約を明記)
let add = |x: i32| -> i32 { x + 1};
- クロージャとして定義 (型制約を省略)
let add = |x| { x + 1};
- クロージャとして定義 (波括弧を省略)
let add = |x| x + 1;
クロージャと型推論
クロージャの記述で戻り値の型を省略して記述することができています。ここでの型推論は、このクロージャの最初の呼び出しで決定します。
let sample = |x| x ;
let s = sample(String::from("Rust"));
let n = sample(1); // エラー
1 回目の呼び出しで引数の型が String
に決定しました。そのため、2 回目の呼び出しで数値を引数とした場合にエラーになります。
クロージャとトレイト
クロージャは以下のトレイトのいずれかを実装します。
Fn
はFnMut
を継承しています。
FnMut
はFnOnce
を継承しています。
- Fn
trait Fn<Args> : FnMut<Args> {
fn call(&self, args: Args) -> Self::Output;
}
- FnMut
trait FnMut<Args> : FnOnce<Args> {
fn call_mut(&mut self, args: Args) -> Self::Output;
}
- FnOnce
trait FnOnce<Args> {
type Output;
fn call_once(self, args: Args) -> Self::Output;
}
これらのトレイトの違いは以下のようになります:
-
Fnトレイト
はクロージャの参照を引数にとり、何度でも呼び出し可能 -
FnMutトレイト
はクロージャの可変参照を引数にとり。何度でも呼び出し可能 -
FnOnceトレイト
はクロージャの移動を行い、一度だけ呼び出し可能
ところで、このトレイトについて Day 1 - Day 15 の間のどこにも振れていなかったと思うので、ここで簡単に説明をしてみようと思います。
トレイトとは、データ型を分類するための仕組みのことです。
トレイトを使うことで同じ名前の機能を様々な型に対して実装することができたり、追加したりすることができるようになります。
Java でいうところの、インターフェースや抽象クラスに似ている概念と思えば分かりやすかもしれません。
うまく表現しきれていない気がするので、明日 Day 17 でトレイトについて説明できるようにしようと思いました。
Day 16 のまとめ
今日は、Rust の演算子についてまとめながら、最終的にクロージャについて記述しました。
演算子は一通りまとめたことで、覚えないといけないものがはっきりと分かったかなと思いました。
一方でクロージャを説明しようとしたところで、トレイトについてキチンと理解した上でないと説明しきれないところがあることに気づきました。
ということで、明日はしっかりとトレイトが説明できるようになることをテーマとしようと思います。
Discussion