16

ポインターを備えた言語では、コンパイラーがコンパイル時にすべてのポインターが正しく使用されているかどうか、および/または有効である (生きているオブジェクトを参照する) かどうかをさまざまな理由で完全に判断することはできないことをどこかで読んだことがあります。本質的に停止問題の解決を構成します。この場合、この関連する質問で述べられていることと同様に、コンパイル時のプログラムの実行時の動作を推測できるため、直感的には驚くことではありません。

ただし、私が知る限り、Rust 言語では、ポインターのチェックをコンパイル時に完全に行う必要があります (ポインターに関連する未定義の動作はなく、少なくとも「安全な」ポインターはなく、「無効なポインター」または「null ポインター」のランタイムはありません)。いずれかの例外)。

Rust コンパイラが停止問題を解決しないと仮定すると、誤りはどこにあるのでしょうか?

  • ポインターのチェックがコンパイル時に完全に行われるわけではなく、Rust のスマート ポインターは、たとえば C の生のポインターと比較して、実行時のオーバーヘッドをまだ導入しているのですか?
  • それとも、Rust コンパイラが完全に正しい決定を下すことができず、おそらくライフタイム アノテーションの 1 つ (<'lifetime_ident>構文を持つもの) を使用して Just Trust The Programmer™ を使用する必要がある可能性はありますか? この場合、これはポインタ/メモリの安全性が 100% ではなく、プログラマが正しいコードを書くことに依拠しているということですか?
  • もう 1 つの可能性は、Rust ポインターが非「ユニバーサル」または何らかの意味で制限されているため、コンパイラーはコンパイル時にそれらのプロパティを完全に推測できますが、C の生のポインターや C++ のスマートポインターほど有用ではありません。
  • あるいは、それはまったく別のもので、1 つまたは複数の を誤解しているのかもしれません
    { "pointer", "safety", "guaranteed", "compile-time" }
4

3 に答える 3

9

免責事項:私は少し急いでいるので、これは少し蛇行しています。気軽に掃除してください。

言語設計者が嫌う卑劣なトリックの 1 つは、基本的に次のとおりです。Rust は、有効期間 (グローバル変数やその他のプログラム全体の有効期間に使用されるもの) とスタック (つまり、ローカル) 変数の有効期間についてのみ推論できます。ヒープ割り当ての有効期間。'static

これはいくつかのことを意味します。まず第一に、ヒープ割り当てを扱うすべてのライブラリ タイプ (つまり Box<T>Rc<T>Arc<T>) はすべて、それらが指すものを所有しています。その結果、存在するために実際にはライフタイムは必要ありません。

ライフタイムが必要なのは、スマート ポインターの内容にアクセスするときです。例えば:

let mut x: Box<i32> = box 0;
*x = 42;

その 2 行目の舞台裏で起こっていることは次のとおりです。

{
    let box_ref: &mut Box<i32> = &mut x;
    let heap_ref: &mut i32 = box_ref.deref_mut();
    *heap_ref = 42;
}

言い換えれば、Box魔法ではないので、コンパイラーに、それを通常の実行可能な借用ポインターに変換する方法を指示する必要があります。これがDerefandDerefMutトレイトの目的です。ここで疑問が生じます: の寿命は正確には何heap_refですか?

これに対する答えは、DerefMut(急いでいるので記憶から)の定義にあります:

trait DerefMut {
    type Target;
    fn deref_mut<'a>(&'a mut self) -> &'a mut Target;
}

前に言ったように、Rustは「ヒープ ライフタイム」について語ることは絶対にできません。代わりに、ヒープ割り当てi32の有効期間を、手元にある唯一の他の有効期間、つまり の有効期間に結び付ける必要がありBoxます。

これが意味することは、「複雑な」ものには表現可能な寿命がないため、管理するものを所有する必要があるということです。複雑なスマート ポインター/ハンドルを単純な借用ポインターに変換するとき、その時点でライフタイムを導入する必要があり、通常はハンドル自体のライフタイムを使用するだけです。

実際には、明確にする必要があります:「ハンドルの寿命」とは、「ハンドルが現在格納されている変数の寿命」を意味します: 寿命は実際にはstorageであり、valuesではありません。これは、Rust の初心者が次のようなことができない理由がわからなくてつまずく典型的な理由です。

fn thingy<'a>() -> (Box<i32>, &'a i32) {
    let x = box 1701;
    (x, &x)
}

「しかし...ボックスが存続し続けることは知っています。なぜコンパイラはそうでないと言うのですか?!」 Rust はヒープの寿命について推論することができず、 の寿命を変数に結びつけることに頼らなければならないため、それがたまたま指すヒープ割り当てではありません。&x x

于 2015-04-14T13:50:05.277 に答える
2

Rust 参照の安全性のほとんどは、厳格な規則によって保証されています。

  • const 参照 ( &) を所有している場合、この参照を複製して渡すことはできますが、そこから変更可能な参照を作成することはできません&mut
  • オブジェクトへの変更可能な ( &mut) 参照が存在する場合、このオブジェクトへの他の参照は存在できません。
  • 参照は、それが参照するオブジェクトよりも長く存続することはできません。また、参照を操作するすべての関数は、ライフタイム アノテーション ( など) を使用して、入力と出力からの参照がどのようにリンクされているかを宣言する必要'aがあります。

したがって、表現力に関しては、単純な生のポインターを使用する場合よりも効果的に制限されます (たとえば、安全な参照のみを使用してグラフ構造を構築することはできません) が、これらのルールはコンパイル時に効果的に完全にチェックできます。

それでも、生のポインターを使用することは可能ですが、それらを処理するコードをunsafe { /* ... */ }ブロックで囲み、コンパイラーに「私を信じてください。ここで何をしているのか知っています」と伝える必要があります。これは、 などの一部の特別なスマート ポインターが内部的に行うRefCellことであり、これらのルールをコンパイル時ではなく実行時にチェックして、表現力を高めることができます。

于 2015-04-14T13:48:05.053 に答える