Rustの勉強です。 前回に続いてhttps://doc.rust-lang.org/bookをやっていきます。
今日は4.1. Variable Bindingsからです。
4.1. Variable Bindings
Variable Bindings
変数の束縛
let
は、変数を束縛します。
fn main() { let x = 5; }
このままでコンパイルは通ります。 ただし、未使用の変数があるので警告が出ました。
src/main.rs:2:9: 2:10 warning: unused variable: `x`, #[warn(unused_variables)] on by default src/main.rs:2 let x = 5;
Patterns
パターン
Rustの変数束縛の構文はちょっとした工夫がされています。
let
の行の=
の左側は変数名ではなく、patternを書きます。
このpatternは、分配束縛のような使い方ができます。以下のように書くと分かりやすいです。
fn main() { let (x, y) = (1, 2); }
xには1、yには2がそれぞれ束縛されます。
ちょっと前に出てきたlet x = 5;
という書き方は、一見patternではなく、変数を左辺に書いているように見えます。
しかし、これも厳密に言うと変数名1つだけのpatternを書いていると言えます。
ちなみにClojureとNode.js(ECMAScript6)で上記と似たような分配束縛を書くとこうなります。
;; Clojure (fn -main [] (let [[x y] [1 2]] (println x y)) ;=> 1 2 (let [{:keys [a, b, c]} {:a 10 :b 20 :z 99}] (println a b c))) ;=> 10 20 nil
// node v6.2.1 let [x, y] = [1, 2] console.log(x, y) //=> 1 2 let {a, b, c} = {a: 10, b: 20, z:99} console.log(a, b, c) //=> 10 20 undefined
Type annotations
型注釈
型は、変数名の後にコロン(:
)に続けて書きます。
さっきのコードには型の記述がありませんでした。
しかし、型推論(type inference)が効いていたのでコンパイルが通ったのです。
rustには、Scalaのような型推論が備わっています。
型注釈を明示すると前述のlet
式は次のようになります。
let x: i32 = 5;
i32
は32ビットの符号付き整数です。
整数の型にはいくつかの種類があります。
- 符号付きの整数は
i
から始まる型名で、 - 符号なしの整数は
u
から始まる型名です。 - サイズには8, 16, 32, 64ビットの4種類があります。
i
とu
は、integer
とunsigned integer
の頭文字でしょうか。
Mutability
可変性
デフォルトでは、束縛はイミュータブル(不変)です。 なので、次のコードはコンパイルできません。 再束縛しようとしているからです。
let x = 5; x = 10;
ミュータブル(可変)にしたかったら、mut
を使います。
let mut x = 5; x = 10;
ミュータブルにしなければいけないものだけ明示的にミュータブルにすることで、安全性を向上させています。
ちなみに以下のように書くいたとしてもコンパイル通るし、ちゃんと動作するようです。
let x: i32 = 10; println!("{}", x); //=> 10 let x: i32 = 20; println!("{}", x); //=> 20
Initializing bindings
束縛の初期化
変数は使用する前に値とともに初期化される必要があります。 また使用しない変数が存在するとコンパイル時に警告されます。
Scope
スコープ
変数の束縛にはスコープがあります。
ブロック({
〜}
)がスコープを作ります。
そして関数定義もまたブロックであり、つまりスコープを作ります。
Javaと一緒だから自分にとっては親しみやすいし直感的です。
fn main() { let x: i32 = 10; { let y: i32 = 20; println!("{}", x); //=> 10 println!("{}", y); //=> 20 } println!("{}", x); //=> 10 // println!("{}", y); //=> y は未定義なのでコンパイルエラーになる }
Shadowing
同じ変数名でも、束縛する(ときの)スコープが違うと、別のものとして束縛され、以前の束縛を覆い隠します。スコープを抜けると覆い隠された束縛が再び露出します。
fn main() { let x: i32 = 10; { println!("{}", x); //=> 10 let x = 20; println!("{}", x); //=> 20 変数が変更されたわけではなく、このスコープでの値が覆い隠している } println!("{}", x); //=> 10 に戻る。スコープを抜けたので。 }
mut
との違いに注意します。
fn main() { let mut x: i32 = 10; // mutを追加。 { println!("{}", x); //=> 10 x = 20; println!("{}", x); //=> 20 に変更された。 } println!("{}", x); //=> 20 のまま。スコープを抜けても変更された結果は変わらない。 }
4.2. Functions
fn
で関数を宣言します。- 関数名に続けて
()
内に,
区切りで引数を羅列します。 - 引数には、
let
同様の書き方で型注釈が必要です。 - 型注釈は省略できません。たとえ関数のボディにおいて、引数の型が自明であったとしても。
- 関数のボディは
{}
内に書きます。
fn main() { print_sum(5, 6); //=> 11 } fn print_sum(x: i32, y: i32) { println!("{}", x + y); }
let
と違って、関数の引数においては型注釈を省略できないっていうのがかなり印象的。
あと、Clojureとかと違って、上記のように後に宣言している関数でも手前で使用可能なようです。
返り値
fn main() { println!("{}", add_one(10)) //=> 11 } fn add_one(x: i32) -> i32 { x + 1 }
次のようにx + 1
にセミコロン;
を付けるとコンパイルエラーになります。
fn main() { println!("{}", add_one(10)) } fn add_one(x: i32) -> i32 { x + 1; //=> Compile error }
Expressions vs. Statements
- 式(
expressions
)は、値を返します。 - 文(
statements
)は、値を返しません。 - rustには2種類の
statements
があります。declaration statements
expression statements
- それ以外は全て
expressions
です。
declaration statements
Rubyでは変数の束縛が式として動作します。
つまり変数束縛が値を返します。
そのため、以下は正しい構文です。
x
とy
はともに5
が束縛されます。
# これは Ruby のコード x = y = 5
rustでは、変数束縛のlet
は式ではなく文です。
つまり値を返さないので、上記のRubyコードのように以下のように書くとコンパイルエラーです。
let x = (let y = 5);
ただし、let
による変数束縛は文であるけれど、y = 5
のような、既に束縛された変数への値のアサイン自体は式になります。
さらにここで注意が必要なのは、例えばy = 5
のようなアサイン式がRubyのように5
と評価されるわけではなく、空のタプル(()
)に評価される点です。
let mut y = 5; let x = (y = 6); // xに空のタプルが束縛される。
expression statements
セミコロンは式を文に変えます。 そして、セミコロンによって作られた文は、返り値がないわけではなく正確には空のタプルを返します。
Early returns
return
キーワードも存在するので、返り値となる式を明示することもできます。
しかし、返り値が関数のボディの最後の式となる場合は省略することが推奨されます。
ここらへんはRubyとかと似た感じですね。
Diverging functions
返り値として!
を書くと、返り値を返却しない関数となります。
!
をdiverges
と呼ぶようです。
fn main() { diverges(); } fn diverges() -> ! { panic!("This function never returns!"); }
上記のpanic!
マクロは、Javaにおける例外のthrow
のように処理フローを分岐します。
そのスレッドの実行をクラッシュさせます。
diverging function は、どんな型としても有効に動作します。
なので、Javaのvoid
とはまた違った概念のようです。
以下はコンパイルが通ります(unused
の警告は出ますが)。
fn main() { let x: i32 = diverges(); let x: String = diverges(); } fn diverges() -> ! { panic!("This function never returns!"); }
Function pointers
次の文は、i32
型の値を1つ取ってi32
型の値を返す関数ポインタを宣言しています。
let f: fn(i32) -> i32;
こんな感じで使います。
fn main() { fn plus_one(i: i32) -> i32 { i + 1 } let f: fn(i32) -> i32 = plus_one; println!("{}", f(10)); //=> 11 }
以下のようなjavascriptに近いイメージなのかな。
// これは Javascript のコード function plus_one(i) { return i + 1; } let f = plus_one; console.log(f(10)); //=> 11
全然進んでないけど、たまに気晴らしにRustの勉強を続けていこう。