😸

#3 概念【知識0がRustやってみる】

2024/04/07に公開

【一般的なプログラミングの概念】

よろしくお願いします!

変数と可変性

変数は標準で不変になります。これは、 Rustが提供する安全性や簡便な並行性の利点を享受する形でコードを書くための選択の1つです。 ところが、まだ変数を可変にするという選択肢も残されています。 どのように、そしてなぜRustは不変性を推奨するのか、さらには、なぜそれとは違う道を選びたくなることがあるのか見ていきましょう。

変数が不変であると、値が一旦名前に束縛されたら、その値を変えることができません。

  1. 変数がデフォで不変である(変数とは..)
  2. 変数を可変にする方法がある
  3. 変数に値を代入することを束縛というっぽい

この3つの情報が出てきたところでさっそくこれを実行しろとサンプルが。

main.rs
fn main() {
    let x = 5;
    println!("The value of x is: {}", x);     // xの値は{}です
    x = 6;
    println!("The value of x is: {}", x);
}

ふむ..変数定義はletprintln!で変数を出力するには("{}",変数)になるのかな。
あれ?型の宣言してないけどいいのか?

とにかくPythonでは普通に通るコード。

main.py
def main():
    x = 5
    print(f"The value of x is:{x}")
    x = 6
    print(f"The value of x is:{x}") 

main()
The value of x is:5
The value of x is:6

これがrustだと通らないんか..?

   Compiling rust v0.1.0 (/home/user/linux_workspace/rust)
error[E0384]: cannot assign twice to immutable variable `x`
 --> src/main.rs:4:5
  |
2 |     let x = 5;
  |         -
  |         |
  |         first assignment to `x`
  |         help: consider making this binding mutable: `mut x`
3 |     println...
4 |     x = 6;
  |     ^^^^^ cannot assign twice to immutable variable

For more information about this error, try `rustc --explain E0384`.
error: could not compile `rust` (bin "rust") due to 1 previous error

撃沈
イミュータブル(不変)変数xに2回目の代入をするなとおっしゃる。
束縛なのか代入なのかどっちなんや

変数は、標準でのみ、不変です。つまり、 第2章のように変数名の前にmutキーワードを付けることで、可変にできるわけです。

出たなミュータブル(可変)・イミュータブル(不変)
rustは変数をデフォで不変にすることで予期せぬデータが入ることを抑制しているらしい。
変数を変数として扱いたければひと手間かけてmutを宣言しろとおっしゃる。

じゃあrustには定数がないのか?ひとまず実行

main.rs
fn main() {
    //  ↓ let の次に可変宣言
    let mut x = 5;
    println!("The value of x is: {}", x);
    x = 6;
    println!("The value of x is: {}", x);
}
The value of x is: 5
The value of x is: 6

通った。

変数と定数(constants)の違い

あるやん

変数の値を変更できないようにするといえば、他の多くの言語も持っている別のプログラミング概念を思い浮かべるかもしれません: 定数です。不変変数のように、定数は名前に束縛され、変更することが叶わない値のことですが、 定数と変数の間にはいくつかの違いがあります。

まず、定数にはmutキーワードは使えません: 定数は標準で不変であるだけでなく、常に不変なのです。

定数はletキーワードの代わりに、constキーワードで宣言し、値の型は必ず注釈しなければなりません。 型と型注釈については次のセクション、「データ型」で講義しますので、その詳細について気にする必要はありません。 ただ単に型は常に注釈しなければならないのだと思っていてください。

  1. 当然だが定数は常に不変
  2. 定数はconstで宣言し、型が必須
  3. 定数はどんなスコープでも定義できる
  4. 命名規則は全て大文字でアンダースコアで単語区切り
const MAX_POINTS: u32 = 100_000;

定数は、プログラムが走る期間、定義されたスコープ内でずっと有効です。

なるほど、rustの型定義は変数名: 型のようです。
それよりも気になるのが、定義されたスコープ内でずっと有効という文言。まるで変数はそうではないみたいな言い方...そうなの..?

シャドーイング

前に定義した変数と同じ名前の変数を新しく宣言でき、 新しい変数は、前の変数を覆い隠します。Rustaceanはこれを最初の変数は、 2番目の変数に覆い隠されたと言い、この変数を使用した際に、2番目の変数の値が現れるということです。 以下のようにして、同じ変数名を用いて変数を覆い隠し、letキーワードの使用を繰り返します

同じ名前の変数を定義できるんですか!?サンプル投下

main.rs
fn main() {
    let x = 5;
    let x = x + 1;
    {
        let x = x * 2;
        println!("The value of x in the inner scope is: {}", x);
    }
    println!("The value of x is: {}", x);
}
The value of x in the inner scope is: 12
The value of x is: 6

先生、意味が分かりません。
同じの名前の変数を宣言した場合は変更ではなく、変数に覆い隠されたといい、出力すると後に宣言した値が現れると..
mutとの違いがよく分からんな

main.rs
fn main() {
    let x = 5;
    let x = x + 1; // 変数 x を覆い隠し x=6 となる
    { // スコープここから
        let x = x * 2;
        println!("The value of x in the inner scope is: {}", x); // 12
    } // スコープここまで
    // スコープ内でいじった x は元の状態へ戻る = 6
    println!("The value of x is: {}", x); // 覆い隠されているので x=6
}

シャドーイングは、変数をmutにするのとは違います。なぜなら、letキーワードを使わずに、 誤ってこの変数に再代入を試みようものなら、コンパイルエラーが出るからです。letを使うことで、 値にちょっとした加工は行えますが、その加工が終わったら、変数は不変になるわけです。

あくまでも再代入ではなく再宣言なのか。
同じスコープ内で再宣言する必要はあまり感じないのでスコープ内で少し小細工したいときに使うのかな?

main.rs
fn main() {
    let child = "勉強中";
    println!("お母さん、今{}やから部屋出て行って。",child);
    {
        let child = "ゲーム最高";
        println!("{}",child);
    }
    println!("何?{}だよ。",child);
}
お母さん、今勉強中やから部屋出て行って。
ゲーム最高
何?勉強中だよ。

データ型

Rustは静的型付き言語であることを弁えておいてください。つまり、 コンパイル時に全ての変数の型が判明している必要があるということです。

苦しい..
データ型は非常に長いのでよく使用するもののみに絞ります。

スカラー型

  • 整数型

    大きさ 符号付き 符号なし
    8-bit i8 u8
    16-bit i16 u16
    32-bit i32 u32
    64-bit i64 u64
    arch isize usize
  • 数値演算(ほか言語と同じ)
    + - / * %

  • 論理値型
    let t = true;
    let f: bool = false;

  • 文字型
    let c = "z";

複合型

  • タプル型
    ※タプルはそれぞれの値が変数全体に束縛されている
    let tup: (i32, f64, u8) = (500, 6.4, 1);

    let tup = (500, 6.4, 1);
    let (x, y, z) = tup; // 
    println!("The value of y is: {}", y);
    

    値を1つだけ選んでアクセスするにはk一度分解し個々を新しい変数に束縛してアクセスする。

    上記の例を見る限り変数宣言は複数同時にできる模様

    もしくは変数名.index番号でもアクセスできる

    let x: (i32, f64, u8) = (500, 6.4, 1);
    println!("{}",x.0) // 500
    
  • 配列型
    let a = [1, 2, 3, 4, 5]; とてもシンプル!!
    let a: [i32; 5] = [1, 2, 3, 4, 5];

    let a: [i32; 5] = [3, 3, 3, 3, 3];
    // 値が同じ場合、下のように省略できる
    let b = [3; 5];
    

    値へのアクセスは変数名[index番号]

    let a = [1, 2, 3, 4, 5];
    println!("{}", a[0]); // 1
    

関数

Rustの関数と変数の命名規則は、スネークケース(訳注: some_variableのような命名規則)を使うのが慣例です。 スネークケースとは、全文字を小文字にし、単語区切りにアンダースコアを使うことです。

関数の命名規則はスネークケースが選択されているとのこと。好き。

fn main() { // rustプログラムは必ずmain.rsのmain関数が実行される
    println!("Hello, world!");
    another_function(); // 関数実行
}
fn another_function() {
    println!("Another function.");
}
Hello, world!
Another function.

引数をとる。引数の型は必ず定義しないといけない..

fn main() {
    another_function(5);
}
fn another_function(x: i32) {
    println!("The value of x is: {}", x); 
}

関数本体は、文と式を含む

関数本体は、文が並び、最後に式を置くか文を置くという形で形成されます。現在までには、 式で終わらない関数だけを見てきたわけですが、式が文の一部になっているものなら見かけましたね。Rustは、式指向言語なので、 これは理解しておくべき重要な差異になります。他の言語にこの差異はありませんので、文と式がなんなのかと、 その違いが関数本体にどんな影響を与えるかを見ていきましょう。

実のところ、もう文と式は使っています。文とは、なんらかの動作をして値を返さない命令です。 式は結果値に評価されます。ちょっと例を眺めてみましょう。

letキーワードを使用して変数を生成し、値を代入することは文になります。 リスト3-1でlet y = 6;は文です。

!?
文と式...文とは、なんらかの動作をして値を返さない命令です。 式は結果値に評価されます。
ようするにletのような値を束縛したりするのは文で、値を返すものは式であるとおっしゃる。

let a = 1; // 文(文は何も値を返さない)
let b = ( let c = 1 ); // エラー : 文は値を持たないので文に文を束縛できない
let d = {
    let e = 1; // 文
    e + 1 // 2という値を返すので式
}; // 最終的に 2 という値が返ってきたので変数 d に値が束縛される
println!("{}",d); // 2

式は終端にセミコロンを含みません。

これ大注意ですね、;をつけるとコンパイラはそれを文と解釈するそう。

戻り値のある関数

関数は、それを呼び出したコードに値を返すことができます。戻り値に名前を付けはしませんが、 矢印(->)の後に型を書いて確かに宣言します。Rustでは、関数の戻り値は、関数本体ブロックの最後の式の値と同義です。

関数に戻り値があるとそれは式、逆に戻り値がないとそれは関数であっても文として扱うのがrustコンパイラ様

fn five() -> i32 {
    5
}
fn main() {
    let x = five();
    println!("The value of x is: {}", x);
}

関数内で戻り値を宣言していればただの「5」でもfive()は立派な式である。

fn five() -> i32 {
    5; // セミコロンをつけると文になるので戻り値として成立せずエラーになる
}

コメント

// hello, world

以上!!!!

制御フロー

if式

とても普通。

fn main() {
    let number = 3;
    if number < 5 {
        println!("condition was true");       // 条件は真でした
    } else {
        println!("condition was false");      // 条件は偽でした
    }
}

少し前のPHPのようにif 変数名で存在すればtrue(今は!empty推奨とわかっていてもたまにやりそうになるやつ)のような甘っちょろいことは通らず、文字通りのtrue or falseでしか判定しないので条件式を与える必要がある。
下記の場合、bool型を期待しているのにnumberの値は3なのでエラー

fn main() {
    let number = 3;
    if number {
        println!("number was three");     // 数値は3です
    }
}

使用頻度は低そうだが値を返せすif文であれば変数に束縛することもできる。

fn main() {
    let condition = true;
    let number = if condition { 5 } else { 6 }; // trueなので 値5 を返す式
    println!("The value of number is: {}", number);
}

rustはコンパイル時点ですべての変数の型が判明していないといけませんが、下記の例では変数numberにu32型がはいるのかstring型が入るのかハッキリせんので、if文を束縛する場合は戻り値を同じ型にする必要がある。
同じ型であればどっちが束縛されようが型はガッツリ判明している。

fn main() {
    let condition = true;
    let number = if condition { 5 } else { "six" };
    println!("The value of number is: {}", number);
}

ループでの繰り返し

rustにはloopなるもので無条件でループさせることができる。
下記は冗談抜きでCTRL+Cで処理を終了しない限り恐怖の無限ループ。

Pythonと同様にループを終了するbreakやループ内の処理をスキップするcontinueも存在しますので、loopを使う場合は内側で適切なif文が必要..

fn main() {
    loop {
        println!("again!");
    }
}

お馴染みfor文です、宣言もpythonと同じですね!
値がある限り繰り返すシンプルなやつ、これは多用しそう。

fn main() {
    let a = [10, 20, 30, 40, 50];
    for element in a {
        println!("the value is: {}", element);
    }
}
the value is: 10
the value is: 20
the value is: 30
the value is: 40
the value is: 50

これは指定回数だけ繰り返したい場合のやつかな?
(1..4) ここでは連番を省略してるっぽい。
.rev() を付けると連番を逆順で解釈できるようです。

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

まとめ

まだまだ本の目次レベルですが、ところどころにRustの思想ががっつり出てて面白い。個人的には文と式が衝撃でした。
プログラミングで文と式なんて考えたことがなかった..

次は最難関と言われている所有権です。
よろしくお願いします。

参考

Rust Book
Rust Book データ型

Discussion