タイプを移動のみでコピー不可にすることはできますか?


96

編集者注:この質問はRust 1.0より前に行われたものであり、質問の一部の主張はRust 1.0では必ずしも当てはまりません。一部の回答は、両方のバージョンに対応するように更新されています。

私はこの構造体を持っています

struct Triplet {
    one: i32,
    two: i32,
    three: i32,
}

これを関数に渡すと、暗黙的にコピーされます。さて、時々私はいくつかの値がコピー可能ではないために移動する必要があると読みました。

この構造体Tripletをコピー不可にすることは可能ですか?たとえば、Tripletコピー不可、つまり「移動可能」にする特性を実装することは可能でしょうか?

Clone暗黙的にコピーできないものをコピーするためにトレイトを実装する必要があることをどこかで読みましたが、暗黙的にコピー可能なものを持ち、それを代わりに移動できるようにコピー不可にすることについては逆に読みませんでした。

それでも意味がありますか?


1
paulkoerbitz.de/posts/...。移動とコピーの理由について、ここで説明します。
Sean Perry 14年

回答:


164

序文:この回答は、オプトインの組み込みトレイト(具体的にCopyアスペクト)が実装れる前に作成されました。私は、ブロッククォートを使用して、古いスキーム(質問が尋ねられたときに適用されたスキーム)にのみ適用されたセクションを示しました。


:基本的な質問に答えるために、NoCopy値を格納するマーカーフィールドを追加できます。例えば

struct Triplet {
    one: int,
    two: int,
    three: int,
    _marker: NoCopy
}

Droptraitを実装することにより)デストラクタを使用してそれを行うこともできますが、デストラクタが何も実行していない場合は、マーカータイプを使用することをお勧めします。

型はデフォルトで移動するようになりました。つまり、新しい型を定義すると、その型にCopy明示的に実装しない限り実装されません。

struct Triplet {
    one: i32,
    two: i32,
    three: i32
}
impl Copy for Triplet {} // add this for copy, leave it out for move

実装が存在できるのは、すべての型がnewに含まれるstructenum、それ自体である場合のみCopyです。そうでない場合、コンパイラはエラーメッセージを出力します。また、型に実装がない場合にのみ存在しDropます。


あなたが尋ねなかった質問に答えるために...「移動とコピーはどうしたの?」:

まず、2つの異なる「コピー」を定義します。

  • バイトのコピーなどだけ浅くポインタ以下ではない、オブジェクトのバイト単位でのコピーされ、あなたが持っているならば(&usize, u64)、それは64ビットコンピュータ上で16バイトで、浅いコピーは、これらの16のバイトを取って、自分のを複製することになりますメモリのいくつかの他の16バイトのチャンクの値、無しタッチusizeの他方の端部&。つまり、を呼び出すことと同じmemcpyです。
  • セマンティックコピー値を複製するには、安全に古いものに別々に使用することができ、新たな(やや)の独立したインスタンスを作成します。たとえば、anのセマンティックコピーはRc<T>、参照カウントを増やすだけで、aのセマンティックコピーはVec<T>、新しい割り当てを作成し、保存されている各要素を古いものから新しいものに意味的にコピーします。これらは、とすることができるディープコピー(例えばVec<T>)または浅い(例えば、Rc<T>接触していない保存されたT)、Clone仕事の最小量は、意味的タイプの値をコピーするために必要に応じて緩く定義されているT内部から&TT

RustはCのようなもので、値の値による使用はすべてバイトコピーです。

let x: T = ...;
let y: T = x; // byte copy

fn foo(z: T) -> T {
    return z // byte copy
}

foo(y) // byte copy

それらは、T移動するかどうかに関係なくバイトコピーであるか、「暗黙的にコピー可能」です。(明確にするために、これらは必ずしも実行時に文字どおりバイト単位のコピーであるとは限りません。コードの動作が保持されている場合、コンパイラーはコピーを自由に最適化できます。)

ただし、バイトコピーには根本的な問題があります。メモリ内の値が重複することになり、デストラクタがある場合は非常に悪い可能性があります。

{
    let v: Vec<u8> = vec![1, 2, 3];
    let w: Vec<u8> = v;
} // destructors run here

w単純なバイトコピーであるv場合、同じ割り当てを指す2つのベクトルがあり、両方ともそれを解放するデストラクタを使用して... 問題が発生する二重解放を引き起こします。NB。vintoのセマンティックコピーを実行した場合、これは完全に問題ありません。wそれwは、それ自体が独立していてVec<u8>、デストラクタが互いに踏みにじっていないためです。

ここにいくつかの可能な修正があります:

  • プログラマにCのように処理させます(Cにはデストラクタがないため、それほど悪くありません...代わりにメモリリークが残ります。:P)
  • セマンティックコピーを暗黙的に実行wします。これにより、コピーコンストラクターを使用するC ++のように、独自の割り当てが行われます。
  • 値による使用を所有権の譲渡と見なすため、v使用できなくなり、デストラクタが実行されなくなります。

最後は、Rustが行うことです。移動は、ソースが静的に無効化される値による使用にすぎないため、コンパイラーは無効になったメモリをそれ以上使用できなくなります。

let v: Vec<u8> = vec![1, 2, 3];
let w: Vec<u8> = v;
println!("{}", v); // error: use of moved value

デストラクタを持つ型は、値(バイトコピー時)として使用されるときに移動する必要があります。これは、リソース(メモリ割り当てやファイルハンドルなど)の管理/所有権があり、バイトコピーがこれを正しく複製する可能性が非常に低いためです。所有。

「ええと…暗黙のコピーとは何ですか?」

次のようなプリミティブ型について考えてみてくださいu8。バイトコピーは単純で、シングルバイトをコピーするだけで、セマンティックコピーも同じくらい単純で、シングルバイトをコピーします。特に、バイトコピーセマンティックコピーです... Rustには、どのタイプが同一のセマンティックコピーとバイトコピーを持つかをキャプチャする組み込みの特性Copyもあります。

したがって、これらのCopyタイプの場合、値による使用は自動的にセマンティックコピーでもあるため、ソースを継続して使用しても完全に安全です。

let v: u8 = 1;
let w: u8 = v;
println!("{}", v); // perfectly fine

OldNoCopyマーカーは、コンパイラーの自動動作をオーバーライドして、可能なタイプCopy(つまり、プリミティブとの集合体のみを含む&)がであると想定しますCopy。ただし、オプトインの組み込みトレイトが実装れると、これは変更されます。

上記のように、オプトインの組み込みトレイトが実装されているため、コンパイラーは自動動作しなくなりました。ただし、以前の自動動作に使用されていたルールは、実装が正当かどうかを確認するためのルールと同じCopyです。


@dbaupp:オプトインの組み込みトレイトがRustのどのバージョンに表示されたか知っているでしょうか?0.10だと思います。
Matthieu M.

@MatthieuM。それはまだ実装されていません。実際、最近、オプトインの組み込みの設計にいくつかの修正案が提案されています
huon

古い見積もりは消去すべきだと思います。
Stargateur 2017

1
#[
derive

6

最も簡単な方法は、コピーできないものをタイプに埋め込むことです。

標準ライブラリは、まさにこのユースケースのための「マーカータイプ」を提供します:NoCopy。例えば:

struct Triplet {
    one: i32,
    two: i32,
    three: i32,
    nocopy: NoCopy,
}

15
これはRust> = 1.0では無効です。
マルバルボ2016年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.