前の記事
- 【0】 準備 ← 初回
- ...
- 【11】 Sized トレイト・From トレイト・関連型 ~おもしろトレイトと関連型~ ← 前回
- 【12】 Clone・Copy・Dropトレイト ~ 覚えるべき主要トレイトたち ~ ← 今回
全記事一覧
- 【0】 準備
- 【1】 構文・整数・変数
- 【2】 if・パニック・演習
- 【3】 可変・ループ・オーバーフロー
- 【4】 キャスト・構造体 (たまにUFCS)
- 【5】 バリデーション・モジュールの公開範囲 ~ → カプセル化!~
- 【6】 カプセル化の続きと所有権とセッター ~そして不変参照と可変参照!~
- 【7】 スタック・ヒープと参照のサイズ ~メモリの話~
- 【8】 デストラクタ(変数の終わり)・トレイト ~終わりと始まり~
- 【9】 Orphan rule (孤児ルール)・演算子オーバーロード・derive ~Empowerment 💪 ~
- 【10】 トレイト境界・文字列・Derefトレイト ~トレイトのアレコレ~
- 【11】 Sized トレイト・From トレイト・関連型 ~おもしろトレイトと関連型~
- 【12】 Clone・Copy・Dropトレイト ~覚えるべき主要トレイトたち~
- 【13】 トレイトまとめ・列挙型・match式 ~最強のトレイトの次は、最強の列挙型~
- 【14】 フィールド付き列挙型とOption型 ~チョクワガタ~
- 【15】 Result型 ~Rust流エラーハンドリング術~
- 【16】 Errorトレイトと外部クレート ~依存はCargo.tomlに全部お任せ!~
- 【17】 thiserror・TryFrom ~トレイトもResultも自由自在!~
- 【18】 Errorのネスト・慣例的な書き方 ~Rustらしさの目醒め~
- 【19】 配列・動的配列 ~スタックが使われる配列と、ヒープに保存できる動的配列~
- 【20】 動的配列のリサイズ・イテレータ ~またまたトレイト登場!~
- 【21】 イテレータ・ライフタイム ~ライフタイム注釈ようやく登場!~
- 【22】 コンビネータ・RPIT ~ 「
Iterator
トレイトを実装してるやつ」~ - 【23】
impl Trait
・スライス ~配列の欠片~ - 【24】 可変スライス・下書き構造体 ~構造体で状態表現~
- 【25】 インデックス・可変インデックス ~インデックスもトレイト!~
- 【26】 HashMap・順序・BTreeMap ~Rustの辞書型~
- 【27】 スレッド・'staticライフタイム ~並列処理に見るRustの恩恵~
- 【28】 リーク・スコープ付きスレッド ~ライフタイムに技あり!~
- 【29】 チャネル・参照の内部可変性 ~Rustの虎の子、mpscと
Rc<RefCell<T>>
~ - 【30】 双方向通信・リファクタリング ~返信用封筒を入れよう!~
- 【31】 上限付きチャネル・PATCH機能 ~パンクしないように制御!~
- 【32】
Send
・排他的ロック(Mutex
)・非対称排他的ロック(RwLock
) ~真打Arc<Mutex<T>>
登場~ - 【33】 チャネルなしで実装・Syncの話 ~考察回です~
- 【34】
async fn
・非同期タスク生成 ~Rustの非同期入門~ - 【35】 非同期ランタイム・Futureトレイト ~非同期のお作法~
- 【36】 ブロッキング・非同期用の実装・キャンセル ~ラストスパート!~
- 【37】 Axumでクラサバ! ~最終回~
- 【おまけ1】 Rustで勘違いしていたこと3選 🏄🌴 【100 Exercises To Learn Rust 🦀 完走記事 🏃】
- 【おまけ2】 【🙇 懺悔 🙇】Qiitanグッズ欲しさに1日に33記事投稿した話またはQiita CLIとcargo scriptを布教する的な何か
100 Exercise To Learn Rust 演習第12回になります!今回紹介するトレイト達はRustでプログラミングする際に常に重要になってくる者たちです。
今回の関連ページ
[04_traits/11_clone] Cloneトレイト
問題はこちらです。
// TODO: add the necessary `Clone` implementations (and invocations)
// to get the code to compile.
pub fn summary(ticket: Ticket) -> (Ticket, Summary) {
(ticket, ticket.summary())
}
pub struct Ticket {
pub title: String,
pub description: String,
pub status: String,
}
impl Ticket {
pub fn summary(self) -> Summary {
Summary {
title: self.title,
status: self.status,
}
}
}
pub struct Summary {
pub title: String,
pub status: String,
}
Clone
トレイトを実装して呼んでほしい、という問題です。
解説
Clone
トレイトを実装して呼ぶだけですね。
// TODO: add the necessary `Clone` implementations (and invocations)
// to get the code to compile.
pub fn summary(ticket: Ticket) -> (Ticket, Summary) {
- (ticket, ticket.summary())
+ (ticket.clone(), ticket.summary())
}
+ #[derive(Clone)]
pub struct Ticket {
pub title: String,
pub description: String,
pub status: String,
}
impl Ticket {
pub fn summary(self) -> Summary {
Summary {
title: self.title,
status: self.status,
}
}
}
pub struct Summary {
pub title: String,
pub status: String,
}
Clone
トレイトは複製のトレイトです。所有権に厳しいRustで、変更を共有するつもりがない構造体の複製は、 Clone
トレイトの .clone()
メソッドを呼ぶことによって行われます。
かなり重要なトレイトではありますが、構造体に関しては幸いにして全フィールドに Clone
トレイトが実装されていれば手動実装もとても楽なトレイトです(そうでない場合でも手動実装可能です)。書き出してみると .clone()
メソッドが何を行うのかは一目瞭然でしょう。
struct Hoge {
a: usize,
b: String,
c: Vec<bool>,
}
impl Clone for Hoge {
// 元の変数の所有権は奪わず、新しい構造体インスタンスを返している
fn clone(&self) -> Self {
Self {
// 複製されてそう
a: self.a.clone(),
b: self.b.clone(),
c: self.c.clone(),
}
}
}
そしてこの処理を自動実装してくれるのが Clone
deriveマクロです。 Clone
マクロを使うには、全フィールドの型が Clone
トレイトを実装している必要があります。 下手に手動実装するより、この制約を守りながらderiveマクロで実装するのが定石でしょう。
このマクロを使用して Clone
を実装すると嬉しいこととして、 .clone()
メソッドを呼び出すと複製が再帰的に呼び出されます。すなわちデフォルトで ディープコピーされる わけです!
所有権や参照という仕組み、そしてここまでの演習からもしかしたら察しがつくかもしれませんが、 Rustにはシャローコピーという考え方は基本的にありません 1。 もちろん工夫をすればシャローコピーに準じた実装をすることが可能ですが、その場合ちょっと大掛かりになる(第29回か、下記に示す拙著を参照してほしいです)ので普段は気にする必要がありません。他言語で散々プログラマを惑わすあの罠は、Rustには基本通用しないのです!
もっと詳しい話は拙著を読んでくださると恐悦至極です!
[04_traits/12_copy] Copyトレイト
問題はこちらです。
// TODO: implement the necessary traits to make the test compile and pass.
// You *can't* modify the test.
pub struct WrappingU32 {
value: u32,
}
impl WrappingU32 {
pub fn new(value: u32) -> Self {
Self { value }
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_ops() {
let x = WrappingU32::new(42);
let y = WrappingU32::new(31);
let z = WrappingU32::new(u32::MAX);
assert_eq!(x + y + y + z, WrappingU32::new(103));
}
}
「テストを変えずにコンパイルを通してね」問題になります。
解説
.clone()
メソッドが呼び出されていないので Clone
トレイトだけでは不十分です。 マーカートレイト Copy
も実装することで、所有権を奪われそうになったら自動複製されるようにします。
use std::ops::Add;
+ #[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub struct WrappingU32 {
value: u32,
}
impl WrappingU32 {
pub fn new(value: u32) -> Self {
Self { value }
}
}
impl Add for WrappingU32 {
type Output = Self;
fn add(self, rhs: Self) -> Self::Output {
Self {
value: self.value.wrapping_add(rhs.value),
}
}
}
Copy
トレイトを実装するには、前提として Clone
トレイトが実装されている必要があるため、回答には Clone
トレイトも付けています。
Copy
トレイトの付与にはもう一つ条件があって、内包するフィールド全てが Copy
トレイトを実装している 必要があります。そして、 Copy
を実装している型は大体 スタック上に値が保持されるプリミティブ型 です。ヒープアロケーションが必要な型( String
や Vec<T>
など)を内包している時は Copy
は実装できないと考えておきましょう。
Clone
トレイトを付与していることから内部的に .clone()
を呼んでいると勘違いしがちですが(実際筆者は勘違いしていました。)、 Copy
トレイトが付与された型の複製は、スタックメモリ上の値がそのまま別な場所に複製されるだけで .clone()
は呼ばれません!
Copy
を実装している型で .clone()
にて変わった処理を実装することは可能ですが、その場合望まない結果になることが容易に予想されるので、やめておいた方が良さそうです。
Copy
による自動複製では .clone()
が呼ばれていないことが観測できます。
[04_traits/13_drop] Dropトレイト
問題はこちらです。
// TODO: implement a so-called "Drop bomb": a type that panics when dropped
// unless a certain operation has been performed on it.
// You can see the expected API in the tests below.
#[cfg(test)]
mod tests {
use super::*;
#[test]
#[should_panic]
fn test_drop_bomb() {
let bomb = DropBomb::new();
// The bomb should panic when dropped
}
#[test]
fn test_defused_drop_bomb() {
let mut bomb = DropBomb::new();
bomb.defuse();
// The bomb should not panic when dropped
// since it has been defused
}
}
「ドロップ前にリソースをしっかりと解放していないとパニックになる、『Drop爆弾』を実装してください」という問題です。
解説
Rustのリソース管理便利トレイト、 Drop
の登場です!このトレイトにより提供される drop
メソッドは、 変数のドロップ時に呼び出されます !
struct DropBomb {
defused: bool,
}
impl DropBomb {
fn new() -> Self {
Self { defused: false }
}
fn defuse(&mut self) {
self.defused = true;
}
}
impl Drop for DropBomb {
fn drop(&mut self) {
if !self.defused {
panic!("Resources are not yet cleaned up! Please call `defuse` method before drop to clean up it.");
}
}
}
この性質を利用してテストを通します!DropBombの値のライフタイムが終了してドロップされる際に drop
メソッドの中身が呼ばれるので、 defuse
メソッドであらかじめ然るべきリソース解放処理をしていないと、パニックしてしまう、という寸法です。
Drop
トレイトを実装すれば、Rustのライフタイムの仕組みを借りて、リソース管理ができるのです!Pythonの with 文やC#のuseブロックに近い感じでしょうか...?
これは例えば以前少し触れたようにファイルハンドラや、そしてDBのコネクションやトランザクション等にも応用できます。
トランザクション等の close
の呼び忘れを気にしなくて良いので、とても強力なトレイトですね。常に意識しておきたいです。
Bookに書かれていることですが、 Copy
トレイトと Drop
トレイトは共存できません! 。これも筆者知らなかったよ...
Copy
トレイトが実装される型はスタック上でしか扱われない軽い型であるというコンセプトを鑑みると、リソース片付けを目的とする Drop
トレイトが共存不可というのは納得できる話ではあります。
では次の問題に行きましょう!
次の記事: 【13】 トレイトまとめ・列挙型・match式 ~最強のトレイトの次は、最強の列挙型~
登場したPlayground
(実際に無効化したことはないですが、)Rust Playground上のデータが喪失する可能性を鑑みて、一応記事にもソースコードを掲載することとしました。
#[derive(Debug)]
struct Hoge(u32);
impl Clone for Hoge {
fn clone(&self) -> Self {
println!("beep!: {}", self.0);
Hoge(self.0)
}
}
impl Copy for Hoge {}
fn main() {
let h = Hoge(10);
let h2 = h;
dbg!(h);
dbg!(h2);
dbg!(h);
h.clone();
}
-
「参照が複製される様はシャローコピーでは?」というツッコミが来そうですが、「なんでわざわざそんな難しい言い方をするのですか?『参照が複製される』と表現すれば良くないですか?」と返したくなっちゃうんですよね。もとい、シャローコピーという考え方がない、というよりは、そういう言い回しをしない、という方が正確なのかもしれません。 ↩