Rustの勉強(その8)
チュートリアル終わったので、syntax and semanticsで深く行きます。
mao-instantlife.hatenablog.com
変数のバインド
大体のパターンはチュートリアルに出ていますね。
let x = 5; // 型が推測できる場合は省略可能(xの型はi32) let (x, y) = (1,2); // letの後は変数名ではなくパターン let x: i32 = 5; // 型を指定する場合はコロン(:)で区切る
Rustの変数はそのままでは不変(immutable)です。
let x = 5; x = 10; // 再代入はコンパイル時にエラーになる
mutableな変数にするには mut
をつけます。
let mut x = 5; x = 10; // 再代入時にコンパイルエラーにならない
初期化と利用
宣言だけされて利用されていない変数がある場合はコンパイル時に警告が出ます。下記、警告は出ますが実行は出来ます。
fn main() { let x: i32; println!("Hello world"); }
初期化をされていない変数がある場合はコンパイル時にチェックされます。下記は、未初期化の変数を出力に使用しているのでコンパイルエラーとなります。
fn main() { let x: i32; println!("the value of x is: {}", x); }
ちなみに、xの宣言を let mut
に変更してもコンパイルエラーです。なおかつ、以下のようにするとコンパイルエラーにはなりませんが「xはmutableじゃなくてもいいのよ?」という意味の警告を出してくれます。
fn main() { let mut x: i32; x = 10; println!("the value of x is: {}", x); }
mao study_for_rust $ cargo build Compiling study_for_rust v0.1.0 (file:///Users/mao/Documents/RustWorkspace/study_for_rust) src/main.rs:2:9: 2:14 warning: variable does not need to be mutable, #[warn(unused_mut)] on by default src/main.rs:2 let mut x: i32; ^~~~~
スコープとシャドーイング
基本的に変数のスコープは宣言されたブロックの中です。親ブロックで宣言された変数は子ブロックで有効だが、逆は無効。このチェックはコンパイル時にされます。
fn main() { let x: i32 = 17; { let y: i32 = 3; println!("The value of x is {} and value of y is {}", x, y); } println!("The value of x is {} and value of y is {}", x, y); // yが無効なのでコンパイルエラー }
mao study_for_rust $ cargo build Compiling study_for_rust v0.1.0 (file:///Users/mao/Documents/RustWorkspace/study_for_rust) src/main.rs:7:62: 7:63 error: unresolved name `y`. Did you mean `x`? [E0425] src/main.rs:7 println!("The value of x is {} and value of y is {}", x, y); // This won't work ^ <std macros>:2:25: 2:56 note: in this expansion of format_args! <std macros>:3:1: 3:54 note: in this expansion of print! (defined in <std macros>) src/main.rs:7:5: 7:65 note: in this expansion of println! (defined in <std macros>) src/main.rs:7:62: 7:63 help: run `rustc --explain E0425` to see a detailed explanation error: aborting due to previous error Could not compile `study_for_rust`. To learn more, run the command again with --verbose.
ただし、子ブロックで同名の変数をブロック内のみ一時的に宣言することが可能で、これをシャドーイングといいます。
let x: i32 = 8; { println!("{}", x); // 親ブロックのx=8 let x = 12; println!("{}", x); // シャドーイングしたx=12 } println!("{}", x); // 親ブロックのx=8 let x = 42; println!("{}", x);
ちなみに、下記のように変数をmutableからimmutableに変更したり、型を変更したりする再宣言みたいなことができるみたいだけど、自分で自分の首を絞めそうだし、やらないほうが幸せだと思います。あくまでサンプルということで。
let mut x: i32 = 1; x = 7; let x = x; // xをmutableに再宣言 let y = 4; let y = "I can also be bound to text!"; // yを文字列に再宣言
関数
fn plus_number(x: i32, y: i32) -> i32 { x + y }
引数リストでの型の省略はコンパイルエラーになります(当たり前といえば当たり前)。戻り値がある場合は、アローダイアグラムで戻り値の型を指定します。
ちなみに、戻り値の型を明示した場合に、最終行を return
を明記せずに評価させたい場合はセミコロンをつけたらコンパイルエラーになるっぽい。
mao study_for_rust $ cargo build Compiling study_for_rust v0.1.0 (file:///Users/mao/Documents/RustWorkspace/study_for_rust) src/main.rs:2:3: 4:4 error: not all control paths return a value [E0269] src/main.rs:2 fn add_one(x: i32) -> i32 { src/main.rs:3 x + 1; src/main.rs:4 } src/main.rs:2:3: 4:4 help: run `rustc --explain E0269` to see a detailed explanation src/main.rs:3:10: 3:11 help: consider removing this semicolon: src/main.rs:3 x + 1; ^ error: aborting due to previous error Could not compile `study_for_rust`. To learn more, run the command again with --verbose.
これに限らずRustのコンパイルエラー時のメッセージは判りやすいですね。今まで見たケースはほとんどサジェストが付いています。
戻り値の型を省略した場合はセミコロンつけてもコンパイルエラーにならないので、Unitっぽい挙動になる?と思っているのですが、関数利用時に戻り値を使うようなコードを書いてるとコンパイルエラーになるので、値がないということですかね。
fn main() { println!("{} is added!", hoge(3)); } fn hoge(x: i32) { x + 1; }
上記のようなコードをコンパイルしてみると以下のようなコンパイルエラーとなります。
mao study_for_rust $ cargo build Compiling study_for_rust v0.1.0 (file:///Users/mao/Documents/RustWorkspace/study_for_rust) src/main.rs:2:28: 2:35 error: the trait `core::fmt::Display` is not implemented for the type `()` [E0277] src/main.rs:2 println!("{} is added!", hoge(3)); ^~~~~~~ <std macros>:2:25: 2:56 note: in this expansion of format_args! <std macros>:3:1: 3:54 note: in this expansion of print! (defined in <std macros>) src/main.rs:2:3: 2:37 note: in this expansion of println! (defined in <std macros>) src/main.rs:2:28: 2:35 help: run `rustc --explain E0277` to see a detailed explanation src/main.rs:2:28: 2:35 note: `()` cannot be formatted with the default formatter; try using `:?` instead if you are using a format string src/main.rs:2:28: 2:35 note: required by `core::fmt::Display::fmt` error: aborting due to previous error Could not compile `study_for_rust`. To learn more, run the command again with --verbose.
ちゃんと戻り値のある関数に変えるとコンパイルも実行もできます。
式と文
怖いやつです(違。Rustには以下の2種類の文しかなくて、他は式であるとのこと。
- declaration statement(定義文?)
- expression statement(式文?)
なお、
- 式 = 値を返すもの
- 文 = 値を返さないもの
です。
declaration statement
まあ、これはわかるやつですね。さっきの変数のバインドです。
let x = 5; // 文。式でないことを示すためにletを書くようになってる?
以下のようなことはできません。
let x = (let y = 5); // letが定義文なので無理
とか、
let mut y = 5; let x = (y = 6); // x は 空タプル`()` で、`6` にはならない
意図しない挙動になるから、さっきのブロックの話と言い、変数のバインドと言い、ちょっと安全側に寄ったコーディングルールで書いたほうがよさそうです。この程度の冗長は気にしないほうがいいでしょう。
expression statement
これが、コード読んでどこのこと言ってるんだかはわかるけど、なんのことを言ってるんだかわからんやつです。値をリターンする関数の最後に評価する部分で、ここは文で定義される必要があり、Rustでは式はセミコロンで区切られる、とのこと。。。
fn foo(x: i32) -> i32 { x + 1 // ここがexpression statement }
セミコロンが入ると空タプルが返ることになってコンパイルエラーになります。セミコロンつける時は明示的に return
つけるべし、ということですね。 return
は let
と同じく文であることを宣言するためのものなのかも?(自信ない)
今日のまとめ
まあ、大体新しい言語書くときに思うんですけど、ある程度書き方に自由度が許容されている、かつ、理解しないまま書くと意図しない動きになる場合は、書くときに自分で統一したほうがいいですね。変数はちゃんと一つずつ宣言しよう、明示的に return
書こう、というルールに落ち着きそうです。