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

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

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

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

Q&A

解決済

1回答

1236閲覧

構造体のフィールドが全てスタック上にあるなら,MoveセマンティクスとCopyセマンティクスはほとんど同じ?

mitiko

総合スコア2

Rust

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

0グッド

4クリップ

投稿2021/11/30 13:00

知りたいこと

  1. 構造体のフィールドが全てスタック上にある場合,MoveセマンティクスとCopyセマンティクスはほとんど同じ?
  2. そのような構造体には常にCopyトレイトを実装しても良い(もしくはいつ実装すべきか)?

内容

まず1についてです.struct Point { x: f64, y: f64 } のような構造体をmoveすると,xyの値がそれぞれコピーされます.実際,以下のようなコードを実行すると,r1r2が別物であることが分かります.

Rust

1struct Point { x: f64, y: f64 } 2fn main() { 3 let r1 = Point { x: 0.0, y: 0.0 }; 4 println!("r1: {:p},{:p},{:p}", &r1, &r1.x, &r1.y); 5 let r2 = r1; 6 // println!("r1: {:p},{:p},{:p}", &r1, &r1.x, &r1.y); <- Compile error! 7 println!("r2: {:p},{:p},{:p}", &r2, &r2.x, &r2.y); 8}
r1: 0x7ffe4731eb48,0x7ffe4731eb48,0x7ffe4731eb50 r2: 0x7ffe4731ebe8,0x7ffe4731ebe8,0x7ffe4731ebf0

また,同じ構造体にCopyトレイトを実装した場合let r2 = r1;はcopyセマンティクスになりますが,当然この時もxyはコピーされます.

Rust

1#[derive(Clone, Copy)] 2struct Point { x: f64, y: f64 } 3fn main() { 4 let r1 = Point { x: 0.0, y: 0.0 }; 5 println!("r1: {:p},{:p},{:p}", &r1, &r1.x, &r1.y); 6 let r2 = r1; 7 println!("r1: {:p},{:p},{:p}", &r1, &r1.x, &r1.y); 8 println!("r2: {:p},{:p},{:p}", &r2, &r2.x, &r2.y); 9}
r1: 0x7fff72f1ee38,0x7fff72f1ee38,0x7fff72f1ee40 r1: 0x7fff72f1ee38,0x7fff72f1ee38,0x7fff72f1ee40 r2: 0x7fff72f1eed8,0x7fff72f1eed8,0x7fff72f1eee0

このPointのようにフィールドが全てスタック上にあるような構造体の場合,moveセマンティクスとcopyセマンティクスの違いはなんでしょうか?let r2 = r1;のあとr1が使える・使えない以外は同じに思えます.

そして2の質問ですが,もしmoveとcopyで同じようにコピーが発生するなら,Pointのような構造体には常にCopyトレイトを実装しても良いのでしょうか?どうせ全てコピーされるなら,Copyトレイトを実装していたほうができることが増えるから良いような気がします(例えばlet p = points[0]).逆に,それが良くないことならば,Copyトレイトはいつ実装するのが正しいのでしょうか?

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

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

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

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

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

guest

回答1

0

ベストアンサー

まず、最初に説明しなければならないのは、Moveセマンティクス、Copyセマンティクスというのは言語仕様の世界の話であり、実装の世界とは別のものであるという点です。

Moveセマンティクス、Copyセマンティクスは、言語仕様として代入等の操作がどのような意味を持つかということを示すものです。
Moveセマンティクスなら所有権の移動が行われて元の変数は失効します。
Copyセマンティクスなら、暗黙的にコピーが行われて元の変数とコピーされた変数はそれぞれ別の変数として残ります。

一方で、実装においてMoveセマンティクスやCopyセマンティクスがどう実装されているかというのは、実装に依存する話です。
実装は言語仕様を満たしていなければなりませんが、満たす方法については実装依存になります。
ですから、例のようなMoveセマンティクスの場合に、変数のメモリ自体は同じ場所を使い回すことでメモリコピーを節約するような実装をすることも可能なはずです。

ということから、 1. の回答としては「MoveセマンティクスとCopyセマンティクスは明確に異なる」です。
MoveセマンティクスとCopyセマンティクスは言語仕様の世界で見ると所有権の扱いが全く異なります。
ですから、

この Point のようにフィールドが全てスタック上にあるような構造体の場合,moveセマンティクスとcopyセマンティクスの違いはなんでしょうか? let r2 = r1; のあと r1 が使える・使えない以外は同じに思えます.

という疑問については、「r1 が使える・使えない以外は同じ」という見方が適切ではないということです。

この所有権の扱いの違いは、2. のCopyトレイトを常に実装すべきかという話にも関わります。
結論から言えば、Copyを実装すべきかどうかは、明示的に複製すべき型なのか、暗黙的・自動的に複製すべき型なのかを考慮して決めることになります。
データの中には、データを複製する操作がデータに対する操作の1つとして意味的に重要なものと、実装の都合で複製するだけであまり重要でないものがあります。

例えば、下のコードでは Transfer は中身がプリミティブ型しかないので CloneCopy を簡単に実装できますが、気軽にコピーされることによって意図しない実装をする可能性があり、むしろ正しいコードを書くことが難しくなっています。
これは、transfer を複製するという処理が重要な意味を持つためです。
このような場合、むしろ意図的に Copy を実装しないことで、.clone() を書くことを強制させるという手法があります。

/// 振込を表す #[derive(Clone, Copy)] struct Transfer { // 振込元の口座ID from_account: u64, // 振込先の口座ID to_account: u64, // 振込金額 sum: u64, } fn main() { loop { let transfer = recv_transfer(); execute_transfer(transfer); // ここで暗黙的に複製されている execute_transfer(transfer); // 簡単に複数回実行できてほしくない! } }

また、コードの変更に伴う互換性の破壊というリスクもあります。
コードの変更で構造体内に Vec<_> 等が追加される場合、Copy を実装することはできなくなります。
このとき、構造体を使用するコードで Copy に依存する使い方が多数ある場合、修正コストが大きくなります。
このように、不用意に実装が可能だからといって Copy を実装してしまうと、コードの変更に関するリスクを抱え込むことになります。

以上から、Copy を実装するのは、コードの意味的にその型が Copy であることが妥当であると判断してからにすべきです。

投稿2021/12/01 02:38

IgaguriMK

総合スコア148

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

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

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.35%

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

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

質問する

関連した質問