読者です 読者をやめる 読者になる 読者になる

(-> % read write unlearn)

All opinions expressed are solely my own and do not express the views or opinions of my employer.

rust-lang#02 Variable Bindings & Function

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種類があります。

iuは、integerunsigned 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とかと違って、上記のように後に宣言している関数でも手前で使用可能なようです。

返り値
  • 関数の返り値は引数に続けて->とともに書きます。
  • 関数のボディの最後の行の評価値が返り値になります。RubyClojureとかと同じです。
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では変数の束縛が式として動作します。 つまり変数束縛が値を返します。 そのため、以下は正しい構文です。 xyはともに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 は、どんな型としても有効に動作します。 なので、Javavoidとはまた違った概念のようです。

以下はコンパイルが通ります(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の勉強を続けていこう。