Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

参照カウントポインターと raw ポインター

TODO カスタムポインターと Deref トレイトの議論を追加する(たぶん後で、ここではない)

ここまで、一意ポインターと借用ポインターについて扱ってきました。一意ポインターは C++ の新しい std::unique_ptr と非常によく似ており、借用参照は、C++ でポインターや参照を使う場面で通常まず手に取る「デフォルト」のポインターです。Rust には、ライブラリ内または言語組み込みとして、もう少し多くの、よりまれなポインターがあります。これらはおおむね、C++ でおなじみかもしれないさまざまな種類のスマートポインターに似ています。

この投稿を書くのには時間がかかりましたし、今でも気に入っていません。ここには、私の説明にも Rust 自体にも、多くの未整理な部分があります。後の投稿でいくつかは改善され、言語の発展に伴っていくつかは改善されることを願っています。Rust を学んでいるなら、今のところこの内容は飛ばしてもよいかもしれません。おそらく必要にはならないでしょう。これは他のポインター型に関する投稿の後で、完全性のためにここにあるだけです。

Rust には多くのポインター型があるように感じるかもしれませんが、ライブラリで利用できるさまざまなスマートポインターについて考えれば、C++ とかなり似ています。ただし Rust では、言語を学び始めたときにそれらに出会う可能性が高くなります。また、Rust のポインターはコンパイラのサポートを受けているため、それらを使うときにエラーを起こす可能性もはるかに低くなります。

これらについては、一意参照や借用参照ほど詳しくは扱いません。率直に言って、それほど重要ではないからです。後でより詳しく戻ってくるかもしれません。

Rc

参照カウントポインターは Rust 標準ライブラリの一部として提供されます。これらは std::rc モジュールにあります(モジュールについては近いうちに扱います。例に出てくる use という呪文の理由はモジュールにあります)。型 T のオブジェクトへの参照カウントポインターの型は Rc<T> です。参照カウントポインターは、静的メソッド(今のところは C++ のもののように考えてよいですが、後で少し違うことが分かります)である Rc::new(...) を使って作成します。これはポインターの指し先となる値を受け取ります。このコンストラクタメソッドは Rust の通常の move/copy セマンティクス(一意ポインターについて議論したものと同様)に従います。いずれの場合も、Rc::new を呼び出した後は、その値にはポインターを介してしかアクセスできなくなります。

他のポインター型と同様に、. 演算子は必要な参照外しをすべて行います。手動で参照外しするには * を使えます。

参照カウントポインターを渡すには、clone メソッドを使う必要があります。これは少し残念で、いずれ修正できるとよいのですが、確実ではありません(残念ながら)。指し先の値への(借用)参照を取ることができるので、あまり頻繁に clone しなくて済むことを期待できます。Rust の型システムは、参照が期限切れになる前に参照カウントされた変数が削除されないことを保証します。参照を取ることには、参照カウントをインクリメントまたはデクリメントする必要がないという追加の利点があり、そのため性能が向上します(ただし、Rc オブジェクトは単一スレッドに制限されているため、参照カウント操作はアトミックである必要がなく、その差はおそらくわずかです)。C++ と同様に、Rc ポインターへの参照を取ることもできます。

Rc の例:

#![allow(unused)]
fn main() {
use std::rc::Rc;

fn bar(x: Rc<i32>) { }
fn baz(x: &i32) { }

fn foo() {
    let x = Rc::new(45);
    bar(x.clone());   // 参照カウントをインクリメントする
    baz(&*x);         // インクリメントしない
    println!("{}", 100 - *x);
}  // このスコープが閉じると、すべての Rc ポインターがなくなるため、ref-count == 0
   // となり、メモリが削除される。
}

参照カウントポインターは常に不変です。可変な参照カウントオブジェクトが必要な場合は、Rc でラップした RefCell(または Cell)を使う必要があります。

Cell と RefCell

Cell と RefCell は、可変性のルールを「ごまかす」ことを可能にする構造体です。これは、Rust のデータ構造と、それらが可変性とどのように連携するかを先に扱わないと説明が少し難しいため、これらの少し扱いにくいオブジェクトについては後で戻ってきます。今のところ、可変で参照カウントされたオブジェクトが必要な場合は、Rc でラップした Cell または RefCell が必要であることを知っておいてください。第一近似としては、プリミティブデータには Cell、move セマンティクスを持つオブジェクトには RefCell が必要になるでしょう。したがって、可変で参照カウントされた int には Rc<Cell<int>> を使うことになります。

*T - raw ポインター

最後に、Rust には 2 種類の raw ポインター(別名 unsafe ポインター)があります。不変 raw ポインター用の *const T と、可変 raw ポインター用の *mut T です。これらは & または &mut を使って作成されます(& 演算子は借用参照と raw ポインターのどちらも作成できるため、&T ではなく *T を得るには型を指定する必要があるかもしれません)。raw ポインターは C のポインターに似ており、どのように使われるかについて制限のない、単なるメモリへのポインターです(キャストなしにポインター演算はできませんが、必要であればその方法で行えます)。raw ポインターは、Rust で null になり得る唯一のポインター型です。raw ポインターには自動参照外しはありません(そのためメソッドを呼び出すには (*x).foo() と書く必要があります)し、自動参照もありません。最も重要な制限は、unsafe ブロックの外では参照外しできない(したがって使用できない)ことです。通常の Rust コードでは、それらを受け渡しすることしかできません。

では、unsafe コードとは何でしょうか。Rust には強力な安全性の保証があり、それによって(まれに)必要なことができなくなる場合があります。Rust はシステム言語であることを目指しているため、可能なことは何でもできなければならず、ときにはそれが、コンパイラが安全だと検証できないことを行うことを意味します。それを実現するために、Rust には unsafe キーワードで示される unsafe ブロックという概念があります。unsafe コードでは、raw ポインターを参照外しする、境界チェックなしで配列にインデックスアクセスする、FFI 経由で別の言語で書かれたコードを呼び出す、変数をキャストする、といった unsafe なことができます。明らかに、unsafe コードを書くときは通常の Rust コードを書くときよりもはるかに注意する必要があります。実際、unsafe コードを書くべき場面はごくまれです。多くの場合、クライアントコードではなくライブラリ内の非常に小さな部分で使われます。unsafe コードでは、安全性を確保するために通常 C++ で行うすべてのことを行わなければなりません。さらに、コンパイラが通常強制する不変条件を維持していることを手動で保証しなければなりません。unsafe ブロックは、Rust の不変条件を手動で強制できるようにするものであり、それらの不変条件を破ることを許可するものではありません。もし破れば、安全なコードと unsafe なコードの両方にバグを持ち込む可能性があります。

raw ポインターを使う例:

#![allow(unused)]
fn main() {
fn foo() {
    let mut x = 5;
    let x_p: *mut i32 = &mut x;
    println!("x+5={}", add_5(x_p));
}

fn add_5(p: *mut i32) -> i32 {
    unsafe {
        if !p.is_null() { // *ポインターは自動 deref しないことに注意。そのためこれは
                          // i32 ではなく *i32 に実装されたメソッドである。
            *p + 5
        } else {
            -1            // 推奨されるエラーハンドリング戦略ではない。
        }
    }
}
}

これで Rust のポインターのツアーは終わりです。次回はポインターから少し離れて、Rust のデータ構造を見ていきます。ただし、借用参照については後の投稿で再び戻ってきます。