web-dev-qa-db-ja.com

Box、ref、&、*の理解と関係

Rustでポインターがどのように機能するかについて少し混乱しています。 refBox、_&_、_*_があり、それらがどのように連携するかわかりません。

現在私がそれを理解している方法は次のとおりです。

  1. Boxは実際にはポインタではありません。これは、ヒープにデータを割り当て、サイズのない型(特に特性)を関数の引数に渡す方法です。
  2. refは、パターンマッチングで使用され、代わりに、一致するものを借用します。例えば、

    _let thing: Option<i32> = Some(4);
    match thing {
        None => println!("none!"),
        Some(ref x) => println!("{}", x), // x is a borrowed thing
    }
    println!("{}", x + 1); // wouldn't work without the ref since the block would have taken ownership of the data
    _
  3. _&_は、借用(借用ポインター)を作成するために使用されます。関数fn foo(&self)がある場合、関数の終了後に期限切れになる自分自身への参照を取得し、呼び出し元のデータはそのままにします。 bar(&mydata)を実行することで、所有権を保持したいデータを渡すこともできます。

  4. _*_は、生のポインターを作成するために使用されます。たとえば、_let y: i32 = 4; let x = &y as *const i32_。私はC/C++のポインタを理解していますが、これがRustの型システムでどのように機能し、どのようにそれらを安全に使用できるかはわかりません。また、このタイプのポインターのユースケースが何であるかわかりません。さらに、_*_シンボルを使用して、物事を参照解除することができます(どのようなもの、そしてなぜ?)。

誰かが4番目のタイプのポインターを私に説明し、他のタイプの私の理解が正しいことを確認できますか?また、私が言及していない一般的なユースケースを指摘してくれる人もいます。

46
zrneely

まず、リストしたアイテムはすべて、ポインターに関連していても、実際にはすべて異なるものです。 Boxはライブラリ定義のスマートポインタータイプです。 refはパターンマッチングの構文です。 &は参照演算子であり、参照型ではシギルとして機能します。 *は間接参照演算子であり、生のポインタ型ではシギルとして機能します。詳細については、以下を参照してください。

Rustには4つの基本的なポインタタイプがあり、これらは参照と生ポインタの2つのグループに分けることができます。

&T        - immutable (shared) reference
&mut T    - mutable (exclusive) reference

*const T  - immutable raw pointer
*mut T    - mutable raw pointer

最後の2つの違いは非常にわずかです。どちらかを制限なくキャストできるため、const/mutの区別はほとんどリントとして機能します。生のポインタは、何にでも自由に作成できます。また、整数からの薄い空気から作成することもできます。

当然、これは参照の場合はそうではありません-参照タイプとその相互作用は、Rustの重要な機能の1つである借用を定義します。参照には、作成方法と作成時期、使用方法、相互作用方法に多くの制限があります。代わりに、unsafeブロックなしで使用できます。ただし、借用とは正確にどのように機能するかは、この回答の範囲外です。

&演算子を使用して、参照と生のポインターの両方を作成できます。

let x: u32 = 12;

let ref1: &u32 = &x;
let raw1: *const u32 = &x;

let ref2: &mut u32 = &mut x;
let raw2: *mut u32 = &mut x;

参照と生のポインターの両方は*演算子を使用して逆参照できますが、生のポインターの場合はunsafeブロックが必要です。

*ref1; *ref2;

unsafe { *raw1; *raw2; }

別の演算子である「ドット」演算子(つまり、.)は、その左の引数を自動的に参照または逆参照するため、逆参照演算子はしばしば省略されます。したがって、たとえば、これらの定義がある場合:

struct X { n: u32 };

impl X {
    fn method(&self) -> u32 { self.n }
}

method()は参照によってselfを使用しますが、self.nは自動的にそれを逆参照するため、(*self).nと入力する必要はありません。 method()が呼び出された場合も同様のことが起こります:

let x = X { n: 12 };
let n = x.method();

ここで、コンパイラはx.method()xを自動的に参照するため、(&x).method()を記述する必要はありません。

最後から2番目のコードは、特別な&self構文も示しています。これは、単にself: &Self、またはより具体的には、この例ではself: &Xを意味します。 &mut self*const self*mut selfも機能します。

したがって、参照はRustの主要なポインタの種類であり、ほぼ常に使用する必要があります。参照の制限がないRawポインタは、高レベルを実装する低レベルコードで使用する必要があります抽象化(コレクション、スマートポインターなど)およびFFI(Cライブラリとの対話)。

Rustには 動的サイズ(またはサイズなし)タイプ もあります。これらの型には、静的に既知の明確なサイズがないため、ポインター/参照を介してのみ使用できます。ただし、ポインターだけでは十分ではありません。たとえば、スライスの長さや特性オブジェクトの仮想メソッドテーブルへのポインターなど、追加情報が必要です。この情報は、サイズなしの型へのポインタに「埋め込まれ」、これらのポインタを「太らせ」ます。

ファットポインターは、基本的に、データへの実際のポインターといくつかの追加情報(スライスの長さ、特性オブジェクトのvtableへのポインター)を含む構造です。ここで重要なのは、Rustはユーザーに対してポインターの内容に関するこれらの詳細を完全に透過的に処理することです。&[u32]または*mut SomeTrait値を渡すと、対応する内部情報が自動的に渡されます。

Box<T>は、Rust標準ライブラリのスマートポインターの1つです。対応する型の値を格納するのに十分なメモリをヒープに割り当て、ハンドルとして機能します。そのメモリへのポインタBox<T>は、それが指すデータを所有し、ドロップされると、ヒープ上の対応するメモリの割り当てが解除されます。

ボックスを考える非常に便利な方法は、ボックスを固定値で通常の値と見なすことです。つまり、Box<T>Tと同等です。ただし、マシンのポインターサイズに対応するバイト数を常に使用する点が異なります。 (所有)ボックスは値のセマンティクスを提供すると言います。内部的には、他のほとんどの高レベルの抽象化と同様に、生のポインターを使用して実装されます。

Boxes(実際、これはRcなど、他のほとんどすべてのスマートポインターに当てはまります)を借りることもできます。&TからBox<T>を取得できます。これは、.演算子を使用して自動的に発生する場合がありますが、参照を解除して再度参照することで明示的に行うこともできます。

let x: Box<u32> = Box::new(12);
let y: &u32 = &*x;

この点で、Boxesは組み込みのポインターに似ています-参照解除演算子を使用してその内容に到達できます。これは、Rustの逆参照演算子がオーバーロード可能であり、すべてではないにしてもほとんどのスマートポインタータイプでオーバーロードされているため可能です。これにより、これらのポインターの内容を簡単に借用できます。

最後に、refは、値ではなく参照型の変数を取得するためのパターンの構文にすぎません。例えば:

let x: u32 = 12;

let y = x;           // y: u32, a copy of x
let ref z = x;       // z: &u32, points to x
let ref mut zz = x;  // zz: &mut u32, points to x

上記の例は、参照演算子を使用して書き換えることができます。

let z = &x;
let zz = &mut x;

(これにより、より慣用的になります)、refsが必須である場合があります。たとえば、参照を列挙バリアントに取り込む場合です。

let x: Option<Vec<u32>> = ...;

match x {
    Some(ref v) => ...
    None => ...
}

上記の例では、xmatchステートメント全体の中でのみ借用されるため、このxの後にmatchを使用できます。そのように書くと:

match x {
    Some(v) => ...
    None => ...
}

xはこのmatchによって消費され、その後使用できなくなります。

87

Boxは、論理的には未加工のポインタ(*const T)を囲むnewtypeです。ただし、構築および破棄中にデータの割り当てと割り当て解除を行うため、他のソースからデータを借用する必要はありません。

Rc-参照カウントポインターなど、他のポインタータイプでも同じことが言えます。これらは、割り当て元および割り当て解除元のプライベート生ポインタを含む構造体です。

生のポインターは、通常のポインターとまったく同じレイアウトを持っているため、いくつかのケースでCポインターと互換性がありません。重要なのは、*const str*const [T]脂肪ポインターであるため、値の長さに関する追加情報が含まれることを意味します。

ただし、生のポインタは、その有効性に関してまったく保証しません。たとえば、私は安全に行うことができます

123 as *const String

メモリ位置123は有効なStringを指していないため、このポインターは無効です。したがって、1つを間接参照する場合、unsafeブロックが必要です。

さらに、特定の法律を尊重するために借り入れが必要であるのに対し、つまり、1つが可変の場合は複数の借り入れはできないということですが、生のポインターはこれを尊重する必要はありません。 従わなければならない他の、より弱い法律があります しかし、これらに違反する可能性は低くなります。

*mut*constの間に論理的な違いはありませんが、特定の操作を行うために他方にキャストする必要がある場合があります-違いは文書化されています。

9
Veedrac

参照と生のポインタは、実装レベルで同じものです。プログラマーの観点との違いは、参照は安全(Rust用語))ですが、生のポインターは安全ではないということです。

ボローチェッカーは、参照が常に有効であること(ライフタイム管理)、一度に1つの可変参照のみを持つことができることなどを保証します。

これらのタイプの制約は、多くのユースケースに対して厳しすぎる可能性があるため、ローポインター(C/C++などの制約を持たない)は、低レベルのデータ構造、および一般的に低レベルのものを実装するのに役立ちます。ただし、unsafeブロック内で未加工のポインターを逆参照するか、操作することができます。

標準ライブラリのコンテナは、BoxRcも生のポインタを使用して実装されます。

BoxRcは、C++のスマートポインター、つまり生のポインターのラッパーです。

5
eulerdisk