コンテンツにスキップ

[The book] 3. Common Programming Concepts

Rust The book の第3章 Common Programming Concepts

他言語でも共通のコンセプトを学習する。

3.1. Variables and Mutability

変数は基本的にImmutableだが、Mutableにすることもできる。
Immutableが基本である理由を説明するためプロジェクトを作る。

cargo new variables

mutは変数をMutableにする

これはコンパイルエラーになる。

let x = 5;
x = 6;

mutをつけるとMutableになるのでコンパイルは通る。

let mut x = 5;
x = 6;

Mutableのメリットとデメリット

巨大なデータを扱う時、Immutable構造体のコピーを生成するよりMutable構造体の一部を変更したほうがパフォーマンスが良いことがある。
しかし、これはバグの起こりやすさ、可読性などとのトレードオフになる。

Immutable variablesとConstantsの違い

項目 Immutable variables Constants
mut 使える 使えない
宣言子 let const
型注釈 任意 必須
グローバルスコープで 使えない 使える
実行時に評価 できる できない

Shadowing

同じ変数名をletで再束縛しているかのようなケースをShadowingという。

fn main() {
    let x = 5;
    // First variable is shadowed by the second. と言われる
    let x = x + 1;
    let x = x * 2;
    println!("The value of x is: {}", x);
    // -> The value of x is: 12
}

Mutableな変数との違いは以下。

  • Shadowingの実行文以外では再束縛できない
  • 同名変数に異なる型を入れられる

3.2. Data Types

Scalar Types

Integer Types

整数型。

  • iがsigned、uがunsignedのPrefix
  • isizeとusizeのbit数は実行環境に依存する
  • 基本的にi32を使うといい
    • 64bitシステムでもi32の方が速いから
// i32で推論される
let x = 2;

型の範囲を超えた場合、デバッグモードではpanic!になるがリリースモードでは輪廻する。
意図的に輪廻させたい場合は標準ライブラリのWrappingを使うといい。

Floating-Point Types

  • 基本的にf64を使うといい
    • f32より精度が高く、速度もほぼ同じため
// f64で推論される
let x = 2.0;

The Character Type

Rustのcharは4byteでUnicodeを表現できる。
stringリテラルとは異なりシングルクォーテーションを使う。

fn main() {
    let c = 'z';
    let z = 'ℤ';
    let heart_eyed_cat = '😻';

    println!("{} {} {}", c, z, heart_eyed_cat);
    // -> z ℤ 😻
}

Compound Types

tuplesとarraysがある。

The Tuple Type

  • Tupleは(i32, f64)のように丸括弧で表現する
  • destructuring
  • ドットアクセス
fn main() {
    let tup: (i32, f64, i32) = (500, 6.4, 1);
    // destructuring
    let (a, b, c) = tup;

    println!("{} is {}");
}

The Array Type

他の言語と異なり長さは固定。
型注釈は[型; 数]のように書く。同一の値で初期化する場合も同様。

// 長さ4、値の型がi32のArray
let a: [i32; 4] = [1, 2, 3, 4];
// 長さ5、値がすべて"hoge"のArray
let b = ["hoge"; 5];

アクセスは[i]を使う。

fn main() {
    let a: [i32; 4] = [1, 2, 3, 4];
    println!("a[2] is {}", a[2]);
    // -> a[2] is 3
}

indexがArrayのサイズを超えていると、コンパイルエラーになる。

fn main() {
    let a = [1, 2, 3, 4, 5];
    let index = 10;

    let element = a[index];

    println!("The value of element is: {}", element);
}

The bookにはコンパイルは通ると書いてあるが..コンパイラが強化されたのかな.. 🤔

The compilation didn’t produce any errors, but the program resulted in a runtime error and didn’t exit successfully.

3.3. Functions

関数定義

fnで関数定義。命名規約はsnake_case。
言語によって違うのでややこしい..。

言語 宣言子
Rust fn
Kotlin fun
Go func
TypeScript function
Bash function
Scala def
Python def
Elixir def
Nim proc
Haskell なし
Java なし
C++ なし

関数の定義と呼び出しについて、コードの登場順としては順不同。
コードとして先に定義する必要はない。

fn main() {
    println!("Hello, world!");

    another_function();
}

fn another_function() {
    println!("Another function.");
}

関数の構成

関数の引数と戻り値はPythonと同じ書き方。

fn plus_one(x: i32) -> i32 {
    x + 1
}

戻り値は関数本体の最後にあるExpressionの結果となる。

fn main() {
    let x = plus_one(5);

    println!("The value of x is: {}", x);
    // -> The value of x is: 6
}

fn plus_one(x: i32) -> i32 {
    x + 1
}

early returnをする場合はreturnキーワードを使う。

fn main() {
    let x = plus_one_if_x_is_positive(-3);
    println!("The value of x is: {}", x);
    // -> The value of x is: -3
}

fn plus_one_if_x_is_positive(x: i32) -> i32 {
    if x < 0 {
        return x;
    }

    x + 1
}

StatementとExpression

関数は複数のStatementと最後のExpressionで構成される。
Statement(文)とExpression(式)の違いは以下の通り。

Statement(文) Expression(式)
値の返却 しない する
fn main() {
    // Statementは値を返却しないため束縛できない
    // let x = (let y = 6);
}

Pythonはx = y = 6のような表現ができる。
これはy = 66を返すExpressionだからである。

また、ExpressionはStatementの一部である。

fn main() {
    // 5はExpression
    let x = 5;

    // { ... }はExpressionであり、その戻り値は`x + 1`
    // よって、yには4が束縛される
    let y = {
        let x = 3;
        // セミコロンがないので`x + 1`はExpression
        // セミコロンをつけると`x + 1;`はStatementになる
        x + 1
    };

    println!("The value of y is: {}", y);
    // -> The value of y is: 4
}

3.4. Comments

//から先はコメント

3.5. Control Flow

条件式、ループの理解用にそれぞれプロジェクトを作る。

cargo new branches
cargo new loops

if文

if文やswitch文が呼び出す{...} はしばしばarmsと呼ばれる。

fn main() {
    let number = 4;

    if number > 5 {
        println!("5 < {}", number);
    } else if number > 3 {
        println!("3 < {} <= 5", number);
    } else {
        println!("others");
    }
}

ifの条件句はbool型でなければならない。
他言語のように暗黙的な変換は認めず、明示的にbool型を示す必要がある。

つまり、if number { ... }のようなケースはエラーになる。

条件が多い場合はmatchを使った方がいい。

if式

Rustのifは式でもある。つまり右辺に指定して変数に束縛できる。

fn main() {
    let condition = true;
    // 三項演算子のように使える
    let number = if condition { 5 } else { 6 };

    println!("The value of number is: {}", number);
    // -> The value of number is: 5
}

ループによる繰り返し

無限ループ

loop {
    println!("Infinite loop!");
}

Loopを抜けたいときはbreakを使う。
breakは式であり、loop式の結果として値を返すことができる。

fn main() {
    let mut counter = 0;

    let result = loop {
        counter += 1;

        if counter == 10 {
            // resultに指定した値が束縛される
            break counter * 2;
        }
    };

    println!("The result is {}", result);
    // -> The result is 20
}

条件付きループ

while文を使う。

fn main() {
    let mut number = 3;

    while number != 0 {
        println!("{}!", number);

        number -= 1;
    }

    println!("LIFTOFF!!!");
}

for in ループ

Pythonと似てる。
Arrayは明示的にイテレータへ変換してから回す。

fn main() {
    let a = [10, 20, 30, 40, 50];

    for element in a.iter() {
        println!("the value is: {}", element);
    }
}

格好良く書くとこんな感じ。

fn main() {
    for number in (1..4).rev() {
        println!("{}!", number);
    }

    println!("LIFTOFF!!!");
}