まず明確にしておかなけばならないのは、Copy
トレイトを実装した型で行われる値のコピーと、Clone
トレイトを実装した型で行われる値のclone()
は多くの場合は異なる操作だということです。
前者はメモリ上のバイト列を単純にコピーする操作で、Cのmemcpy
に相当します。このような操作は一般的にはshallow copy(浅いコピー)と呼ばれます。
後者はその型に実装されたclone()
メソッドを呼び出す操作です。どういうことをするかはclone()
メソッドの実装に依存しますが、多くの場合はmemcpy
よりも複雑なdeep copy(深いコピー)を行います。
たとえばString
型は文字列データをVec<u8>
型で持ちます。Vec<T>
型はT
型の各要素を格納するメモリ領域と、3つの要素を持つ構造体で表現されています。その構造体の要素は、前述のメモリ領域を指すポインタ、Vec<T>
の容量(capacity)、Vec<T>
の長さになっています。
仮にString
がCopy
トレイトを実装できたとすると、shallow copyによってコピーされるのはVec<u8>
の構造体の部分だけになります。u8
型の各要素を格納するメモリ領域はコピーされず、また構造体の中にあるポインタも(memcpy
相当なので)みな同じアドレスを指します。つまり、コピーによって作られた複数のString
が、同じu8
型の要素を共有することになります。
Copy
トレイトはmemcpy
相当の操作だけで完全にコピーできる型にしか実装できないようになっています。たとえばu8
型の値はmemcpy
でコピーすれば十分(他にコピーするものがない)なのでCopy
トレイトが実装されています。一方、Vec<T>
はmemcpy
では不十分(shallow copyになる)なのでCopy
トレイトを実装できません。
一方、Vec<T>
のclone()
メソッドはdeep copyを行うように実装されています。各要素を格納するメモリ領域が新たに割り当てられ、構造体の値も新たに作られます。また個々の要素も、そのclone()
メソッドを呼ぶことでコピーされます。つまり、clone()
によって作られた複数のString
は、それぞれが異なるメモリ領域にあるu8
型の要素を持つことになります。
さて本題のRustの配列型の初期化構文[s; 2]
では、なぜs
のところにCopy
トレイトを実装した型しか受け付けないかですが、その理由はドキュメントに書かれてないので、あくまでも私の推測を元に説明します。
Rustの配列はあらかじめ言語に組み込まれている型(プリミティブ型)です。また、初期化の構文[s; 2]
も言語に組み込まれています。そのような構文からは、clone()
のようにRustコードで実装されたメソッドを呼び出すことができないのかもしれません。メソッドは呼べないので、型の実装に依存しないmemcpy
相当のコピーを行うのではないでしょうか。memcpy
相当の操作で完全にコピーできることを保証するには、s
がCopy
トレイトを実装していなければなりません。
Vec<T>
には配列の初期化にそっくりのvec![s; 2]
の構文があります。Vec<T>
はプリミティブ型ではなく、Rustコードで実装されたユーザー定義型です。またvec![s; 2]
はマクロで実装されており、これもコンパイル時にRustコードに変換されます。そのためだと思いますが、vec![s; 2]
ではs
がCopy
を実装している必要はなく、Clone
を実装していれば初期化できます。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2020/04/15 15:51