Open140

Rust の the book の 1〜5 章までを読む

薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

インストール

https://doc.rust-lang.org/book/ch01-01-installation.html

コマンド
curl --proto '=https' --tlsv1.2 https://sh.rustup.rs -sSf | sh
コンソール出力
info: downloading installer

Welcome to Rust!

This will download and install the official compiler for the Rust
programming language, and its package manager, Cargo.

Rustup metadata and toolchains will be installed into the Rustup
home directory, located at:

  /Users/susukida/.rustup

This can be modified with the RUSTUP_HOME environment variable.

The Cargo home directory is located at:

  /Users/susukida/.cargo

This can be modified with the CARGO_HOME environment variable.

The cargo, rustc, rustup and other commands will be added to
Cargo's bin directory, located at:

  /Users/susukida/.cargo/bin

This path will then be added to your PATH environment variable by
modifying the profile files located at:

  /Users/susukida/.profile
  /Users/susukida/.zshenv

You can uninstall at any time with rustup self uninstall and
these changes will be reverted.

Current installation options:


   default host triple: aarch64-apple-darwin
     default toolchain: stable (default)
               profile: default
  modify PATH variable: yes

1) Proceed with installation (default)
2) Customize installation
3) Cancel installation
>1

info: profile set to 'default'
info: default host triple is aarch64-apple-darwin
info: syncing channel updates for 'stable-aarch64-apple-darwin'
info: latest update on 2024-02-08, rust version 1.76.0 (07dca489a 2024-02-04)
info: downloading component 'cargo'
info: downloading component 'clippy'
info: downloading component 'rust-docs'
info: downloading component 'rust-std'
info: downloading component 'rustc'
 54.7 MiB /  54.7 MiB (100 %)  38.2 MiB/s in  2s ETA:  0s
info: downloading component 'rustfmt'
info: installing component 'cargo'
info: installing component 'clippy'
info: installing component 'rust-docs'
 14.7 MiB /  14.7 MiB (100 %)  11.9 MiB/s in  1s ETA:  0s
info: installing component 'rust-std'
info: installing component 'rustc'
 54.7 MiB /  54.7 MiB (100 %)  26.5 MiB/s in  1s ETA:  0s
info: installing component 'rustfmt'
info: default toolchain set to 'stable-aarch64-apple-darwin'

  stable-aarch64-apple-darwin installed - rustc 1.76.0 (07dca489a 2024-02-04)


Rust is installed now. Great!

To get started you may need to restart your current shell.
This would reload your PATH environment variable to include
Cargo's bin directory ($HOME/.cargo/bin).

To configure your current shell, run:
source "$HOME/.cargo/env"
薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

C コンパイラのインストール

コマンド
xcode-select --install
コンソール出力
xcode-select: error: command line tools are already installed, use "Software Update" in System Settings to install updates

どうやらすでにインストールされているようだ。

薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

Anatomy of Rust Programming

  • C 言語などと同様に main() 関数はプログラムのエントリーポイントとなる。
  • main() 関数は引数を取らず、戻り値もない。
  • 関数の内容は {} で囲まれる、これも C 言語などと同様。
  • Rust では関数名と同じ行に 1 文字半角スペースを開けて { を書くのが良いとされる。
  • rustfmt コマンドを使えば良い感じにフォーマットしてくれる。
  • Rust では半角スペース 4 文字でインデントを揃える、タブではない。
  • println!() は関数に見えるが実際にはマクロ、println() なら関数。
  • マクロについては相当後の章(第 19 章)で説明される。
  • 現状では ! で終わる場合はマクロを呼び出しており、関数とは若干異なるということだけを知っていれば OK。
  • Rust では式の末尾が ; で終わる、これも C 言語などと同じ。
fn main() {

}
薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

Cargo を使ったプロジェクト作成

コマンド
cargo new hello_cargo
cd hello_cargo
コンソール出力
Created binary (application) `hello_cargo` package
薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

Cargo.toml を見てみる

Cargo.toml
[package]
name = "hello_cargo"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
  • package セクションではパッケージに関する設定を行う。
  • name, version, edition はコンパイルを行うために必須。
  • dependencies セクションはプロジェクトの依存関係。
  • Rust ではパッケージはクレートと呼ばれる、クレートは木箱を意味する英単語。
薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

ディレクトリ構成

  • ソースコードは src ディレクトリに入れる。
  • README やライセンスや構成に関するファイルはトップレベルに入れる。
薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

Cargo ビルド

コマンド
cargo build
コンソール出力
   Compiling hello_cargo v0.1.0 (/Users/susukida/workspace/rust/hello_cargo)
    Finished dev [unoptimized + debuginfo] target(s) in 19.77s
  • 実行可能ファイルは target/debug/hello_cargo に出力されるので target/debug/hello_cargo を実行すれば "Hello, world!" と表示される。
  • デフォルトのビルドはデバッグビルドなので target/debug ディレクトリ内に出力される。
  • cargo build を実行すると Cargo.lock ファイルが作成され、このファイルには依存関係のバージョンが保存される。
薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

Cargo Run

コマンド
cargo run
コンソール出力
    Finished dev [unoptimized + debuginfo] target(s) in 0.00s
     Running `target/debug/hello_cargo`
Hello, world!
  • 1 つのコマンドでビルド&実行できる。
  • ソースコードが編集されていなくてビルドが不要な場合はスキップされる。
  • 編集は更新日時で判断しているらしく、内容が変わっていなくてもビルドはされる。
コンソール出力
   Compiling hello_cargo v0.1.0 (/Users/susukida/workspace/rust/hello_cargo)
    Finished dev [unoptimized + debuginfo] target(s) in 0.19s
     Running `target/debug/hello_cargo`
Hello, world!
薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

Cargo Check

コマンド
cargo check
コンソール出力
    Checking hello_cargo v0.1.0 (/Users/susukida/workspace/rust/hello_cargo)
    Finished dev [unoptimized + debuginfo] target(s) in 0.11s

コンパイルが通るかを確認するだけであればこちらの方が早そう。

薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

Cargo コマンドまとめ

  • cargo new: プロジェクト作成
  • cargo build: ビルド
  • cargo run: ビルド&実行
  • cargo check: コンパイルが通るかの確認

Cargo コマンドの利点として Windows だろうが Mac / Linux だろうが同じコマンドで良いことが挙げられる。

薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

リリース版のビルド

コマンド
cargo build --release
コンソール出力
   Compiling hello_cargo v0.1.0 (/Users/susukida/workspace/rust/hello_cargo)
    Finished release [optimized] target(s) in 0.20s
  • 実行可能ファイルは target/release ディレクトリに出力される。
  • リリース版のビルドは最適化がされるので時間がかかるが実行は高速になる。
薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

コーディング

src/main.rs
use std::io;

fn main() {
    println!("Guess the number!");

    println!("Please input your guess.");

    let mut guess = String::new();

    io::stdin()
        .read_line(&mut guess)
        .expect("Failed to read line");

    println!("You guessed: {guess}");
}
  • ユーザー入力を取得するには std::io ライブラリを使用する必要がある。
  • std::io ライブラリを使えるようにするには use std::io; と冒頭に書く。
  • プレリュードというものがよくわからないが全てのプログラムに必要とされるものを一気にインポートしてくれるようだ。
  • 使いたいものがプレリュードに含まれていない場合は明示的にインポートする必要がある。

https://doc.rust-lang.org/std/prelude/index.html

https://zenn.dev/utah/articles/7888ee687fa16a

薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

Storing Values with Variables

https://doc.rust-lang.org/book/ch02-00-guessing-game-tutorial.html#storing-values-with-variables

下記のコードはユーザー入力を保存する変数を作成している。

    let mut guess = String::new();

一方、下記のコードは 5 にバインド(?)された変数を作成している。

let apples = 5;

Rust ではデフォルトで変数は immutable(不変)であり、変更することはできない。

後から変更できるようにするには mut キーワードを使う必要がある。

let apples = 5; // immutable
let mut bananas = 5; // mutable

ちなみに // はコメント。

String::new() は文字列のインスタンスを作成して返す関数。

String は growable(多分長さを増やせるという意味)な UTF-8 文字列の型であり、標準ライブラリから提供される。

String:: は String 型に関連付けられた関数であることを示している。

new() という関数名は Rust ではかなり一般的に使用され、何かの新しい値を作成する関数に使用される。

薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

Receiving User Input

https://doc.rust-lang.org/book/ch02-00-guessing-game-tutorial.html#receiving-user-input

    io::stdin()
        .read_line(&mut guess)

io モジュールの stdin() 関数を呼び出すと標準入力にアクセスできるようだ。

use std:io; を使わない場合は std::io:stdin とフルネームを書く必要がある。

stdin() 関数の戻り値は std::io:Stdin 型のインスタンスであり、このタイプはターミナルからの標準入力を扱うため型である。

続いて read_line() は標準入力型のメソッドであり、ユーザーからの入力を取得するために使用される。

引数としては &mut guess が与えられており、guess 変数にユーザー入力が与えられる。

read_line() メソッドは上書きするのではなく追記するようだ。

& は参照であることを示し、コードの複数の部分がコピーなしでデータにアクセスできるようにする。

参照は複雑な機能であり、安全かつ簡単に参照を使えることは Rust の主要な利点である。

変数のように参照もデフォルトでは不変である、C で言うところの const char * みたいな感じがする。

&guess と書くと変更できない参照になるが、&mut guess と変更できる参照になる。

薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

Handling Potential Failure with Result

        .expect("Failed to read line");

read_line() 関数は Rusult と言う列挙型の値を返す。

列挙型のそれぞれの値はバリアントと呼ばれる。

Result 型はエラー処理情報を符号化(?)するために利用され、バリアントは OkErr の 2 つ。

Ok は処理が成功したことを示し、Ok に含まれているものは成功して生成された値が含まれる。

Err は処理が失敗したことを示し、Err には処理が失敗した理由に関する情報が含まれている。

Result 型は expect() メソッドを持っており、Err の場合は expect() はプログラムをクラッシュさせ、引数として与えられたメッセージをコンソールに出力する。

もし read_line() 関数が Err を返した場合、OS のエラーの可能性が高い。

一方、Ok を返した場合、expect() 関数は Ok インスタンスが保持している戻り値を返す。

read_line() の場合はユーザー入力のバイト数になる。

もし expect() 関数を呼び出さない場合は下記の警告が表示される。

コンソール出力
warning: unused `Result` that must be used
  --> src/main.rs:10:5
   |
10 |     io::stdin().read_line(&mut guess);
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   |
   = note: `#[warn(unused_must_use)]` on by default
   = note: this `Result` may be an `Err` variant, which should be handled

warning: `guessing_game` (bin "guessing_game") generated 1 warning
    Finished dev [unoptimized + debuginfo] target(s) in 0.52s

Rust では Result 型の値を使用しないと警告が表示され、それはプログラムがエラー処理を省略している恐れがあることを示す。

警告が表示されないようにするにはエラー処理のコードを書けば良いが、今回のケースではただプログラムをクラッシュさせれば良いだけなので、その場合は expect() 関数を呼び出すのが便利。

薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

Printing Values with println! Placeholders

文字列内の {} はプレースホルダーと呼ばれ、カッコ内に変数名を書くと内容が表示される。

変数ではなく式の評価結果を表示する場合、空のプレースホルダーを置いて第 2 引数に式を書く。

let x = 5;
let y = 10;

println!("x = {x} and y + 2 = {}", y + 2);

このコードは実行結果は下記のようになる。

コンソール出力
x = 5 and y + 2 = 12
薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

依存関係の追加

Cargo.toml(末尾)
[dependencies]
rand = "0.8.5"

0.8.5^0.8.5 の省略記法であり、0.8.5 以上 0.9 未満であることを指定する。

この状態でビルドしてみる。

コマンド
cargo build
コンソール出力
   Compiling cfg-if v1.0.0
   Compiling ppv-lite86 v0.2.17
   Compiling libc v0.2.153
   Compiling getrandom v0.2.12
   Compiling rand_core v0.6.4
   Compiling rand_chacha v0.3.1
   Compiling rand v0.8.5
   Compiling guessing_game v0.1.0 (/Users/susukida/workspace/rust/guessing_game)
warning: unused `Result` that must be used
  --> src/main.rs:10:5
   |
10 |     io::stdin().read_line(&mut guess);
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   |
   = note: `#[warn(unused_must_use)]` on by default
   = note: this `Result` may be an `Err` variant, which should be handled

warning: `guessing_game` (bin "guessing_game") generated 1 warning
    Finished dev [unoptimized + debuginfo] target(s) in 2.53s

先ほどのコメントアウトを消し忘れていたのでエラーになってしまったが rand の依存関係がコンパイルされている様子がわかる。

ダウンロードされている様子が見当たらないのは VSCode によって事前にダウンロードされたからかも知れない。

薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

Ensuring Reproducible Builds with the Cargo.lock File

  • Cargo は同じ成果物を再ビルドできるしくみを持っている。
  • Cargo.lock にビルド時に使用したバージョンを書き込むことで実現している。
  • したがって Cargo.lock はバージョン管理に含めるべき。
薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

Updating a Crate to Get a New Version

https://doc.rust-lang.org/book/ch02-00-guessing-game-tutorial.html#updating-a-crate-to-get-a-new-version

クレートのバージョンを更新したい場合は下記のコマンドを実行する。

コマンド
cargo update

Cargo は Cargo.lock ファイルを無視して Cargo.toml を見て最新バージョンを取得する。

もし rand クレートのバージョン v0.8.6 がリリースされていたら下記のように表示される。

コンソール出力
    Updating crates.io index
    Updating rand v0.8.5 -> v0.8.6

もし v0.9.x がリリースされていても更新されない。

v0.9.x に更新したい場合は Cargo.toml を編集して明示的に指定する必要がある。

Cargo.toml
[dependencies]
rand = "0.9.0"
薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

Generating a Random Number

src/main.rs
use rand::Rng;
use std::io;

fn main() {
    println!("Guess the number!");

    let secret_number = rand::thread_rng().gen_range(1..=100);

    println!("The secret number is: {secret_number}");

    println!("Please input your guess.");

    let mut guess = String::new();

    io::stdin()
        .read_line(&mut guess)
        .expect("Failed to read line");

    println!("You guessed: {guess}");
}

Rng トレート(?)は乱数生成器が実装するメソッドを定義している。

これらのメソッドを使うためにトレイトをスコープに含める必要がある。

rand::thread_rng() 関数は現在のスレッドに対してローカル(?)で OS によってシードされる乱数生成器を作成する。

gen_range() メソッドは Rng トレイトで定義されており、引数としてはレンジ式を受け取ってレンジ内の乱数を生成する。

start..=end は start と end が inclusive なので 1..=100 は 1〜100 までの範囲となる。

薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

Comparing the Guess to the Secret Number

https://doc.rust-lang.org/book/ch02-00-guessing-game-tutorial.html#comparing-the-guess-to-the-secret-number

src/main.rs
use rand::Rng;
use std::{cmp::Ordering, io};

fn main() {
    println!("Guess the number!");

    let secret_number = rand::thread_rng().gen_range(1..=100);

    println!("The secret number is: {secret_number}");

    println!("Please input your guess.");

    let mut guess = String::new();

    io::stdin()
        .read_line(&mut guess)
        .expect("Failed to read line");

    println!("You guessed: {guess}");

    match guess.cmp(&secret_number) {
        Ordering::Less => println!("Too small!"),
        Ordering::Greater => println!("Too big!"),
        Ordering::Equal => println!("You win!"),
    }
}

このコードをビルドしようとすると下記のエラーが表示される。

コンソール出力
   Compiling guessing_game v0.1.0 (/Users/susukida/workspace/rust/guessing_game)
error[E0308]: mismatched types
   --> src/main.rs:21:21
    |
21  |     match guess.cmp(&secret_number) {
    |                 --- ^^^^^^^^^^^^^^ expected `&String`, found `&{integer}`
    |                 |
    |                 arguments to this method are incorrect
    |
    = note: expected reference `&String`
               found reference `&{integer}`
note: method defined here
   --> /Users/susukida/.rustup/toolchains/stable-aarch64-apple-darwin/lib/rustlib/src/rust/library/core/src/cmp.rs:814:8
    |
814 |     fn cmp(&self, other: &Self) -> Ordering;
    |        ^^^

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

データ型が異なるようだ。

薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

コードの修正

src/main.rs
use rand::Rng;
use std::{cmp::Ordering, io};

fn main() {
    println!("Guess the number!");

    let secret_number = rand::thread_rng().gen_range(1..=100);

    println!("The secret number is: {secret_number}");

    println!("Please input your guess.");

    let mut guess = String::new();

    io::stdin()
        .read_line(&mut guess)
        .expect("Failed to read line");

    let guess: u32 = guess.trim().parse().expect("Please type a number!");

    println!("You guessed: {guess}");

    match guess.cmp(&secret_number) {
        Ordering::Less => println!("Too small!"),
        Ordering::Greater => println!("Too big!"),
        Ordering::Equal => println!("You win!"),
    }
}
薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

シャドウィング

Rust では変数名の再定義が可能。

データ型の変換時によく使用される。

再定義する場合、右辺は前の値になる。

薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

コードの現状

src/main.rs
use rand::Rng;
use std::{cmp::Ordering, io};

fn main() {
    println!("Guess the number!");

    let secret_number = rand::thread_rng().gen_range(1..=100);

    println!("The secret number is: {secret_number}");

    println!("Please input your guess.");

    let mut guess = String::new();

    io::stdin()
        .read_line(&mut guess)
        .expect("Failed to read line");

    let guess: u32 = guess.trim().parse().expect("Please type a number!");

    println!("You guessed: {guess}");

    match guess.cmp(&secret_number) {
        Ordering::Less => println!("Too small!"),
        Ordering::Greater => println!("Too big!"),
        Ordering::Equal => println!("You win!"),
    }
}
薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

loop と break

src/main.rs
use rand::Rng;
use std::{cmp::Ordering, io};

fn main() {
    println!("Guess the number!");

    let secret_number = rand::thread_rng().gen_range(1..=100);

    println!("The secret number is: {secret_number}");

    loop {
        println!("Please input your guess.");

        let mut guess = String::new();

        io::stdin()
            .read_line(&mut guess)
            .expect("Failed to read line");

        let guess: u32 = guess.trim().parse().expect("Please type a number!");

        println!("You guessed: {guess}");

        match guess.cmp(&secret_number) {
            Ordering::Less => println!("Too small!"),
            Ordering::Greater => println!("Too big!"),
            Ordering::Equal => {
                println!("You win!");
                break;
            }
        }
    }
}
  • loop を使うとブロックを繰り返せる。
  • match 式でもブロックを使える。
  • break を使うとループを抜け出せる。
薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

Handling Invalid Input

src/main.rs(抜粋)
        let guess: u32 = match guess.trim().parse() {
            Ok(num) => num,
            Err(_) => continue,
        };

match 式はこんな風にも使える。

JavaScript の switch 文とは異なり、値も取り出せるのはすごい便利だ。

気になったので Result の定義を調べたら下記のようになっている。

#[derive(Copy, PartialEq, PartialOrd, Eq, Ord, Debug, Hash)]
#[must_use = "this `Result` may be an `Err` variant, which should be handled"]
#[rustc_diagnostic_item = "Result"]
#[stable(feature = "rust1", since = "1.0.0")]
pub enum Result<T, E> {
    /// Contains the success value
    #[lang = "Ok"]
    #[stable(feature = "rust1", since = "1.0.0")]
    Ok(#[stable(feature = "rust1", since = "1.0.0")] T),

    /// Contains the error value
    #[lang = "Err"]
    #[stable(feature = "rust1", since = "1.0.0")]
    Err(#[stable(feature = "rust1", since = "1.0.0")] E),
}

Err(_) のアンダーバーは catchall value と呼ばれ、Err だったらとにかくマッチする。

ということはそうではないケースもあるということだ。

薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

Variables and Mutability

https://doc.rust-lang.org/book/ch03-01-variables-and-mutability.html

繰り返しになるが変数はデフォルトで変更不可となる。

プロジェクトのために新たなワークスペースを作成する。

コマンド
cd ~/workspace/rust
cargo new variables
cd variables
src/main.rs
fn main() {
    let x = 5;
    println!("The value of x is: {x}");
    x = 6;
    println!("The value of x is: {x}");
}

上記のコードは動かない、なぜなら変更不可の x に再代入しようとしているため。

cargo run 実行時のエラーメッセージは下記の通り。

コンソール出力
   Compiling variables v0.1.0 (/Users/susukida/workspace/rust/variables)
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!("The value of x is: {x}");
4 |     x = 6;
  |     ^^^^^ cannot assign twice to immutable variable

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

変更可能にするには let の後に mut と書く。

src/main.rs
fn main() {
    let mut x = 5;
    println!("The value of x is: {x}");
    x = 6;
    println!("The value of x is: {x}");
}
コンソール出力
   Compiling variables v0.1.0 (/Users/susukida/workspace/rust/variables)
    Finished dev [unoptimized + debuginfo] target(s) in 0.35s
     Running `target/debug/variables`
The value of x is: 5
The value of x is: 6
薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

Constants

  • 変更不可な変数と定数は異なる。
  • 定数に mut キーワードは使用できない。
  • 定数は型を明示する必要がある。
  • 定数はグローバルを含むどんなスコープでも宣言できる。
  • 定数の右辺値は定数式のみ。
  • 定数名には大文字キャメルケースを使う。
  • 定数式では足し算や掛け算など基本的な演算を行える。
薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

Shadowing

https://doc.rust-lang.org/book/ch03-01-variables-and-mutability.html#shadowing

  • 同じ名前の変数を宣言できる。
  • 右辺にその変数が含まれる場合は直前の値が使用される。
  • これは「1 番目の変数が 2 番目の変数によってシャドウされる」と呼ばれる。
コマンド
mkdir ~/workspace/rust
cargo new shadowing
cd shadowing
src/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}");
}
コマンド
cargo run
コンソール出力
   Compiling shadowing v0.1.0 (/Users/susukida/workspace/rust/shadowing)
    Finished dev [unoptimized + debuginfo] target(s) in 2.58s
     Running `target/debug/shadowing`
The value of x in the inner scope is: 12
The value of x is: 6

2 回目のシャドウィングはスコープが終わると同時に終わり、そのあとは 1 回目のシャドウィングの値である 5 + 1 = 6 が表示される。

もし let を忘れたらコンパイルエラーが表示される、なぜなら mut キーワードが無くて再代入ができないため。

シャドウィングでは前後で同じ型である必要はない、これは型変換を行うときには便利そうだ。

    let spaces = "   ";
    let spaces = spaces.len();

もし下記のように書いたら型が異なるのでエラーになる。

    let mut spaces = "   ";
    spaces = spaces.len();
コンソール出力
   Compiling shadowing v0.1.0 (/Users/susukida/workspace/rust/shadowing)
error[E0308]: mismatched types
  --> src/main.rs:14:14
   |
13 |     let mut spaces = "   ";
   |                      ----- expected due to this value
14 |     spaces = spaces.len();
   |              ^^^^^^^^^^^^ expected `&str`, found `usize`
   |
help: try removing the method call
   |
14 -     spaces = spaces.len();
14 +     spaces = spaces;
   |

For more information about this error, try `rustc --explain E0308`.
error: could not compile `shadowing` (bin "shadowing") due to 1 previous error
薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

Data Types

  • Rust は静的に型付けされた言語であり、コンパイル時に型が決まっている必要がある。
  • コンパイラーが 1 つの型を推論できるケースがあるので明示的な指定は必須ではない。
  • ただし、複数の型が推論されるケースでは曖昧さを無くすために明示的な指定が必要となる。
let guess: u32 = "42".parse().expect("Not a number!");

上記のコードで : u32 を省いたら下記のようなエラーメッセージが表示される。

コンソール出力
   Compiling data_types v0.1.0 (/Users/susukida/workspace/rust/data_types)
error[E0284]: type annotations needed
 --> src/main.rs:2:9
  |
2 |     let guess = "42".parse().expect("Not a number!");
  |         ^^^^^        ----- type must be known at this point
  |
  = note: cannot satisfy `<_ as FromStr>::Err == _`
help: consider giving `guess` an explicit type
  |
2 |     let guess: /* Type */ = "42".parse().expect("Not a number!");
  |              ++++++++++++

エラーメッセージの内容は「型アノテーションが必要」とのこと。

薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

Integer Types

https://doc.rust-lang.org/book/ch03-02-data-types.html#integer-types

  • 符号のありなしとビット数(8, 16, 32, 64, 128, アーキテクチャ)によって i8 / u8 〜 i128 / u128 + isize / usize がある。
  • アーキテクチャが 64 ビット系だと 64 ビットになる。
  • 10 進数のリテラルは 98_222 のように書ける。
  • 2 / 8 / 16 進数のリテラルは 0b / 0o / 0x を前置して書ける、_ も使える。
  • u8 だけだが b'A' のように書くと ASCII コードを表示できる。
  • 57u8 のように整数のデータ型を後置することでリテラルのデータ型を明示的に指定できる。
  • Rust ではデフォルトでは i32 が使用される。
  • 何にするか決めかねた場合はまずは i32 を使ってみる。
薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

Integer Overflow

  • Rust では整数値のオーバーフローが発生した場合、デバッグ版だとパニックする。
  • パニックとはプログラムがエラーで終了すること。
  • リリース版だとパニックせずに C 言語のような動作になる、2 の補数ラッピングと呼ばれるらしい。
  • 意図的に 2 の補数ラッピングを使いたい場合は wrapping_add などのメソッドを使用する。
  • オーバーフローで None を返したい場合は checked_add などを使う。
  • オーバーフローの有無と結果を返したい場合は overflowing_add などを使う。
  • 最大値・最小値で飽和させたい場合は saturating_add などを使う。
薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

Numeric Operations

https://doc.rust-lang.org/book/ch03-02-data-types.html#numeric-operations

fn main() {
    // addition
    let sum = 5 + 10;

    // subtraction
    let difference = 95.5 - 4.3;

    // multiplication
    let product = 4 * 30;

    // division
    let quotient = 56.7 / 32.2;
    let truncated = -5 / 3; // Results in -1

    // remainder
    let remainder = 43 % 5;
}
  • Rust では C 言語や Java と同じように算術演算を書ける。
  • -5 / 3-1 になるのは整数と整数の演算だからだろう。
薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

The Tuple Type

fn main() {
    let tup: (i32, f64, u8) = (500, 6.4, 1);
}
  • タプル型は複数の値を扱う型であり、それぞれの値の型は異なっても良い。
  • タプル型は固定長であり、後から増減させることはできない。
src/main.rs
fn main() {
    let tup = (500, 6.4, 1);

    let (x, y, z) = tup;

    println!("The value of y is: {y}");
}
コンソール出力
The value of y is: 6.4
  • タプルをパーツごとに異なる変数にバインドすることを destructuring と呼ぶ、日本語では分解か?
  • tup.0 のようにアクセスすることもできる。

ユニット

  • 値のないタプル () は「ユニット」と呼ばれる、Lisp の nil みたいだ。
  • 値も型も () と表現される。
  • 何も返さない式は実はユニットを返している。
薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

The Array Type

https://doc.rust-lang.org/book/ch03-02-data-types.html#the-array-type

rust
fn main() {
    let a = [1, 2, 3, 4, 5];
}
  • 配列もタプルのように複数の値を扱う型だがタプルとは異なりデータ型は同じである必要がある。
  • 要素数についてはタプルと同様に固定長である。
  • 配列はヒープではなくスタックにデータを格納したい時やデータが固定長であることを確認したい場合に便利である。
  • 配列とは別にベクタがあり、こちらは可変長である。
  • 配列かベクタを使うかはコーディング時に要素数が確定しているかどうかで決まる。
  • もし確定しているならほとんどのケースでは配列が適している。
  • 配列のデータ型は [i32; 5] のように書ける。
  • この書き方は配列の初期化時にも使える、例えば [3; 5][3, 3, 3, 3, 3] と同じである。
薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

Invalid Array Element Access

https://doc.rust-lang.org/book/ch03-02-data-types.html#invalid-array-element-access

src/main.rs
use std::io;

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

    println!("Please enter an array index.");

    let mut index = String::new();

    io::stdin()
        .read_line(&mut index)
        .expect("Failed to read line");

    let index: usize = index
        .trim()
        .parse()
        .expect("Index entered was not a number");

    let element = a[index];

    println!("The value of the element at index {index} is: {element}");
}

上記のコードに 10 などを入力すると下記のパニックが発生する。

コンソール出力
thread 'main' panicked at src/main.rs:19:19:
index out of bounds: the len is 5 but the index is 10
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

そのプロセスについては明日改めて読んでみよう。

薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida
  • Rust は C 言語とは異なり、配列の添え字チェックを行ってくれる。
  • これはリリース版でも行われるのだろうか?
  • 試したところ行われるようだ。
薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

Statements and Expressions

  • 文は値を返さない。
  • 式は値として評価される。
  • 例えば let y = 6; は文である。
  • 代入は文なので let x = (let y = 6); のように書くことはできない。
  • C 言語などでは代入は式として扱われ、x = y = 6 のように書ける。
  • 例えば 5 + 6 は式であり、11 に評価される。
  • 関数やマクロの呼び出しは式として扱われる。
  • 新しいスコープブロックも式として扱われる。
src/main.rs
fn main() {
    let y = {
        let x = 3;
        x + 1
    };

    println!("The value of y is: {y}");
}
  • 上記のコードでは y の値は 4 になる。
  • x + 1 の最後にセミコロンをつけないように気を付ける、つけたら式ではなく文になる。
薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

Functions with Return Values

https://doc.rust-lang.org/book/ch03-03-how-functions-work.html#functions-with-return-values

src/main.rs
fn five() -> i32 {
    5
}

fn main() {
    let x = five();

    println!("The value of x is: {x}");
}
  • 関数の戻り値に名前をつける必要はないが型を明示的に指定する必要がある。
  • 関数の最後に書かれた式が戻り値になる。
  • return キーワードを使って関数の途中で値を返すこともできる。
src/main.rs
fn main() {
    let x = plus_one(5);

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

fn plus_one(x: i32) -> i32 {
    x + 1
}
薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

Control Flow

https://doc.rust-lang.org/book/ch03-05-control-flow.html

src/main.rs
fn main() {
    let number = 3;

    if number < 5 {
        println!("condition was true");
    } else {
        println!("condition was false");
    }
}
  • Rust の if 文では () はいらない、あっても大丈夫だが警告が表示される。
  • よく見たら if 文ではなく if 式だ、したがって変数の右辺値としてバインドができそうだ。
  • if 式に関連付けられたコードブロックは「アーム」と呼ばれる。
  • else は省略できるが式として使う場合は if アームの型をユニット型にする必要がある、つまりブロックの最後は式ではなく文で終わらせる必要がある。
  • if の条件は必ず bool 型である必要がある。
薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

Handling Multiple Conditions with else if

https://doc.rust-lang.org/book/ch03-05-control-flow.html#handling-multiple-conditions-with-else-if

src/main.rs
fn main() {
    let number = 6;

    if number % 4 == 0 {
        println!("number is divisible by 4");
    } else if number % 3 == 0 {
        println!("number is divisible by 3");
    } else if number % 2 == 0 {
        println!("number is divisible by 2");
    } else {
        println!("number is not divisible by 4, 3, or 2");
    }
}

これについては他の言語とほぼ同様だが Rust では mutch 式があるのでこちらを使った方が良さそうだ。

薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

Repetition with Loops

src/main.rs
fn main() {
    let mut counter = 0;

    let result = loop {
        counter += 1;

        if counter == 10 {
            break counter * 2;
        }
    };

    println!("The result is {result}");
}
  • Rust では loop, while, for の 3 つの種類のループがある。
  • breakcontinue が使えるのは他の言語と同じ、ただし break で値が返せる。
薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

Loop Labels to Disambibuate Between Multiple Loops

https://doc.rust-lang.org/book/ch03-05-control-flow.html#loop-labels-to-disambiguate-between-multiple-loops

src/main.rs
fn main() {
    let mut count = 0;
    'counting_up: loop {
        println!("count = {count}");
        let mut remaining = 10;

        loop {
            println!("remaining = {remaining}");
            if remaining == 9 {
                break;
            }
            if count == 2 {
                break 'counting_up;
            }
            remaining -= 1;
        }

        count += 1;
    }
    println!("End count = {count}");
}
コンソール出力
count = 0
remaining = 10
remaining = 9
count = 1
remaining = 10
remaining = 9
count = 2
remaining = 10
End count = 2
  • break と continue は何も指定しないと最も内側のループブロックに対して行われる。
  • ループラベルを指定することで対象ブロックを明示的に指定できる。
  • ループラベルはシングルクオートから始める必要がある。
薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

Looping Through a Collection with for

https://doc.rust-lang.org/book/ch03-05-control-flow.html#looping-through-a-collection-with-for

src/main.rs
fn main() {
    let a = [10, 20, 30, 40, 50];
    let mut index = 0;

    while index < 5 {
        println!("the value is: {}", a[index]);

        index += 1;
    }
}
  • 上記の方法でも配列を走査できるが配列の要素数が減ったりするとパニックになってしまう。
  • また、毎回添え字が範囲内にあるかをチェックするので実行速度も遅いようだ。
  • このようなケースでは for を使う方が良い。
src/main.rs
fn main() {
    let a = [10, 20, 30, 40, 50];

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

for はレンジ式と組み合わせることでさらに便利になる。

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

    println!("LIFTOFF!!!");
}
薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

Understanding Ownership

オーナーシップは Rust の特徴というよりも独自性という方が良いのかも知れない。

オーナーシップがあることでガベージコレクションを使わずにメモリ安全を実現している。

ボロウィング、スライス、メモリ内のデータ配置について理解することが重要なようだ。

薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

What Is Ownership?

https://doc.rust-lang.org/book/ch04-01-what-is-ownership.html

  • オーナーシップとは Rust のプログラムがどのようにメモリを管理するかを統治するルールである。
  • 言語によってはガベージコレクションや明示的なメモリ確保/解放によってメモリ管理を行なっている。
  • Rust ではこれらの方法を用いずにオーナーシップの仕組みを用いてメモリ管理を行なっている。
  • オーナーシップの規則に反するようなプログラムを書くとコンパイルが通らない。
  • オーナーシップのしくみによって実行時の速度が犠牲になることはない。

オーナーシップは新しい概念なので慣れるまでには時間がかかる、すぐに理解できなくても焦る必要はない。

薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

The Stack and the Heap

https://doc.rust-lang.org/book/ch04-01-what-is-ownership.html#the-stack-and-the-heap

  • 普段プログラミングをする時にはデータがスタックにあるかヒープにあるかを意識することは少ないかも知れないが、Rust のようなシステムプログラミング言語ではかなり重要になる。
  • オーナーシップはスタックやヒープにも関わるので軽くおさらいしてみる。
  • スタックもヒープもプログラムで利用可能なメモリである点は共通している。
  • スタックは固定長である必要があるが、コンパイル時に長さが不定な場合はヒープを使う必要がある。
  • スタックはお皿、ヒープはレストランと考えると理解しやすい。
  • スタックの方が空いているメモリ領域を探す必要がないので比較的高速である。
  • 関数を呼び出す時には引数はスタックに格納される。
  • オーナーシップの主な目的はヒープ上のデータを管理することである、ということを知っておくとオーナーシップがなぜこのようになっているのかを理解しやすい。
薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

Ownership Rules

https://doc.rust-lang.org/book/ch04-01-what-is-ownership.html#ownership-rules

  • Rust では個々の値にはオーナーがいる。
  • ある時点ではオーナーは 1 つしかいない。
  • オーナーがスコープから外れると、値も一緒に削除される。

上は暗記するくらいに後から繰り返した方が良いと思うので繰り返そう。

  • 個々の値にオーナーがアイル。
  • ある時点では 1 つのオーナーしかいない。
  • オーナーがスコープから外れると値も一緒に削除される。
薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

Variable Scope

https://doc.rust-lang.org/book/ch04-01-what-is-ownership.html#variable-scope

スコープとは変数などが有効である範囲である。

{ // この時点では s はまだ宣言されていないので有効ではない。
    let s = "hello"; // s はこの行より後で有効になる。
} // ここがスコープの終端なので s は有効ではなくなる。
  • s がスコープに入った時点で有効になる。
  • s はスコープを出るまで有効であり続ける。
薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

The String Type

  • 基本的なデータ型は関数などの引数に渡されると単純にコピーされる。
  • String のようなデータ型はヒープを利用するのでオーナーシップを学ぶのに適している。
  • 文字列リテラルは変更不可であ理、可変の文字列を扱うには String 型が必要になる。
  • 文字列リテラルから String インスタンスを作成するには String::from() 関数を使う。
src/main.rs
fn main() {
    let mut s = String::from("hello");
    s.push_str(", world!");
    println!("{}", s);
}
コンソール出力
hello, world!
  • :: 演算子は String 名前空間にある from() 関数にアクセスするために使用される。

文字列リテラルは不変であるのに対して String 型は可変であり、この違いはこれらの 2 種類の型がメモリをどう扱っているかによってもたらされている。

薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

Memory and Allocation

https://doc.rust-lang.org/book/ch04-01-what-is-ownership.html#memory-and-allocation

  • 文字列リテラルはコンパイル時に内容が確定しており、実行ファイルにハードコードされる。
  • これにより高速だが不変となっている。
  • 一方、String 型ではヒープ上にメモリ領域を確保することで可変になっている。
  • String 型では実行時にアロケーターに対してメモリが要求され、メモリを使い終わったらアロケーターにメモリを返却する手段が必要となる。
  • メモリ要求は String::from() 関数を呼び出すことによって行われる。
  • Rust では変数がスコープから外れた時点で自動的にメモリ領域が返却される。
{
    let s = String::from("hello"); // s は次の行から有効になる。
} // スコープが終わったので s が有効ではなくなる。
  • 変数がスコープから外れた時、Rust は特殊な関数を呼び出し、その関数は drop() と呼ばれる。
  • String 型の実装者はメモリ領域を返却するコードを drop() 関数に書く。
  • ちなみに C++ ではこのようなパターンは RAII (resource Aquisition Is Initialization) と呼ばれる。
  • これは単純に見えるが Rust コードの書き方に大きなインパクトを与える。
薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

Variables and Data Interacting with Move

let x = 5;
let y = x;

上記のコードでは x と y はそれぞれ 5 にバインドされ、スタックには 2 つの 5 が積まれる。

let s1 = String::from("hello");
let s2 = s1;

上記のコードでは s1 は s2 にコピーされない。

スタックには 1 つの String インスタンスだけが積まれる。

String 型はポインタ、長さ、容量の 3 つのデータから構成されており、インスタンスを作るとこれらのデータがスタックに積まれる。

s2 に s1 を代入するとポインタ、長さ、容量の 3 つのデータはコピーされるが、ポインタの先にある実際の文字列データはコピーされない。

変数がスコープから外れると drop() 関数が呼ばれるが、この場合は s1 と s2 の 2 つの変数があるので 2 回呼ばれてしまいそうな気がする。

しかし実際には 1 回しか呼ばれない、なぜなら、s2 に s1 を代入した時点で s1 が有効ではなくなるため。

仮に下記のようなコードを書いた場合はコンパイルが失敗する。

src/main.rs
fn main() {
    let s1 = String::from("Hello");
    let s2 = s1;

    println!("{}, world!", s1);
}
コンソール出力
   Compiling strings v0.1.0 (/Users/susukida/workspace/rust/strings)
warning: unused variable: `s2`
 --> src/main.rs:9:9
  |
9 |     let s2 = s1;
  |         ^^ help: if this is intentional, prefix it with an underscore: `_s2`
  |
  = note: `#[warn(unused_variables)]` on by default

error[E0382]: borrow of moved value: `s1`
  --> src/main.rs:11:28
   |
8  |     let s1 = String::from("Hello");
   |         -- move occurs because `s1` has type `String`, which does not implement the `Copy` trait
9  |     let s2 = s1;
   |              -- value moved here
10 |
11 |     println!("{}, world!", s1);
   |                            ^^ value borrowed here after move
   |
   = note: this error originates in the macro `$crate::format_args_nl` which comes from the expansion of the macro `println` (in Nightly builds, run with -Z macro-backtrace for more info)
help: consider cloning the value if the performance cost is acceptable
   |
9  |     let s2 = s1.clone();
   |                ++++++++

For more information about this error, try `rustc --explain E0382`.
warning: `strings` (bin "strings") generated 1 warning
error: could not compile `strings` (bin "strings") due to 1 previous error; 1 warning emitted

String インスタンスの代入はシャロウコピーのように見えるが、実際には Rust は右辺値の変数を無効にする点が異なっている。

これはシャロウコピーではなく「移転」と呼ばれる。

したがってs1 = s2; は「s1 は s2 に移転された」と説明される。

Rust は決して自動的にディープコピーを作成することはない。

したがって通常の代入はパフォーマンスの観点からはそれほど高価な処理ではない。

薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

Variables and Data Interacting with Clone

src/main.rs
fn main() {
    let s1 = String::from("hello");
    let s2 = s1.clone();

    println!("s1 = {}, s2 = {}", s1, s2);
}

移転ではなくディープコピーをしたい場合は String の clone() メソッドを使う。

薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

Stack-Only Data: Copy

https://doc.rust-lang.org/book/ch04-01-what-is-ownership.html#stack-only-data-copy

src/main.rs
fn main() {
    let x = 5;
    let y = x;

    println!("x = {}, y = {}", x, y);
}

上記のコードは clone() メソッドを使っていないが問題なくコンパイルできる。

この理由は整数のような型はコンパイル時にサイズが決まっているのでスタックに積むことができ、素早くコピーすることができる。

したがって y に x を代入した時点で x を無効にする必要がない。

換言するとディープコピーとシャローコピーに違いがない。

Rust には Copy と呼ばれる特別なアノテーションがあり、このアノテーションを使うとスタックに積める型を作れる。

Copy トレイトが実装されている型の変数では移転が起こらず、コピーがスタック内で行われる。

ある型が Drop トレイトを実装している場合、Copy でアノテートすることができない。

これはある型が Drop トレイトを実装している型を含む場合も同様である。

スカラー値は Copy トレイトが実装されている。

Copy トレイトを実装している型だけからなるタプルも Copy を実装していると見なされる。

薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

Return Values and Scope

関数が値を返すことも所有権の移転になる。

src/main.rs
fn main() {
    let s1 = gives_ownership();
    let s2 = String::from("hello");
    let s3 = takes_and_gives_back(s2);
}

fn gives_ownership() -> String {
    let some_string = String::from("yours");

    some_string
}

fn takes_and_gives_back(a_string: String) -> String {
    a_string
}

ヒープ上のデータを含む変数がスコープから外れると drop() 関数が実行されるが、所有権が移転される場合はこの限りではない。

上記のコードは意図した通りに動作するが所有権が移転したり戻ったりを取り扱うのが煩雑になっている。

関数にパラメーターを渡したいが所有権を渡したくない場合は参照を利用する。

薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

References and Borrowing

  • 参照はポインタのようなもので他の変数が所有権を持つデータにアクセスできる。
  • ポインタと異なるのは有効な値を指していることが保証されている点である。
src/main.rs
fn main() {
    let s1 = String::from("hello");
    let len = calculate_length(&s1);

    println!("The length of '{}' is {}.", s1, len);
}

fn calculate_length(s: &String) -> usize {
    s.len()
}
コンソール出力
The length of 'hello' is 5.
  • 変数の参照を渡す時には & を前置する。
  • 変数の参照を受け取る時も & を前置する。
  • calculate_length() 関数に渡されるのは参照なので s がスコープから外れても参照先のデータは無効にならない。
  • したがって以前のコードのように s を返す必要がない。
  • 参照を作成することはボロウィング(借用?)と呼ばれる。

下記のコードはコンパイルが失敗する。

src/main.rs
fn main() {
    let s = String::from("hello");
    change(&s);
}

fn change(some_string: &String) {
    some_string.push_str(", world");
}
コンソール出力
   Compiling ownership v0.1.0 (/Users/susukida/workspace/rust/ownership)
error[E0596]: cannot borrow `*some_string` as mutable, as it is behind a `&` reference
  --> src/main.rs:48:5
   |
48 |     some_string.push_str(", world");
   |     ^^^^^^^^^^^ `some_string` is a `&` reference, so the data it refers to cannot be borrowed as mutable
   |
help: consider changing this to be a mutable reference
   |
47 | fn change(some_string: &mut String) {
   |                         +++

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

参照もデフォルトでは不可変であり、可変にするには可変参照を使う必要がある。

薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

Mutable References

https://doc.rust-lang.org/book/ch04-02-references-and-borrowing.html#mutable-references

src/main.rs
fn main() {
    let mut s = String::from("hello");
    change(&mut s);
}

fn change(some_string: &mut String) {
    some_string.push_str(", world");
}

変更箇所は次の 3 つである。

  • 変数を mut にする。
  • 参照に &mut を前置する。
  • 引数に &mut を前置する。

&mut というのがなんとなく覚えにくい "mutable reference" なら mut& なので覚えやすいのだが。

可変参照を使う場合(多分、同じスコープに)他の参照を設けることができない、不可変参照でもダメ。

ただし可変参照を関数のパラメーターとして渡すのは大丈夫。

最初の可変借用は println!() を呼び出すまで続き、その間に別の借用を作成しようとするとエラーになる。

どうやら最初の可変借用を使い終わるまで、別の可変借用は作成できないようだ。

これは図書館で借りた本を返すまで、他の人がその本を借りられないのに似ている気がする。

時間が来てしまったのでこの途中から再開しよう。

The restriction preventing multiple mutable references to the same data at the same time allows for mutation but in a very controlled fashion.

薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

ここは大事そうなので全訳してしまおう

The restriction preventing multiple mutable references to the same data at the same time allows for mutation but in a very controlled fashion.

複数の可変参照が同時に同じデータにアクセスすることを防ぐ制約はとても管理された状態でデータを変更することを可能とします。

It’s something that new Rustaceans struggle with because most languages let you mutate whenever you’d like.

この制約は Rust 初学者が(理解するのに)苦戦する点です、なぜなら多くの言語では好きな時にいつでもデータを変更できるからです。

The benefit of having this restriction is that Rust can prevent data races at compile time.

この制約を設けることの利点は Rust がコンパイル時にデータレースを防げる点です。

A data race is similar to a race condition and happens when these three behaviors occur:

データレースはレースコンディションに似ており、下記 3 つの挙動が起こる時に発生します。

Two or more pointers access the same data at the same time.

2 つ以上のポインタが同時に同じデータにアクセスする時

At least one of the pointers is being used to write to the data.

少なくとも 1 つのポインタがデータを書き込むのに使われる時

There’s no mechanism being used to synchronize access to the data.

データへのアクセスを同期するのに使われるメカニズムがない時

Data races cause undefined behavior and can be difficult to diagnose and fix when you’re trying to track them down at runtime;

データレースは未定義の挙動を発生させ、実行時にこれらを追跡しようと努めている時に原因調査やバグ修正を困難にします。

Rust prevents this problem by refusing to compile code with data races!

Rust はデータレースのあるコンパイルを拒否することでこの問題を防いでいます。

薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

Mutable References 残り

あるデータへの不可変参照がある時に同じデータへの可変参照を作成することもできない。

不可変参照の使用者は値が変更されていることを想定している。

複数の不可変参照は OK、なぜなら他のデータ読み込みに対して影響を及ぼさないため。

参照のスコープは作成されてから最後に使われるところまでとなっている。

不可変参照を使い終わった後、同じデータへの可変参照を作成するのは大丈夫。

この制約は煩わしく感じられるが、アクセスしたデータが考えていた値と異なるという状況が発生せず、追跡する必要がなくなるので便利である。

薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

Dangling References

  • ダングリングポインタとはよく理解できていないが、ポインタの指している先が既に解放されたメモリ領域であり、もしかすると無関係のデータを格納するために使われているメモリ領域である恐れのあるものと考えて良いのだろうか?
  • Rust ではコンパイラがダングリングポインタ/参照が無いことを保証してくれるようだ。
src/main.rs
fn main() {
    let reference_to_nothing = dangle();
}

fn dangle() -> &String {
    let s = String::from("hello");
    &s
}

上記のコードを実行されると下記のエラーメッセージが表示されるようだ。

コンソール出力
   Compiling ownership v0.1.0 (file:///projects/ownership)
error[E0106]: missing lifetime specifier
 --> src/main.rs:5:16
  |
5 | fn dangle() -> &String {
  |                ^ expected named lifetime parameter
  |
  = help: this function's return type contains a borrowed value, but there is no value for it to be borrowed from
help: consider using the `'static` lifetime
  |
5 | fn dangle() -> &'static String {
  |                 +++++++

For more information about this error, try `rustc --explain E0106`.
error: could not compile `ownership` due to previous error

エラーメッセージのなかで下記が重要らしい。

this function's return type contains a borrowed value, but there is no value
for it to be borrowed from

この関数の戻り値の型は借用された値を含んでいるがそのような値が無い、みたいな意味だろうか?

String インスタンスの s のメモリ領域は dangle() 関数が終わると共に解放される(多分スタックとヒープの両方が解放される)。

したがってこのコードのコンパイルが通ってしまうと解放されたメモリ領域を指してしまうことになる。

上記のコードを直すには下記のようにして所有権ごと返せば良い。

fn no_dangle() -> String {
    let s = String::from("from");
    s
}
薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

The Slice Type

  • スライスを使うとコレクション内の要素の連続した並びを参照できる。
  • スライスは参照なので所有権はない。
fn first_word(s: &String) -> usize {
    let bytes = s.as_bytes();
    for (i, &item) in bytes.iter().enumerate() {
        if item == b' ' {
            return i;
        }
    }

    len()
}
  • String 型の as_bytes() メソッドを呼び出すことで u8 の配列への参照を取得できる。
  • 配列の iter() メソッドを呼び出すことで要素を取得するためのイテレーターを取得できる。
  • イテレーターの enumerate() メソッドを呼び出すことで for でタプルとしてアクセスできる。
  • タプルの 2 つ目は参照なので参照先のデータ自体にアクセスするために & を使っている。
  • Space の ASCII コードを取得するために b' ' を使っている。
薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

String Slices

文字列スライスは文字列の一部への参照である。

let s = String::from("hello world");

let hello = &s[0..5];
let world = &s[6..11];

スライス自体は始点(inclusive)と終点(exclusive)で指定するが内部的には始点と長さを保持しているようだ。

.. はレンジ記法と呼ばれる。

始点が 0 の場合や終点が文字列の長さと同じ場合は省略できる。

例えば [..5][6..] のように書ける、また、両方省略して [..] のようにも書ける。

文字列スライスは UTF-8 文字の境界に対して使用する必要がある。

文字列スライスのインデックスはバイト単位のようなので、絵文字とかではなく普通の日本語を扱う時も気をつける必要がありそうだ。

文字列スライスを使って first_word() 関数を書き換えると次のようになる。

fn first_word(s: &String) -> &str {
    let bytes = s.as_bytes();

    for (i, &item) in bytes.iter().enumerate() {
        if item == b' ' {
            return &s[0..i];
        }
    }

    return &s[..];
}

文字列スライスの利点は参照先の文字列が有効であるかをコンパイラがチェックしてくれる点にある。

例えば次のようなプログラムを書くとコンパイルが失敗する。

src/main.rs
fn main() {
    let mut s = String::from("hello world");

    let word = first_word(&s);

    s.clear(); // error!

    println!("the first word is: {}", word);
}
コンソール出力
   Compiling string_slices v0.1.0 (/Users/susukida/workspace/rust/string_slices)
error[E0502]: cannot borrow `s` as mutable because it is also borrowed as immutable
 --> src/main.rs:6:5
  |
4 |     let word = first_word(&s);
  |                           -- immutable borrow occurs here
5 |
6 |     s.clear(); // error!
  |     ^^^^^^^^^ mutable borrow occurs here
7 |
8 |     println!("the first word is: {}", word);
  |                                       ---- immutable borrow later used here

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

時間が来たので次は下記の部分から始めよう。

Recall from the borrowing rules that if we have an immutable reference to something, we cannot also take a mutable reference.

薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

不可変参照がある場合、可変参照を持つことはできないルールがある。

clear() メソッドは文字列のデータを操作するので可変参照を取得する必要がある。

メソッド呼び出しはイメージ的には可変参照を引数とする関数を呼び出す感じなのかも知れない。

不可変参照を使い終わっていないのに、可変参照を取得しようとしてコンパイルエラーになっている。

文字列スライスを使うことは不可変参照を使っているのと同じとみなされるようだ。

薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

String Slices as Parameters

https://doc.rust-lang.org/book/ch04-03-slices.html#string-slices-as-parameters

first_word() 関数のシグニチャは fn first_word(s: &String) -> &str だが、fn first_word(s: &str) -> &str と書くことで引数に文字列参照または文字列スライスを渡せるようになる。

こうすることで引数が文字列スライスの場合はそのまま渡せるのに加え、文字列の場合は参照かスライスを渡すことができるようになる。

let my_string = String::from("hello world");
let word = first_word(&my_string[0..6]);
let word = first_word(&my_string[..]);
let word = first_word(&my_string); // この行は上と同じになる。

let my_string_literal = "hello world";
let word = first_word(&my_string_literal[0..6]);
let word = first_word(&my_string_literal[..]);
let word = first_word(my_string_literal);
薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

Other Slices

https://doc.rust-lang.org/book/ch04-03-slices.html#other-slices

文字列スライスの他にも配列スライスがある。

配列の一部を参照したい場合は下記のように書く。

let array = [1, 2, 3, 4, 5];
let slice = &[1..3];
assert_eq(slice, &[2, 3]);

slice の型は &[i32] になる。

配列リテラルは文字列リテラルと同様に開始位置と件数を格納している。

まだ学んでいないがコレクションと呼ばれるものについてはスライスが利用できる。

薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida
  • 構造体とは自分で作るデータ型であり、複数の値を 1 つにまとめて名前を付けることができる。
  • 複数の値を扱う点ではタプルと似ているが、それぞれ適するユースケースが異なる。
薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

Defining and Instantiating Structs

https://doc.rust-lang.org/book/ch05-01-defining-structs.html#defining-and-instantiating-structs

src/main.rs
struct User {
    active: bool,
    username: String,
    email: String,
    sign_in_count: u64,
}

fn main() {
    let user1 = User {
        active: true,
        username: String::from("someusername123"),
        email: String::from("someone@example.com"),
        sign_in_count: 1,
    };

    println!(
        "{}, {}, {}, {}",
        user1.active, user1.username, user1.email, user1.sign_in_count
    );
}
  • 構造体内の個々の値は「フィールド」と呼ばれる。
  • 構造体のインスタンスを作成するには User { ... } のように書く。
  • インスタンス化のフィールドの並びは自由だがなるべく合わせた方がわかりやすいかも。
  • フィールドにアクセスするには . 記法を使う。
薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

Creating Instances from Other Instances with Struct Update Syntax

https://doc.rust-lang.org/book/ch05-01-defining-structs.html#creating-instances-from-other-instances-with-struct-update-syntax

    let user1 = build_user(
        String::from("someone@example.com"),
        String::from("someusername123"),
    );

    let user2 = User {
        email: String::from("another@example.com"),
        ..user1
    };

構造体更新構文を使うことで一部を除いて同じ内容の別の構造体を簡潔に作成できる。

TypeScript と似ているが .. とドットが 2 個の点と元の構造体を最後に書く点が異なる。

構造体更新構文は代入を使っているので、所有権の移転が行われることに注意する。

上の例の場合は user1.username の所有権が user2.username に移転しているので、user1.username を使うことができなくなる。

In this example, we can no longer use user1 as a whole after creating user2 because the String in the username field of user1 was moved into user2.

ドキュメントには上記のように書かれているが user1.username 以外は問題なく使える気がする。

もし username についても新しい文字列インスタンスを指定していれば所有権の移転は発生しないので、user1 の全てのフィールドは問題なく使える。

Copy トレイトを実装していればわざわざ明示的に指定しなくても所有権の移転を行わずに済みそうだ。

薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

Using Tuple Structs Without Named Fields to Create Different Types

  • Rust ではタプルとよく似たタプル構造体をサポートしている。
  • タプル構造体はフィールドの型だけを指定する。
  • タプル構造体はタプルに名前を付け、他のタプルとは異なることを示すのに便利である。
  • また色や座標などフィールド名をつけなくてもわかりそうなものを扱うのに便利である。
src/main.rs
struct Color(i32, i32, i32);
struct Point(i32, i32, i32);

fn main() {
    let black = Color(0, 0, 0);
    let origin = Point(0, 0, 0);
}
  • 上記の例ではタプルとしての型は同じだが別の型として扱われる。
  • . を使ってフィールドにアクセスできる点や分解できる点は通常のタプルと同じである。
薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

Unit Like Structs Without Any Fields

https://doc.rust-lang.org/book/ch05-01-defining-structs.html#unit-like-structs-without-any-fields

  • フィールドを持たない構造体も定義でき、unit-like 構造体と呼ばれる。
  • 日本語訳はユニット類似型だろうか?
  • 何かの型のトレイトを実装するのに便利とのことで、トレイトについては後から学習する。
src/main.rs
struct AlwaysEqual;

fn main() {
    let subject = AlwaysEqual;
}

Imagine that later we’ll implement behavior for this type such that every instance of AlwaysEqual is always equal to every instance of any other type, perhaps to have a known result for testing purposes.

上の文章の理解が及ばない、下記は機械翻訳の結果。

後で、AlwaysEqual のすべてのインスタンスが他の型のすべてのインスタンスと常に等しいように、この型の動作を実装して、おそらくテスト目的で既知の結果を取得することを想像してください。

おそらくテスト目的で既知の結果を得るために、後ほど私たちがこの型の振る舞いを AlwaysEqual のすべてのインスタンスが他の型のすべてのインスタンスと等しいように実装することを想像してください。

これは現段階では理解が及ばないが、トレイトがわかったら理解できるようになるのかも知れない。

薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

Ownership of Struct Data

  • User 構造体では &str ではなく String を使っているがこれは意図的な選択である。
  • 理由は構造体のそれぞれのインスタンスがデータの所有権を持っており、構造体が有効な限りデータも有効であるようにしたかったから。
  • &str を使うことも可能だが、そうするためには「ライフタイム」と呼ばれる Rust の機能を使う必要があり、ライフタイムについては後のチャプターで学習する。
  • ライフタイムは構造体によって参照されるデータが有効であることを構造体が有効である限り確実にする。
struct User {
    active: bool,
    username: &str,
    email: &str,
    sign_in_count: u64,
}

fn main() {
    let user1 = User {
        active: true,
        username: "someusername123",
        email: "someone@example.com",
        sign_in_count: 1,
    };
}

もし上記のようなコードを書いた場合には下記のエラーメッセージが表示される。

コンソール出力
   Compiling structs v0.1.0 (file:///projects/structs)
error[E0106]: missing lifetime specifier
 --> src/main.rs:3:15
  |
3 |     username: &str,
  |               ^ expected named lifetime parameter
  |
help: consider introducing a named lifetime parameter
  |
1 ~ struct User<'a> {
2 |     active: bool,
3 ~     username: &'a str,
  |

error[E0106]: missing lifetime specifier
 --> src/main.rs:4:12
  |
4 |     email: &str,
  |            ^ expected named lifetime parameter
  |
help: consider introducing a named lifetime parameter
  |
1 ~ struct User<'a> {
2 |     active: bool,
3 |     username: &str,
4 ~     email: &'a str,
  |

For more information about this error, try `rustc --explain E0106`.
error: could not compile `structs` due to 2 previous errors

名前付きライフタイムパラメーターが必要とのこと。

薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

An Example Program Using Structs

https://doc.rust-lang.org/book/ch05-02-example-structs.html#an-example-program-using-structs

src/main.rs
fn main() {
    let width1 = 30;
    let height1 = 50;

    println!(
        "The area of the rectangle is {} square pixels.",
        area(width1, height1)
    );
}

fn area(width: u32, height: u32) -> u32 {
    width * height
}
コンソール出力
The area of the rectangle is 1500 square pixels.

上記のコードを構造体を使ってリファクタリングしていく。

薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

Refactoring with Structs: Adding More Meaning

src/main.rs
struct Rectangle {
    width: u32,
    height: u32,
}

fn main() {
    let rect1 = Rectangle {
        width: 30,
        height: 50,
    };

    println!(
        "The area of the rectangle is {} square pixels.",
        area(&rect1)
    );
}

fn area(rectangle: &Rectangle) -> u32 {
    rectangle.width * rectangle.height
}
  • 構造体を使うとグループと個々のフィールドに名前を与えることができる。
  • area() 関数の引数は参照なっており、借用を使うことでデータの所有権は main() 関数から移転しない。

もうこれでリファクタリング完了というレベルな気がする。

薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

Adding Useful Functionality with Derived Traits

https://doc.rust-lang.org/book/ch05-02-example-structs.html#adding-useful-functionality-with-derived-traits

src/main.rs
struct Rectangle {
    width: u32,
    height: u32,
}

fn main() {
    let rect1 = Rectangle {
        width: 30,
        height: 50,
    };

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

上記のコードをコンパイルすると下記のエラーメッセージが表示される。

コンソール出力
error[E0277]: `Rectangle` doesn't implement `std::fmt::Display`
  --> src/main.rs:33:29
   |
33 |     println!("rect1 is {}", rect1);
   |                             ^^^^^ `Rectangle` cannot be formatted with the default formatter
   |
   = help: the trait `std::fmt::Display` is not implemented for `Rectangle`
   = note: in format strings you may be able to use `{:?}` (or {:#?} for pretty-print) instead
   = note: this error originates in the macro `$crate::format_args_nl` which comes from the expansion of the macro `println` (in Nightly builds, run with -Z macro-backtrace for more info)

For more information about this error, try `rustc --explain E0277`.
error: could not compile `rectangles` (bin "rectangles") due to 1 previous error
  • println! マクロは {} を読み取ると Display と呼ばれるものを使ってエンドユーザー向けの出力を生成する。
  • 整数などの基本的な型は Display を実装しているが、構造体の場合は自分で実装する必要がある。
  • {} の代わりに {:?} を使うと下記のエラーメッセージが表示される。
コンソール出力
error[E0277]: `Rectangle` doesn't implement `Debug`
  --> src/main.rs:33:31
   |
33 |     println!("rect1 is {:?}", rect1);
   |                               ^^^^^ `Rectangle` cannot be formatted using `{:?}`
   |
   = help: the trait `Debug` is not implemented for `Rectangle`
   = note: add `#[derive(Debug)]` to `Rectangle` or manually `impl Debug for Rectangle`
   = note: this error originates in the macro `$crate::format_args_nl` which comes from the expansion of the macro `println` (in Nightly builds, run with -Z macro-backtrace for more info)
help: consider annotating `Rectangle` with `#[derive(Debug)]`
   |
22 + #[derive(Debug)]
23 | struct Rectangle {
   |

For more information about this error, try `rustc --explain E0277`.
error: could not compile `rectangles` (bin "rectangles") due to 1 previous error
  • {:?} は Debug と呼ばれるものを使って出力を生成することを伝えている。
  • Debug トレイトを使うと構造体の内容を開発者向けに出力できる。
  • ただしデバッグ情報の出力は明示的にオプトインする必要があり、そのためには #[derive(Debug)] を前の行に書く。
src/main.rs
#[derive(Debug)]
struct Rectangle {
    width: u32,
    height: u32,
}

fn main() {
    let rect1 = Rectangle {
        width: 30,
        height: 50,
    };

    println!("rect1 is {:?}", rect1);
}
コンソール出力
rect1 is Rectangle { width: 30, height: 50 }

次は下記から始めよう。

Nice! It’s not the prettiest output, but it shows the values of all the fields for this instance, which would definitely help during debugging.

薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

{:#?} を使うと改行して表示されるようになるらしい、やってみよう。

コンソール出力
rect1 is Rectangle {
    width: 30,
    height: 50,
}

さらに !dbg というマクロもあり、ファイル名や行番号も表示してくれるようだ。

ただし所有権が行ったり戻ったりするので参照を渡すように注意した方が良さそうだ。

また、出力先が標準エラー出力となる点についても println! マクロと異なる。

src/main.rs
#[derive(Debug)]
struct Rectangle {
    width: u32,
    height: u32,
}

fn main() {
    let scale = 2;
    let rect1 = Rectangle {
        width: dbg!(30 * scale),
        height: 50,
    };

    dbg!(&rect1);
}
コンソール出力
[src/main.rs:31:16] 30 * scale = 60
[src/main.rs:35:5] &rect1 = Rectangle {
    width: 60,
    height: 50,
}

Rust では Debug の他にも derive 可能なトレイトが用意されており、これらを使うことで自作の型に便利な振る舞いを追加できる。

またトレイト自体を自作することもできる。

また derive の他にも属性がある。

area 関数は Rectangle 専用なので、さらに Rectangle に強く関連付けられたら便利である。

このために次はメソッドについて学ぶ。

https://doc.rust-lang.org/book/ch05-03-method-syntax.html

薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

Method Syntax

  • メソッドは関数と似ているが構造体などの文脈内で定義されることや、最初のパラメーターが self である点が異なる。
src/main.rs
#[derive(Debug)]
struct Rectangle {
    width: u32,
    height: u32,
}

impl Rectangle {
    fn area(&self) -> u32 {
        self.width * self.height
    }
}

fn main() {
    let rect1 = Rectangle {
        width: 30,
        height: 50,
    };

    println!(
        "The area of the rectangle is {} square pixels.",
        rect1.area()
    );
}
コンソール出力
The area of the rectangle is 1500 square pixels.
  • メソッドは impl ブロック内で定義していく。
  • 第 1 引数は &self とする、型アノテーションは不要。
  • メソッドを呼び出す時にはインスタンスの後に .area() と書く、これはメソッド構文と呼ばれるらしい。
  • &selfself: &Self の省略形らしい、impl ブロック内では Self は構造体などを指すようだ、この場合は Rectangle になる。
  • メソッドは所有権を取得することもできるようだ、また可変参照もできる。
  • メソッドは構造体などに関連付けられているだけで基本的に関数と同じことは全てできそうだ。
  • 可変参照が必要な場合は第 1 引数を &mut self とすれば良い、所有権が必要な場合は self とすれば良い。
  • とはいえ多くのケースでは &self を使い、次いで &mut self を使うことになりそうだ。
  • self を使うケースとしては何かに変換し、その後は変換前の値を使わせないようにする、みたいなケースのようだ。
  • メソッドを使う主な目的はソースコードの組織化にあり、構造体などに関連するメソッドを集約することにある。
  • メソッド名はフィールドと同じ名前であっても構わない。
  • 多くのケースではメソッド名とフィールド名が同じ場合はそのまま値を返すだけの関数を実装し、これはゲッターと呼ばれる。
  • Rust はゲッターを自動的には実装しない。
  • ゲッターはフィールドを読み込み専用にしたい場合などに便利である。
  • フィールドやメソッドの公開/非公開については後のチャプターで学習する。
薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

Whare's the -> Operator?

  • C や C++ ではオブジェクトかオブジェクトへのポインターかによって .-> を使い分ける必要があった。
  • Rust ではメソッドの第 1 引数によって &&mut* が自動的に付加される。
薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

Methods with More Parameters

https://doc.rust-lang.org/book/ch05-03-method-syntax.html#methods-with-more-parameters

src/main.rs
#[derive(Debug)]
struct Rectangle {
    width: u32,
    height: u32,
}

impl Rectangle {
    fn area(&self) -> u32 {
        self.width * self.height
    }

    fn can_hold(&self, other: &Self) -> bool {
        return self.width > other.width && self.height > other.height;
    }
}

fn main() {
    let rect1 = Rectangle {
        width: 30,
        height: 50,
    };

    let rect2 = Rectangle {
        width: 10,
        height: 40,
    };

    let rect3 = Rectangle {
        width: 60,
        height: 45,
    };

    println!("Can rect1 hold rect2? {}", rect1.can_hold(&rect2));
    println!("Can rect1 hold rect3? {}", rect1.can_hold(&rect3));
}
コンソール出力
Can rect1 hold rect2? true
Can rect1 hold rect3? false
薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

Associated Functions

https://doc.rust-lang.org/book/ch05-03-method-syntax.html#associated-functions

  • 第 1 引数が &self などではないメソッドは関連関数と呼ばれる。
  • 例えば String::from などがそうである。
  • 関連関数はコンストラクタとしてよく使われ、new と名付けられることが多い。
  • 関連関数を呼び出すにはインスタンス名 + . ではなく構造体名 + :: を使う。
  • :: 演算子は関連関数だけではなくモジュール名前空間にも使われるようだ。