🦀

TS使っている人のRust 0から勉強日記2

2024/10/17に公開

こんにちは!普段TypeScriptを使ってフロントエンド開発をしている人です🙌

今回は前回の日記1に引き続き、copyトレイトとcloneトレイトについてとなってます。

また今回も、内容は基礎の基礎(+TypeScriptとの比較?)で自分の勉強の記録用に書いています。


所有権についてのおさらい

全ての値に「所有者」が存在し、その所有者が所有権を持っています。(1つの値に1つの所有者)
所有者がスコープから外れるとその所有者が持つ値はメモリから解放されます。

スコープ・束縛・参照・可変性・借用チェックについては日記1に書いているので気になる方は見てみてください〜

Shallow Copy と Deep Copy の違い

今回は値を別の名前(変数名)で定義したい場合についての話をしていくのですが、その前に値をコピーする方法についてまずは確認します。
それぞれ直訳して「浅いコピー」と「深いコピー」と言われています。

JavascriptやTypescriptでのそれぞれの違い

Javascriptなどで使用されるShallow CopyとRustでいうShallow Copyについて違う点があるため、まずは(私が慣れている)JavascriptやTypescriptでいうCopyの違いについて解説します。
(そんなもんわかってるって方は次の項目へ…)

let obj1 = { a: 1, b: { c: 2 } };

//Shallow Copy
let obj2 = { ...obj1 };  // 浅いコピーを行う
obj2.a = 10;            // `obj1.a`には影響なし
obj2.b.c = 20;          // `obj1.b.c`にも影響する(参照が共有されているため)

console.log(obj1);  // { a: 1, b: { c: 20 } }
console.log(obj2);  // { a: 10, b: { c: 20 } }

// Deep Copy
let obj1 = { a: 1, b: { c: 2 } };
let obj2 = JSON.parse(JSON.stringify(obj1));  // 深いコピーを行う

obj2.a = 10;
obj2.b.c = 20;

console.log("obj1:", obj1);  // { a: 1, b: { c: 2 } }
console.log("obj2:", obj2);  // { a: 10, b: { c: 20 } }

浅いコピーでは、オブジェクトや配列の参照が共有されいます。
それに対して深いコピーではオブジェクトのネストされたプロパティや要素を再帰的にコピーし新しいオブジェクトを生成します。要するに元のオブジェクトとコピーしたオブジェクトが完全に独立した状態になります。

Rustでのそれぞれの違い

Rustにはコピーを行うための浅いコピーを行うCopyトレイトと深いコピーを行うCloneトレイトというものが用意されています。
それぞれの書き方と表示した時の結果は以下のようになります。

//Shallow Copy
let original = 42;
let copy = original;

let modified_copy = copy + 1;

println!("Modified Copy: {}", modified_copy);   // Modified Copy: 43
println!("Original: {}", original);     //Original: 42

//Deep Copy
let original = String::from("Hello, world!");
let clone = original.clone();

let mut modified_clone = clone;
modified_clone.push_str(" Modified!");

println!("Modified Clone: {}", modified_clone);     // Modified Clone: Hello, world! Modified!
println!("Original: {}", original);      //Original: Hello, world!

Rustではどちらのコピー方法を使用してもそれぞれの値を持っており、コピー先を変更してもコピー元であるoriginalは変更されていませんね。
どちらのコピー方法でも完全に独立したコピーが生成されるということです。
まとめると以下のような表になります。↓

コピー方法 TS RS
Shallow Copy 参照のコピー 独立したコピー(コピー元もコピー先もそれぞれの値となる)
Deep Copy 独立したコピー 独立したコピー

Rust内でのそれぞれのCopy方法について書きましたが、どっちもCopyトレイトって名前ではだめなのか??と疑問になったので調べてみました。

Copyトレイト

Copyトレイトは使用できる型に制約があります。Copyトレイトが使用できる型というのは「メモリの内容を単純にビット単位でコピーしても問題がない型」となります。
なんのこっちゃって感じなのでCopyトレイトが使用できる型をまとめたものがこちら。

  • 整数型(i32,u64など)
  • 浮動小数点型(f32,f64)
  • bool型
  • 参照(&T)
  • &str

整数型などを例として挙げるとi32は32ビットの固定サイズを持つデータ型なので「メモリの内容を単純にビット単位でコピーしても問題がない型」となります。
要するにヒープ領域にデータを持たないプリミティブ型や所有権の移動に特別な意味を持たない型ならCopyトレイトが使用できるということですね。

では、Copyトレイトが使用できない型も例を挙げておきます。

  • String
  • Vec< T >
  • Box< T >

これらは動的にメモリを確保するような型のため、コピーを行いたい場合はCloneトレイトを使用しましょう。
Copyトレイトが使用出来る型はCopyトレイトもCloneトレイトもどちらも使えます。

それぞれの書き方

上の解説でも少し載せましたが、それぞれのコピー方法のRustでの書き方を解説します。

copy
let original = 42;
let copy = original;

Copyトレイトを使用出来る型の場合、.copyなど書かなくてもCopyトレイトを使用することができます。

clone
let original = String::from("Hello, world!");
let clone = original.clone();

Cloneトレイトを使用する場合、明示的に.clone()とかいてコピーすることを宣言します。

自分で作成したstructにCloneトレイトやCopyトレイトを実装したい場合は以下のように書きます。

//structの要素が全てCopyトレイトが使用できる型の時のみCopyトレイトは可能
#[derive(Copy,Clone)] 
pub struct A {
    x:f64,
    y:f64
}

#[derive(Clone)]
pub struct B {
    a:String,
    b:Vec<String>
}

deriveというのは継承するということでそれぞれのトレイトを継承して使用する的な感じになるのだと思います。
deriveで使用出来るトレイトは他にもあるので気になるかたはこちら↓
https://doc.rust-lang.org/rust-by-example/trait/derive.html


まとめ

結構ざっくりだったかもしれませんが、Rustでの変数などのコピー方法についてまとめてみました。
Rustは浅いコピーでも深いコピーでもコピー先が独立しており、それぞれトレイトが違うよってことがわかってくださればもう💮
題名にもあるように日記としているので結構自分自身のメモ用みたいになってしまいましたが、よしとしてください🥺

ちょっとした自分のギモン:
書きながら思っていたのですが、Copyトレイトの使用がコードを見ただけではわかりにくいと感じてますが、慣れるものなんでしょうか…?

次回こそiterとかfor文の書き方とかまとめたい気持ちではあります…!

Discussion