参照カウント式ポインタ、生ポインタ
ここまでで、所有権ありポインタと所有権なしポインタをカバーしてきた。所有権ありポインタは新規格C++のstd::unique_ptrに酷似している。また、所有権なしポインタは、C++でポインタや参照を使用していた人が行き着くデフォルトのポインタである。
Rustには他にも、より使う頻度の低いポインタが標準ライブラリや言語組み込みで存在する。これらは、C++で聞き馴染みのあるスマポによく似ているものだ。
この投稿を書き上げるのに、だいぶ時間がかかってしまったが、未だに気に入っていない。投稿自体とRustの機能自体に締まりがない部分が多々あるからだ。記事はブラッシュアップし、Rustの機能も言語の進化とともに改良されるといいね。
現在Rustを学習中の人は、この部分を飛ばしたいと思うかもしれないし、必要ない方がいい。他のポインタについて解説したから、ここに記してあるだけなのだ。
Rustにはいろんな種類のポインタがあるような気がするかもしれないが、C++でも標準ライブラリ内に多種多様なスマポがあることを鑑みれば、よく似ている。
ただRustでは、言語を学び始めてからそれらのポインタに遭遇する機会が多い。Rustのポインタはコンパイラのサポートがあるので、誤使用が少なくなるからね。
所有権ありポインタや所有権なしポインタほど、これらのポインタについて深入りするつもりはない。率直に言って、それほど重要ではないからね。
もしかしたら、後で補足するかもしれないけど。
参照カウント式ポインタ
参照カウント式ポインタは、標準ライブラリのstd::rcモジュール(モジュールについてはそろそろ触れる。例にuseという呪文が含まれているのはこのモジュールのためだ)に含まれている。T型のオブジェクトに対する参照カウント式ポインタの型は、Rc<T>となる。
参照カウント式ポインタは、スタティックメソッド(今のところはC++のものと同じと考えておいていいが、後々少しだけ違うとわかる)を使用して作成する。Rc::new(...)という参照先のオブジェクトを引数に取るメソッドだ。
このコンストラクタメソッドは、Rustの通常のmove/copy機構に倣っている(所有権ありポインタについて議論したのと同様に)。どちらにしても、Rc::newメソッドを呼ぶと、その値にはポインタ経由でしかアクセスできなくなるのだ。
他種のポインタと同じく、.演算子で必要な被参照は全て行うことができる。*演算子を使えば、手動で被参照することもできる。
参照カウント式ポインタを実引数にするには、cloneメソッドを呼ぶ必要がある。これはめんどくさいし、できれば直したいところだが、確定ではない(残念ながらね)。参照先の値に対して(無所有権)参照を取ることもできるから、それほどcloneメソッドを呼ぶ必要はないはずだ。
Rustの型システムにより、全参照がなくなるまで参照カウントされている変数が削除されないことが保証されている。また、参照を取ることには一々、参照カウントを増減させる必要がなく、パフォーマンスが上がるという別の利点もある(ただ、この違いはさほど重要ではないだろう。なぜなら、参照カウント式ポインタは、シングルスレッドに限定されているため、参照カウントが原始的な処理である必要がないからだ)。C++同様、参照カウントポインタ自体への参照を作成することもできる。
参照カウント式ポインタの例をあげよう。
use std::rc::Rc; fn bar(x: Rc<i32>) { } fn bad(x: &i32) { } fn foo() { let x = Rc::new(45); bar(x.clone()); // 参照カウントをインクリメント bad(&*x); // こちらはインクリメントしない println!("{}", 100 - *x); } // このスコープが終了すると、全参照カウントオブジェクトがなくなり、 // 参照カウントが0になるので、メモリー領域が解放される参照カウント式ポインタは、必ず不変になる。可変な参照カウント式のオブジェクトが必要なら、RefCell(かCell)オブジェクトをRcポインタに包む必要がある。
CellとRefCellオブジェクト
CellとRefCellオブジェクトは、可変性ルールを誤魔化せる構造体だ。
まず、Rustの構造体と構造体の可変性ルールをカバーしないと説明するのはなんとなく難しいので、このちょっとトリッキーなオブジェクトの説明は後回しにしよう。
とりあえず、可変な参照カウント式オブジェクトが必要なら、CellかRefCellオブジェクトをRcポインタに包まなければならないということを知っておけばよい。勘として、組み込み型のデータにはCellオブジェクトを、move機構のあるオブジェクトにはRefCellオブジェクトを使えばいいだろう。
したがって、可変かつ参照カウント式のint型変数は、Rc<Cell<int>>と表せる。
*T - 生ポインタ
最後に、Rustには2種類の生ポインタ(またはアンセーフポインタとも呼ばれる)がある。不変な生ポインタを表す*const Tと、可変な生ポインタを表す*mut Tだ。生ポインタも&か&mut演算子を使用して作成する(&演算子は、無所有権参照と生ポインタのどちらにも使われるので、&Tではなく*Tを得るには型を明示しなければいけない可能性がある)。
生ポインタは、Cのポインタに似ている。特に使用制限のないメモリ領域を表すだけのポインタだ(ポインタ演算はキャストをしなければできないが、どうしようもない時はキャストすればポインタ演算が行える)。
生ポインタは、Rustにおいて唯一nullになりうるポインタだ。生ポインタには自動被参照と自動参照機能は存在しない(なので、メソッド呼び出しは(*x).foo()と書かなければならない)。最重要な制限は、生ポインタがunsafeブロック外では被参照できず、使えないことにある。ノーマルなRustのコード内では、持ち回ることしかできない。
では、アンセーフコードとはなんだろうか?Rustには強力な型制限がかかっている。そのために稀にではあるが、すべきことができない時が出てくる。
Rustの目的は、システム記述用言語なので、可能なことはなんでもできなければならない。それは時として、コンパイラが安全を保証できないような事柄も行うことを意味する。
これを実現するために、Rustにはunsafeブロックという概念が存在し、unsafeキーワードによって使用する。
アンセーフコード内では、非安全な事柄が行える。生ポインタを被参照したり、配列の境界値チェックを行わずにアクセスしたり、Foreign Function Interface経由で他言語で書かれたコードを呼び出したり、変数をキャストすることだ。
アンセーフコードを書くにはノーマルなRustのコードを書くよりもずっと注意を要することは一目瞭然だろう。事実、アンセーフコードを書かなければいけない機会なんて皆無に等しい。ほとんどユーザコードよりもライブラリ内でちょこっと使うだけに終わる。
アンセーフコードでは、C++で安全性を確保するために普通にしていたことと同じことをしなければならない。さらに、普段ならコンパイラが保証してくれる不変性を手動で保たなければならない。アンセーフブロックはRustの不変性を保つ努力をさせてくれるのであって、不変性を破らせてくれるわけではない。そんなことをしたら、通常コードとアンセーフコード両方にバグを生み出してしまう。
さて、生ポインタの例だ。
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() { // 生ポインタは自動被参照できないので、このメソッドは*i32のもの。 // i32のものではないことに注目してほしい *p + 5 } else { -1 // 褒められたエラー処理法ではない } } }これにて、Rustのポインタを巡る旅は終了だ。次回は、少しポインタから離れて、Rustのデータ構造を見ていこう。まあ、また無所有権参照のところで戻ってくるんだがね。
原文: https://github.com/nrc/r4cppp/blob/master/rc%20raw.md
0 件のコメント:
コメントを投稿
なにか意見や感想、質問などがあれば、ご自由にお書きください。