Chapter 09

ブロックとスコープ

ブロック

if 式について説明したとき,条件式の後や else の後に { } で囲まれた部分が登場しました.このように { } で囲まれた部分をブロックといいます.

use proconio::input;

fn main() {
    input! {
        x: i32,
    }
    if x > 0 { // ブロックの始まり
        println!("正");
    } // ブロックの終わり
}

main 関数の中身も { } で囲まれています.これもブロックです.

use proconio::input;

fn main() { // ブロックの始まり
    input! {
        x: i32,
    }
    if x > 0 {
        println!("正");
    }
} // ブロックの終わり

input! の直後にも { } で囲まれた部分が続いていますが,このようなマクロ呼び出しにおける { } はブロックではありません.

if 式のブロックは条件を満たすときのみ実行されましたが,単にブロックだけを書けば常に実行されます.

fn main() {
    println!("Good morning");
    {
        println!("Good afternoon");
        println!("Good evening");
    }
    println!("Good night");
}

main 関数の中身は,

  1. println!("Good morning"); という文
  2. { println!("Good afternoon"); println!("Good evening"); } というブロック
  3. println!("Good night"); という文

の 3 つです. 2. のブロックの中身は,

  1. println!("Good afternoon"); という文
  2. println!("Good evening"); という文

の 2 つです.

main 関数全体を表すブロックも含めて,ブロックの中身は上から順に実行されるので,出力は

Good morning
Good afternoon
Good evening
Good night

となります.

スコープ

あるブロックの中で変数を宣言するとします.すると,ブロックの外側ではその変数を使うことができません.

fn main() {
    {
        let hoge = 10;
    }
    println!("{}", hoge);
}

次のようなエラーになります.

error[E0425]: cannot find value `hoge` in this scope
 --> src/main.rs:5:20
  |
5 |     println!("{}", hoge);
  |                    ^^^^ not found in this scope

cannot find value `hoge` in this scope は「hoge という変数が見つからない」という意味です.ブロックの外側では, hoge という変数が存在しないことになっています.

逆に,ブロックの外側で宣言した変数をブロックの内側で使うことはできます.

fn main() {
    let hoge = 10;
    {
        println!("{}", hoge);
    }
}

これを,変数のスコープといいます.あるブロックの内部で宣言された変数のスコープは,変数が宣言されてから,そのブロックが終了するまでです.変数をそのスコープの外側で使うことはできません.

fn main() {
    let hoge = 10; // hoge のスコープの開始
    {
        println!("{}", hoge);
        let fuga = 20; // fuga のスコープの開始
        println!("{}", fuga);
    } // ブロックの終了:fuga のスコープの終了
    println!("{}", hoge);
} // ブロックの終了:hoge のスコープの終了

シャドーイング

同じ名前の変数を,複数回宣言することができます.

fn main() {
    let hoge = 10;
    println!("{}", hoge);
    let hoge = 20;
    println!("{}", hoge);
}

このとき, 1 つめの hoge と 2 つめの hoge は,名前は同じでも別の変数です.

1 回目の println! では, 1 つめの hoge の値が出力されます.一方, 2 回目の println! では, 2 つめの hoge の値が出力されます.よって出力は 10 20 となります.

このように,同じ名前の変数を宣言すると,前の変数が使えなくなります.これを変数のシャドーイングといいます.

シャドーイングをブロックの中で行うと,

fn main() {
    let hoge = 10;
    {
        println!("{}", hoge);
        let hoge = 20;
        println!("{}", hoge);
    }
    println!("{}", hoge);
}

ブロックを抜けた後は 2 つめの hoge が存在しないので,最後の println! では 1 つめの hoge の値が出力されます.よって出力は 10 20 10 となります.

2 つの hoge は別の変数なので,型が違っても構いません.

fn main() {
    let hoge: i32 = 10;
    println!("{}", hoge);
    let hoge: f64 = 20.;
    println!("{}", hoge);
}

値を返すブロック

次のコードを見てください.

fn main() {
    println!("ブロックの前");
    let hoge = {
        println!("ブロックの中");
        10
    };
    println!("ブロックの後, hoge の値は {}", hoge);
}

なにやらブロックが不思議な使われ方をしています.{ } の中に, println!("ブロックの中"); という文と, 10 という式が書かれていて, 10 にはセミコロン ; が付いていません.

このように書くと,ブロックが実行されたとき,最後に書いた式の値がブロック全体の値となります.これを「ブロックが値を返す」と言い表します.

今回,ブロックの最後に 10 と書いてあるので,このブロックが実行されると,最後に 10 という値を返します.すると, let hoge = 10; と書いたのと同じように, hoge に 10 が代入されます.

よって,これを実行すると,

  1. ブロックの前 と出力される
  2. ブロックが始まる
  3. ブロックの中 と出力される
  4. 10 という値を返してブロックが終わる
  5. ブロックの返した値 10 が hoge に代入される
  6. ブロックの後, hoge の値は 10 と出力される

となります.

また,今回は } の後にセミコロンを付けないと

fn main() {
    println!("ブロックの前");
    let hoge = {
        println!("ブロックの中");
        10
    }
    println!("ブロックの後, hoge の値は {}", hoge);
}

コンパイルエラーになります.

error: expected `;`, found `println`
 --> src/main.rs:6:6
  |
6 |     }
  |      ^ help: add `;` here
7 |     println!("ブロックの後, hoge の値は {}", hoge);
  |     ------- unexpected token

expected `;`, found `println` は,「} の後に ; が来るはずだったのに,実際には println があった」という意味です. add `;` here は「ここにセミコロン ; を付けてはどうか」という提案です.

値を返す if 式

前の章の最後に,

use proconio::input;

fn main() {
    input! {
        x: i32,
    }
    let abs;
    if x >= 0 {
        abs = x;
    } else {
        abs = -x;
    }
    println!("絶対値は{}です", abs);
}

というコードを書きました.しかし, if 式のブロックも値を返すことができるため,これは次のように書き換えることができます.

use proconio::input;

fn main() {
    input! {
        x: i32,
    }
    let abs;
    abs = if x >= 0 { x } else { -x };
    println!("絶対値は{}です", abs);
}

条件 x >= 0 が正しければ, { x } のブロックが実行され, x の値が返されます.一方,条件 x >= 0 が正しくなければ, { -x } のブロックが実行され, -x の値が返されます.よって, abs には x の絶対値が代入されます.

今回も } の後にセミコロンを付けないとコンパイルエラーになります.

このコードを,型に着目して見てみましょう. abs の型が注釈されていないため,周囲から推論されています.

if 式の 1 つめのブロックが返す xi32 型です.また, 2 つめのブロックが返す -xi32 型です.よって,この if 式が返す値は常に i32 型であると分かるので, absi32 型であると推論されます.

もし 1 つめのブロックと 2 つめのブロックの返す値の型が異なると,

fn main() {
    let x = 0;
    let y = if x >= 0 { 10 } else { 10. };
}

エラーになります.

error[E0308]: `if` and `else` have incompatible types
 --> src/main.rs:3:37
  |
3 |     let y = if x >= 0 { 10 } else { 10. };
  |                         --          ^^^ expected integer, found floating-point number
  |                         |
  |                         expected because of this

`if` and `else` have incompatible types は「ifelse の型が相容れない」という意味です.後者の 10. を指して expected integer, found floating-point number (「整数が来るはずだったが,浮動小数点数が来た」)と書かれ,さらに整数が来るはずだった理由として前者の 10 が示されています.

練習問題