Rustの所有権システム

完全に理解した

@kfurumiya

Twitter

GitHub

本名

古川 亘

ハンドルネーム

古都こと

しゅみ

ゲーム

プログラミング

好きな言語

TypeScript

Rust

React / React Native

Next.js

AWS / GCP

ウェブパフォーマンス

1991年生まれ

近況

原神(PC版)に

ハマっている

今日のお話

Rustの所有権システム

完全に理解した

本日の献立

  • Rust is 何
  • 値の所有権
  • 借用
  • ライフタイム

Rust is 何

Rustとは

use std::process::exit;
use std::env;

fn main() {
    let cli_args: Vec<String> = env::args().collect();

    let name = match cli_args.get(1) {
        Some(a) => a,
        None => exit(0)
    };
    
    println!("Hello, {}!", name);
}

Rustとは

  • 2010年頃に登場
  • コンパイル型のモダンなシステムプログラミング言語
  • Mozilla従業員の個人プロジェクトから始まった
  • チェックが非常に厳格でとても安全にコードを書ける
  • ゼロランタイムなため極めて高速に動作する
  • 採用例:Firefox、Discord、Dropbox、など

Rustの特徴

全ての値に対する

所有権」が存在する

Rustの特徴

😗

値の所有権

所有権

(ownership)

値の所有権(ownership)

fn main() {
    let val = String::from("Hello");
    let val2 = val;
    
    println!("{} {}", val, val2);
}
error[E0382]: borrow of moved value: `val`
 --> src/main.rs:5:23
  |
2 |     let val = String::from("Hello");
  |         --- move occurs because `val` has type `String`, which does not implement the `Copy` trait
3 |     let val2 = val;
  |                --- value moved here
4 |     
5 |     println!("{} {}", val, val2);
  |                       ^^^ value borrowed here after move

↓コンパイルエラー

値の所有権(ownership)

fn main() {
    // ここで値がvalに束縛される
    let val = String::from("Hello");
    
    // val2に所有権がムーブし、valは所有権を失う
    let val2 = val;
    
    // valは既に所有権を失っているので
    // ここでアクセスするとエラー
    println!("{} {}", val, val2);
}

Why???

What's?

所有権とムーブセマンティクス

  • 値の使用者をコンパイル時に完全に決定したい
    • メモリが安全に解放されることが保証できる
    • メモリ安全でないコードは全てエラー
    • これを実現するための所有権システム
  • RustにGCは存在しないがメモリの明示的解放は不要
    • allocした値を自動的にfreeしてくれる
    • 値の有効範囲がコンパイル時に確定できる

所有権とムーブセマンティクス

  • ムーブセマンティクス
    • 値をコピーするのではなくムーブする挙動
    • Rustではこれがデフォルト
    • C++ではunique_ptrとmoveで実現可能
  • ⇆ コピーセマンティクス
    • 一般的な言語はこっち
    • Rustでも一部のプリミティブはコピーする
    • Rustではtrait(mix-inみたいなの)で実現可能

所有権とムーブセマンティクス

fn main() {
    let val = String::from("Hello");
    
    {
        let _list = vec![val]; // valがVectorにmoveする
    } // ここで_listもvalもfreeされてしまう
    
    println!("{}", val); // もう戻ってこない
}

所有者のスコープを抜けると

freeされてしまう

借用

(borrowing)

ムーブの問題

fn string_len(text: String) -> usize {
    // returnしているのがusizeなので
    // 受け取ったString自体は元の場所にムーブしない……
    text.len()
}

fn main() {
    let val = String::from("Hello");
    
    // ムーブしたStringが戻ってこない
    let len = string_len(val);
    
    // エラー!元のStringはfreeされてしまった……
    println!("{} {}", val, len);
}

借用(borrowing)

  • 所有権をムーブせず借りることができる
    • 借用(borrowing)
  • 借用は参照(ポインタ)の形で行われる
    • ポインタで渡せば自動的に借用になる
  • ムーブしないので返す必要もない
    • ただし所有はしていないので制限は大きい

借用してみる

fn string_len(text: &String) -> usize {
    // Stringを借用しているだけなので
    // 返す必要はない
    text.len()
}

fn main() {
    let val = String::from("Hello");
    
    // Stringは貸してるだけ
    // 所有権はvalの元にある
    let len = string_len(&val);
    
    // 動く!!!!
    println!("{} {}", val, len);
}

可変参照の借用

fn string_len(text: &String) -> usize {
    // エラー!借用しているだけなので
    // mutateできない…
    text.push_str(" World!");
    text.len()
}

fn string_len_2(text: &mut String) -> usize {
    // &mut で明示的に可変な借用をする
    text.push_str(" World!");
    text.len()
}

fn main() {
    // 可変な値として束縛する
    let mut val = String::from("Hello");
    
    let len = string_len(&val); // エラー!
    let len2 = string_len_2(&mut val); // 可変参照を生成する
    
    println!("{} {}", val, len2);
}

ぶら下がりポインタ

fn dangle() -> &String {
    let s = String::from("Hello");
    return &s;
} // sはここでfreeされる
// よって&sは危険!エラー!

fn main() {
    let val = dangle();
}

ライフタイム

(lifetime)

借用のライフタイム

fn main() {
    let val = String::from("Hello");
    
    // xのライフタイム 'a
    let x = &val;
    
    {
        let y = &val; // yのライフタイム 'b
    } // 'b ここまで
}// 'a ここまで

'a 'b 'c…と自動的に振られていく!

スコープに名前付けてるイメージ

ライフタイムが確定しない場合

// エラー
// xのライフタイム'aとyのライフタイム'bが自動的に振られるが
// どちらが返るかわからない → 戻り値のライフタイムも確定しない
fn longer(x: &String, y: &String) -> &String {
    if x.len() > y.len() {
        x
    } else {
        y
    }
}

fn main() { 
    let a = String::from("aa");
    let b = String::from("aaa");
    
    let result = longer(&a, &b);
    println!("{}", result)
}

ライフタイムを明示してあげる

// ライフタイムを明示する
fn longer<'a>(x: &'a String, y: &'a String) -> &'a String {
    if x.len() > y.len() {
        x
    } else {
        y
    }
}

fn main() { 
    let a = String::from("aa");
    let b = String::from("aaa");
    
    let result = longer(&a, &b);
    println!("{}", result)
}

まとめ!!

💯 Rustの所有権システム完全に理解した 💯

  • Rustでは値の所有権がある
    • 使用範囲をコンパイル時検出して自動でfreeする
  • 所有権をムーブしたくないときはポインタで借用する
    • 関数の引数とか
  • 借用にはライフタイムがある
    • コンパイラが自動検出できない時は明示する

Rustの所有権システム完全に理解した

By Koto Furumiya

Rustの所有権システム完全に理解した

Rustの所有権システムについて、軽くお話しします。 「【オンライン】エンジニア達の「〇〇完全に理解した」Talk #11 ( https://easy2.connpass.com/event/194418/ )」登壇用資料。

  • 2,525