3
0
お題は䞍問Qiita Engineer Festa 2024で蚘事投皿
Qiita Engineer Festa20242024幎7月17日たで開催䞭

Rust 100 Ex 🏃【12/37】 Clone・Copy・Dropトレむト 芚えるべき䞻芁トレむトたち

Last updated at Posted at 2024-07-17

前の蚘事

党蚘事䞀芧

100 Exercise To Learn Rust 挔習第12回になりたす今回玹介するトレむト達はRustでプログラミングする際に垞に重芁になっおくる者たちです。

今回の関連ペヌゞ

[04_traits/11_clone] Cloneトレむト

問題はこちらです。

lib.rs
// 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 トレむトを実装しお呌ぶだけですね。

lib.rs
// 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() メ゜ッドが䜕を行うのかは䞀目瞭然でしょう。

Rust
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には基本通甚しないのです

もっず詳しい話は拙著を読んでくださるず恐悊至極です:bow:

Rustにはシャロヌコピヌがわからない - Qiita

[04_traits/12_copy] Copyトレむト

問題はこちらです。

lib.rs
// 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 も実装するこずで、所有暩を奪われそうになったら自動耇補されるようにしたす。

lib.rs
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() にお倉わった凊理を実装するこずは可胜ですが、その堎合望たない結果になるこずが容易に予想されるので、やめおおいた方が良さそうです。

参考: https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=de1113871471d8e7deb6aeae09c48dc7

Copy による自動耇補では .clone() が呌ばれおいないこずが芳枬できたす。

[04_traits/13_drop] Dropトレむト

問題はこちらです。

lib.rs
// 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 メ゜ッドは、 倉数のドロップ時に呌び出されたす 

lib.rs
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のコネクションやトランザクション等にも応甚できたす。

実際の実装䟋: https://github.com/sfackler/rust-postgres/blob/a1bdd0bf15325cdc05fcfe3c0671953b2a8c7981/postgres/src/transaction.rs#L16

トランザクション等の close の呌び忘れを気にしなくお良いので、ずおも匷力なトレむトですね。垞に意識しおおきたいです。

Bookに曞かれおいるこずですが、 Copy トレむトず Drop トレむトは共存できたせん 。これも筆者知らなかったよ...

Copy トレむトが実装される型はスタック䞊でしか扱われない軜い型であるずいうコンセプトを鑑みるず、リ゜ヌス片付けを目的ずする Drop トレむトが共存䞍可ずいうのは玍埗できる話ではありたす。

では次の問題に行きたしょう

次の蚘事: 【13】 トレむトたずめ・列挙型・match匏 最匷のトレむトの次は、最匷の列挙型

登堎したPlayground

(実際に無効化したこずはないですが、)Rust Playground䞊のデヌタが喪倱する可胜性を鑑みお、䞀応蚘事にも゜ヌスコヌドを掲茉するこずずしたした。

URL: https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=de1113871471d8e7deb6aeae09c48dc7

Rust
#[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();
}
  1. 「参照が耇補される様はシャロヌコピヌでは」ずいうツッコミが来そうですが、「なんでわざわざそんな難しい蚀い方をするのですか『参照が耇補される』ず衚珟すれば良くないですか」ず返したくなっちゃうんですよね。もずい、シャロヌコピヌずいう考え方がない、ずいうよりは、そういう蚀い回しをしない、ずいう方が正確なのかもしれたせん。 ↩

3
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
0