冥冥乃志

ソフトウェア開発会社でチームマネージャをしているエンジニアの雑記。アウトプットは少なめです。

follow us in feedly

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 つけるべし、ということですね。 returnlet と同じく文であることを宣言するためのものなのかも?(自信ない)

今日のまとめ

まあ、大体新しい言語書くときに思うんですけど、ある程度書き方に自由度が許容されている、かつ、理解しないまま書くと意図しない動きになる場合は、書くときに自分で統一したほうがいいですね。変数はちゃんと一つずつ宣言しよう、明示的に return 書こう、というルールに落ち着きそうです。