コンテンツにスキップ

データ・型

はじめに

本サイトにつきまして、【IT技術の知見】はじめに - 俺の技術ノート をご認識のほど宜しくお願いいたします。


01. enum

Rust の enum は、バリアントごとに異なるデータを持たせられる。

各バリアントは、Command::Login { ... } のように 列挙型名::バリアント名 で指定する。

// 列挙型を定義する
enum Command {
    // Command::Login バリアントは url と no_browser を持つ
    Login { url: Option<String>, no_browser: bool },
    // Command::Token バリアントは url だけを持つ
    Token { url: Option<String> },
}


02. 所有権と借用

借用 (&)

& を付けて渡すと値を借用 (参照) するだけなので、呼び出し元は引き続きその変数を使える。

C 言語のようにメモリを手動で解放する言語は高速だが、「解放済みのメモリの参照」や「二重解放」などのバグが起こりやすい。

これらのバグは、プログラムのクラッシュやセキュリティ脆弱性の原因になる。

Java や Go のようにガベージコレクションを使う言語ではこれらのバグを防げるが、実行時にガベージコレクションの処理コストがかかる。

Rust では、所有権と借用という仕組みによって、「解放済みのメモリの参照」や「二重解放」などをコンパイル時に検出する。

// 借用する場合

// 引数 creds は Credentials の値の所有権を持たず、借用するだけである
fn resolve_origin(url: Option<&str>, creds: &Credentials) -> Result<String> {
    // ...
}

// Credentials の値を作り、creds 変数がその値の所有権を持つ
let creds = Credentials::load(&path)?;

// & を付けて渡すため、呼び出し後も creds 変数が Credentials の値の所有権を持ち、creds を使える
resolve_origin(None, &creds);
creds.save(&path)?;

以下の例では、Credentials の値を & を付けずに渡すと所有権が移動 (move) し、呼び出し元ではその変数を使えなくなる。

// 借用しない場合

// 引数の型に & を付けないため、引数 creds が Credentials の値の所有権を持つ
fn resolve_origin(url: Option<&str>, creds: Credentials) -> Result<String> {
    // ...
}

// Credentials の値を作り、creds 変数がその値の所有権を持つ
let creds = Credentials::load(&path)?;

// & を付けずに渡すため、resolve_origin 関数の引数 creds が Credentials の値の所有権を持つ
resolve_origin(None, creds);

// creds 変数は Credentials の値の所有権を持たないため、コンパイルエラーになる
// コンパイル時に、「解放済みのメモリの参照」や「二重解放」などを防ぐ
creds.save(&path)?;


型によるコピーと所有権の移動

整数などのプリミティブ型では、内部的に Copy の実装に基づいて値が暗黙的にコピーされるため、借用せずに代入や関数へ値を渡しても、元の変数を引き続き使える。

借用が暗黙的に行われるわけではなく、コピー元とコピー先がそれぞれ独立した値を所有する。

関数へ渡した後も元の変数を使うための方法を、型ごとに以下に示す。

型の例 関数へ渡した後も元の変数を使うには
整数型の i32 や u64、浮動小数点型の f32 や f64、bool、char そのまま渡せる
配列の [i32; 3]、タプルの (i32, bool)、Option<i32> そのまま渡せる
共有参照の &T や &str そのまま渡せる
String、Vec<T>、Box<T> 借用するか、複製できる場合は明示的に複製して渡す
自作の構造体や列挙型 借用して渡す。そのまま渡せるかは型の定義による


String と &str

String は自分で所有する伸縮可能な文字列で、&str は他の場所にある文字列を借りて見ているだけの参照である。

引数は &str で受け取り、新しく作った文字列は String で返すことが多い。

// 引数は借用した文字列の &str で受け取り、新しく作った文字列の String を返す
fn resolve_origin(url: Option<&str>, creds: &Credentials) -> Result<String> {
    // ...
}


03. ライフタイム

'a

'a はライフタイムであり、「戻り値の参照は、creds が生きている間だけ有効である」とコンパイラに伝える。

コンパイラはライフタイムを使い、解放済みのデータへの参照 (ダングリング参照) をコンパイル時に防ぐ。

// 戻り値の &'a str は、&'a Credentials 型の引数 creds が生きている間だけ有効である
fn saved_token<'a>(url: Option<&str>, creds: &'a Credentials) -> Result<(String, &'a str)> {
    // ...
}


'static

&'static str は、プログラムの終了までずっと有効な文字列を表す。

ソースコードに直接書いた文字列リテラルは 'static になる。

// body には、文字列リテラルなど、プログラムの終了まで有効な文字列だけを渡せる
fn serve_once(status: u16, body: &'static str) {
    // ...
}


04. raw 文字列

r#"..."# は raw 文字列であり、中の " をエスケープせずに書ける。

JSON をテストデータとして書くときに便利である。

// " をエスケープせずに JSON を書ける
let body = r#"[{"name":"org-a","roleName":"Administrator","current":true}]"#;