質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
Rust

Rustは、MoFoが支援するプログラミング言語。高速性を維持しつつも、メモリ管理を安全に行うことが可能な言語です。同じコンパイル言語であるC言語やC++では困難だったマルチスレッドを実装しやすく、並行性という点においても優れています。

Q&A

解決済

1回答

15689閲覧

RustのCloneとCopyについての素朴な疑問

hito4

総合スコア10

Rust

Rustは、MoFoが支援するプログラミング言語。高速性を維持しつつも、メモリ管理を安全に行うことが可能な言語です。同じコンパイル言語であるC言語やC++では困難だったマルチスレッドを実装しやすく、並行性という点においても優れています。

3グッド

1クリップ

投稿2020/04/15 13:32

RustのCloneとCopyトレイトについての質問です。

配列の要素として、Stringのclone()を並べるとコンパイルが通ります。
let s = String::from("xxx");
let a = [s.clone(),s.clone(),]; ←これはOK

配列の初期化の書き方をすると、エラーになります(String型にCopyトレイトが実装されていないので)。
let s = String::from("xxx");
let a = [s;2]; ←これはNG

clone()を並べる書き方が許されていて、初期化の書き方が許されていないのは、
何故なのでしょうか。同じように見えるのですが‥‥?

実害はないのですがすごく気になり質問いたしました‥‥ X-(

error[E0277]: the trait bound `std::string::String: std::marker::Copy` is not satisfied --> src/main.rs:5:10 | 5 | let a = [s;2]; | ^^^^^ the trait `std::marker::Copy` is not implemented for `std::string::String` | = note: the `Copy` trait is required because the repeated element will be copied
tatsuya6502, A_kirisaki, yohhoy👍を押しています

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答1

0

ベストアンサー

まず明確にしておかなけばならないのは、Copyトレイトを実装した型で行われる値のコピーと、Cloneトレイトを実装した型で行われる値のclone()は多くの場合は異なる操作だということです。

前者はメモリ上のバイト列を単純にコピーする操作で、Cのmemcpyに相当します。このような操作は一般的にはshallow copy(浅いコピー)と呼ばれます。

後者はその型に実装されたclone()メソッドを呼び出す操作です。どういうことをするかはclone()メソッドの実装に依存しますが、多くの場合はmemcpyよりも複雑なdeep copy(深いコピー)を行います。

たとえばString型は文字列データをVec<u8>型で持ちます。Vec<T>型はT型の各要素を格納するメモリ領域と、3つの要素を持つ構造体で表現されています。その構造体の要素は、前述のメモリ領域を指すポインタ、Vec<T>の容量(capacity)、Vec<T>の長さになっています。

仮にStringCopyトレイトを実装できたとすると、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相当の操作で完全にコピーできることを保証するには、sCopyトレイトを実装していなければなりません。

Vec<T>には配列の初期化にそっくりのvec![s; 2]の構文があります。Vec<T>はプリミティブ型ではなく、Rustコードで実装されたユーザー定義型です。またvec![s; 2]はマクロで実装されており、これもコンパイル時にRustコードに変換されます。そのためだと思いますが、vec![s; 2]ではsCopyを実装している必要はなく、Cloneを実装していれば初期化できます。

投稿2020/04/15 15:33

tatsuya6502

総合スコア2035

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

hito4

2020/04/15 15:51

とても丁寧な説明をありがとうございます。わかりやすく、腑に落ちました。 ネットでも調べていたのですが、ブログ等の記事だと、CloneとCopyの説明はぼんやりしたものが多かったように思います。TRPLの和訳も読んだのですが、なかなか頭に入らなくて困っていました。 Rustはコンパイラが何をやっているかわかると、理解が深まりますね。 本当にありがとうございました。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問