vec Rustは移動セマンティクスをどのように提供しますか?




rust vec get_mut (2)

Rust言語ウェブサイトの主張は、セマンティクスを言語の機能の1つとして移します。 しかし、私は移動のセマンティクスがRustでどのように実装されているのか分かりません。

錆ボックスは、移動セマンティクスが使用される唯一の場所です。

let x = Box::new(5);
let y: Box<i32> = x; // x is 'moved'

上記の錆コードは、C ++で次のように書くことができます。

auto x = std::make_unique<int>();
auto y = std::move(x); // Note the explicit move

私が知っている限り(私が間違っていれば正しい)、

  • Rustにはコンストラクタがありません。コンストラクタは移動させません。
  • 右辺値の参照はサポートされていません。
  • rvalueパラメータで関数を作成する方法はありません。

Rustは移動セマンティクスをどのように提供しますか?


私はそれがC ++からくるときには非常に一般的な問題だと思います。 C ++では、コピーや移動に関しては明示的にすべてを行います。 この言語は、コピーと参照を中心に設計されています。 C ++ 11では、「移動」する機能がそのシステムに貼り付けられました。 一方、錆は新鮮なスタートを切った。

Rustにはコンストラクタがありません。コンストラクタは移動させません。

移動コンストラクタは必要ありません。 Rustは、 "コピーコンストラクタを持たない"すべてを移動します。

struct A;

fn test() {
    let a = A;
    let b = a;
    let c = a; // error, a is moved
}

Rustのデフォルトのコンストラクタは、(慣例により)単にnewという関連する関数です。

struct A(i32);
impl A {
    fn new() -> A {
        A(5)
    }
}

より複雑なコンストラクタには、より表現力のある名前が必要です。 これはC ++の名前付きコンストラクタイディオムです

右辺値の参照はサポートされていません。

それは常に要求された機能です。RFCの問題998を参照してください。しかし、おそらくあなたは別の機能を求めているでしょう:機能を機能に移す:

struct A;

fn move_to(a: A) {
    // a is moved into here, you own it now.
}

fn test() {
    let a = A;
    move_to(a);
    let c = a; // error, a is moved
}

rvalueパラメータで関数を作成する方法はありません。

あなたは形質でそれを行うことができます。

trait Ref {
    fn test(&self);
}

trait Move {
    fn test(self);
}

struct A;
impl Ref for A {
    fn test(&self) {
        println!("by ref");
    }
}
impl Move for A {
    fn test(self) {
        println!("by value");
    }
}
fn main() {
    let a = A;
    (&a).test(); // prints "by ref"
    a.test(); // prints "by value"
}

Rustの移動とコピーのセマンティクスはC ++とは大きく異なります。 私はそれらを説明するために、既存の答えよりも別のアプローチをとるつもりです。

C ++では、コピーはカスタムコピーコンストラクタのために任意に複雑になる可能性のある操作です。 Rustは、単純な代入や引数渡しのカスタムセマンティクスを望んでいないので、別のアプローチが必要です。

まず、Rustで渡される代入または引数は、常に単純なメモリコピーです。

let foo = bar; // copies the bytes of bar to the location of foo (might be elided)

function(foo); // copies the bytes of foo to the parameter location (might be elided)

しかし、オブジェクトがいくつかのリソースを制御する場合はどうでしょうか? 私たちが単純なスマートポインタ、 Boxを扱っているとしましょう。

let b1 = Box::new(42);
let b2 = b1;

この時点で、バイトだけがコピーされた場合、デストラクター(Rustのdrop )はオブジェクトごとに呼び出されないので、同じポインターを2回解放して未定義の動作を引き起こしますか?

答えは、Rust デフォルトで移動することです。 これは、新しい場所にバイトをコピーし、古いオブジェクトがなくなったことを意味します。 上記の2行目の後にb1にアクセスするのはコンパイルエラーです。 デストラクタは呼び出されません。 値はb2に移動され、 b1も存在しなくなる可能性があります。

これは、移動セマンティクスがRustでどのように機能するかです。 バイトはコピーされ、古いオブジェクトはなくなります。

C ++の移動セマンティクスに関するいくつかの議論では、Rustの方法は "破壊的な移動"と呼ばれていました。 C ++と同様の意味を持つことができるように、C ++に似たような「デストラクターの移動」を追加することが提案されています。 しかし、C ++で実装されているセマンティクスを移動させないでください。 古いオブジェクトは残され、そのデストラクタは引き続き呼び出されます。 したがって、移動操作で必要なカスタムロジックを処理するには、移動コンストラクタが必要です。 移動は、特定の方法で動作することが期待される特殊なコンストラクタ/代入演算子です。

したがって、デフォルトでは、Rustの割り当てによってオブジェクトが移動し、古い場所が無効になります。 しかし、多くの型(整数、浮動小数点、共有参照)は、バイトをコピーすることは、古いオブジェクトを無視する必要がなく、実際のコピーを作成するための完全な有効な方法です。 そのような型は、コンパイラによって自動的に導出されるCopy特性を実装する必要があります。

#[derive(Copy)]
struct JustTwoInts {
  one: i32,
  two: i32,
}

これは、代入と引数の受け渡しが古いオブジェクトを無効にしないことをコンパイラに通知します。

let j1 = JustTwoInts { one: 1, two: 2 };
let j2 = j1;
println!("Still allowed: {}", j1.one);

ささいなコピーと破壊の必要性は互いに排他的であることに注意してください。 CopyあるタイプはDropもありません

バイトをコピーするだけでは十分ではないもの、例えばベクトルのコピーを作るときはどうでしょうか? このための言語機能はありません。 技術的には、型は、正しい方法で作成された新しいオブジェクトを返す関数を必要とするだけです。 しかし、慣例により、これはClone特性およびそのclone機能を実施することによって達成される。 実際には、コンパイラーはClone自動導出もサポートしています。クローンはすべてのフィールドをクローンします。

#[Derive(Clone)]
struct JustTwoVecs {
  one: Vec<i32>,
  two: Vec<i32>,
}

let j1 = JustTwoVecs { one: vec![1], two: vec![2, 2] };
let j2 = j1.clone();

また、 Vecようなコンテナはクローンを内部で使用するため、 Copyを取得するたびにClone派生させる必要があります。

#[derive(Copy, Clone)]
struct JustTwoInts { /* as before */ }

さて、これにはマイナス面がありますか? 実際、オブジェクトを別のメモリ位置に移動するのは、バイトをコピーするだけで、カスタムロジックを持たないため、タイプに参照を持たせることができないため、大きな欠点があります 。 実際、Rustの生涯システムは、そのような型を安全に構築することを不可能にしています。

しかし、私の意見では、トレードオフはそれに値する。





move-semantics