🔥

[Rust]今日の学び2022-08-16 - ボックス化はどのような場合に行うのか(初歩的意訳と練習)

2022/08/17に公開

これは個人的な練習のノートです。
間違っていた場合はごめんなさい。
御手柔らかにお願いします。アドバイスを貰えると喜びます。

はじめに

今年中に、手足のようにRustを使える自信をつけたくて
Effective Rustを1章ずつ読んでいく目標を立てました。

ただ、それだけだと飽きてしまう恐れがある。
不安な基本部分を取り出して咀嚼したり練習をしています。
ここでは、その備忘録を公開しようと思いますしようと思います。

今日はボックス化について

  • ボックス化はどのような場合に行うのか
    • リファレンスのユースケース(英文)を意訳する
  • 練習: 整数型を使ったボックス化
    • 簡単なコードを書きながら理解する

をやっていきます。

参考情報

本題

ボックス化はどのような場合に行うのか

  • Rust by example 日本語版のBox, スタックとヒープを読んだ

    • ボックス化は、いわゆるスマートポインタ。ボックス化することで意図的にヒープ上に割り当てることができるということは理解。
  • どのようなケースでボックス化を意識して行うべき(あるいは行わないべき)かを知りたい

    • 基本的には、ヒープ上の領域確保は、スタックと比べると重たい処理。ボックス化(要するに、ヒープ確保)をすると、スタックの高速な領域確保の利点が失われることになる。
      • 意図的にヒープ上の領域確保を考えるべきケースを除いて、やらないほうがよさそうという認識している。
    • その上で、どのようなケースでボックス化を意識して行うべき(あるいは行わないべき)かを知りたい。
  • doc.rust-lang.org(英語版)の説明があった。愚直に翻訳することにした。

    • doc.rust-lang.org/book/ch15-01-box.html - Using Box<T> to Point to Data on the Heap
      • 3つのユースケースが紹介されていた。抜粋しつつ個人的に意訳した。
        • Case1. When you have a large amount of data and you want to transfer ownership but ensure the data won’t be copied when you do so
          • 個人的意訳: 大規模データを扱っており、かつ、所有権の移動が発生するようなソースコードで、データのコピーが発生しないことを確実に保証する目的でボックス化を行う
        • Case2. When you have a type whose size can’t be known at compile time and you want to use a value of that type in a context that requires an exact size
          • 個人的意訳: コンパイル時にサイズがわからない型を扱う場合(かつ、その型の値の正確なサイズが必要なケース)
        • Case3. When you want to own a value and you care only that it’s a type that implements a particular trait rather than being of a specific type
          • 個人的意訳: 自分で値の所有権を保持したい場合で、かつ、その値の具体的な型よりもその値の型がなんらかのtrait(性質)が実装したものであることの方を重視したい場合
      • 感想
        • Case1, Case2はなんとなくわかる。Case3はどの程度発生するケースなのか疑問が残ったままだ(実践が必要)
          • 自分で値の所有権を保持したい場合はわからないが、traitで抽象化して扱うことはそれなりにありそう(意外とよく使うのか?)
            • 参考にできるコードを探して読んでみようと思う。

練習: 整数型を使ったボックス化

  • このようなコードを実際に書くことはないということは理解の上で練習。
  • Box<T>した後の参照・借用、その後の値の書き換え。エラーが発生するポイントを、教科書的に確認していく作業。
  • ボックス化した後を参照してから中身を更新するコードを書くと、C言語の2重ポインタに似た感じになるな。
fn awesome_borrow_with_box(int_on_box_borrow: &Box<i32>) -> bool {
    
    // 以下のコードはコメントアウトするとエラーになる 理由: expected struct `Box`, found integer
    // *int_on_box_borrow = 101;

    // 以下のコードはコメントアウトするとエラーになる 理由: cannot assign to `**int_on_box_borrow`, which is behind a `&` reference
    // **int_on_box_borrow = 101;

    println!("int with boxed: borrow = {}", int_on_box_borrow);
    true
}

fn main() {

    // 通常のinteger(スタック上に格納される)
    let int_on_stack: i32 = 100;

    // 変数int_on_stack_copyにコピー ※値の変更確認のためmut修飾子を付与)
    let mut int_on_stack_copy = int_on_stack;
    println!("int on stack: copy is ok. original = {}, copy = {}.", int_on_stack, int_on_stack_copy);
    println!("int on stack: change value of int_on_stack_copy(from 100 to 50).");
    // 
    int_on_stack_copy = 50;
    println!("int on stack: copy is stored as different on stack. original = {}, copy = {}.", int_on_stack, int_on_stack_copy);

    let mut int_on_box: Box<i32> = std::boxed::Box::new(100);

    // box化した値の参照・借用の動作を確認
    let int_on_box_borrow_local = &int_on_box;

    // 以下のコードはコメントアウトするとエラーになる 理由: cannot assign to `*int_on_box` because it is borrowed (L29の参照で値が借用されている→元の変数int_on_boxに新しい値を代入できない)
    // *int_on_box = 102;

    // 以下のコードはコメントアウトするとエラーになる 理由: expected struct `Box`, found integer(int_on_box_borrow_localは、i32をbox化した値の参照だから、*int_on_box_borrow_localと書くとこれは struct Box型が返される)
    // *int_on_box_borrow_local = 102;

    // 以下のコードはコメントアウトするとエラーになる 理由: cannot assign to `**int_on_box_borrow_local`, which is behind a `&` reference
    // **int_on_box_borrow_local = 102;
    
    println!("int with boxed: int_on_box_borrow_local={}, original = {}", int_on_box_borrow_local, int_on_box);
    
    // 関数の呼び出しを通じて値の借用を試す
    awesome_borrow_with_box(&int_on_box);
    // L43の関数実行が終了し借用値の→元の変数int_on_boxに新しい値を代入できない)
    *int_on_box = 103;
    println!("int with boxed: original = {}", int_on_box);

    let int_on_box_borrow_mut = &mut int_on_box;
    **int_on_box_borrow_mut = 104;

    // 以下のコードはコメントアウトするとエラーになる 理由: error[E0502]: cannot borrow `int_on_box` as immutable because it is also borrowed as mutable
    // println!("int with boxed: int_on_box_borrow_mut={}, original = {}", int_on_box_borrow_mut, int_on_box);

    // 以下のコードはコメントアウトするとエラーになる 理由: error[E0499]: cannot borrow `int_on_box` as mutable more than once at a time
    // println!("int with boxed: int_on_box_borrow_mut={}, original = {}", int_on_box_borrow_mut, &mut int_on_box);

    println!("int with boxed: int_on_box={}", int_on_box);
}
  • 実行結果
int on stack: copy is ok. original = 100, copy = 100.
int on stack: change value of int_on_stack_copy(from 100 to 50).
int on stack: copy is stored as different on stack. original = 100, copy = 50.
int with boxed: int_on_box_borrow_local=100, original = 100
int with boxed: borrow = 100
int with boxed: original = 103
int with boxed: int_on_box=104

おわりに

所有権が難しい。
借りられてる変数を一瞬忘れてprintln!しようとすると不意にエラーになって、借用、むずかしい。。という気持ちになった。

Rustの満足行く開発ができるようになるまで
ちょっとずつやっていきます。

GitHubで編集を提案

Discussion