Open17

Rust Design Patternsを読む

fkunn1326fkunn1326

2.1 Use borrowed types for arguments

引数には所有型よりも借用型(参照型)を使ったほうがいい(Stringより&strがいい)。
借用型にすることで、暗黙的に型変換されて幸せになれる。

fn some_func(word: &String)fn some_func(word: &str)

下の関数では、Stringを入れたとしても強制的に&strに変換される

fkunn1326fkunn1326

2.2 Concatenating strings with format!

文字列の結合はformat!を使うのが楽。

format!("Hello {name}!")
fkunn1326fkunn1326

2.3 Constructors

Rustにはクラスやコンストラクターはないので、代わりにstructnew()Defaultを実装して対応する

fkunn1326fkunn1326

2.4 The Default Trait

Rustでは、impl Default for traitをする必要なく#[derive(Default)]を使って楽に実装できる。
また、Defaultで部分的に引数を指定したい場合、このように書くことができる。

#[derive(Default)]
struct Foo {
    a: bool,
    b: bool,
}

fn main() {
    let foo = Foo {
        a: true,
        ..Default::default()
    };
}

boolDefaultfalseとなるので、このコードの場合atruebfalseになる。
それぞれの型がDefaultがどのようになるかはここにまとまっている。

fkunn1326fkunn1326

2.5 Collections are smart pointers

そもそもスマートポインタとは、メモリ管理の自動化などを行ってくれる便利なポインタのこと。
Rustでは特殊なトレイト(DerefDrop)を有した構造体として実装されていて、例として、Vec<T>Stringがあげられる。

自作の構造体にDerefを実装することで、参照外し演算子を扱えるようになり、暗黙的な型変換が利用できるようになる。

この記事が結構わかりやすかった。
https://zenn.dev/suzuki_hoge/articles/2024-04-rust-smart-pointer-94e5c1ce3c93f4

fkunn1326fkunn1326

2.6 Finalisation in destructors

Rustでは、Dropを実装して、デストラクタの振る舞いを実現できる。
これも上の記事にまとまっていた。

fkunn1326fkunn1326

2.7 mem::{take(), replace()} to keep owned values in changed enums

enumで2つ以上のバリアントが存在し、それを変換したいとき、mem::{take, replace}を使うと元の値を空にして所有権を獲得できる。

use std::mem;

enum MyEnum {
    A { name: String, x: u8 },
    B { name: String },
}

fn a_to_b(e: &mut MyEnum) {
    if let MyEnum::A { name, x: 0 } = e {
        *e = MyEnum::B {
            // mem::take()を行うと、MyEnum::Aのnameは空の値に置き換えられる
            name: mem::take(name),
            // ❌ name: name.to_string()
        }
    }
}
fkunn1326fkunn1326

2.10 Iterating over an Option

RustのOption型は、IntoIteratorを実装しているので、vecにextend()するのがとても楽

let foo = Some("D");
let mut bar = vec!["A", "B", "C"];

foo.extend(bar);

// ❌ 上と同じ処理だが、長くなってしまう
if let Some(foo_inner) = foo {
    bar.push(foo_inner);
}
fkunn1326fkunn1326

2.11 Pass variables to closure

Rustのクロージャー(無名関数)ではmoveしないといけないから変数の扱いが大変になるが、借用やクローンをスコープ内に書くことで、きれいに書ける。

use std::rc::Rc;

let num1 = Rc::new(1);
let num2 = Rc::new(2);
let num3 = Rc::new(3);

// 先に借用すると、余計な変数ができてしまって美しくない
// ❌ let num2_cloned = num2.clone();
// ❌ let num3_borrowed = num3.as_ref();

let closure = {
    // ここで書くことで上のスコープを借用しつつ下の処理に回すことができる
    let num2 = num2.clone();
    let num3 = num3.as_ref();
    move || {
        *num1 + *num2 + *num3;
    }
};
fkunn1326fkunn1326

2.12 #[non_exhaustive] and private fields for extensibility

あとでよむ

fkunn1326fkunn1326

2.14 Temporary mutability

一時的な値を作ってすぐ処理する場合(例えばvecのsortなど)、ブロックで囲んであげることできれいに書ける。

// ❌ このように書くよりも
let mut data = get_vec();
data.sort();
let data = data;

// ⭕ こう書いた方がいい
let data = {
    let mut data = get_vec();
    data.sort();
    data
};

こう書くことで、意図しない値の変更を防いだりもできる

fkunn1326fkunn1326

2.15 Return consumed argument on error

参照ではなく所有権の移動を引き起こす関数は、エラーの中でその変数を返すべき。
そうすることで、再試行するときに無駄な複製をしなくて済むようになる。

fkunn1326fkunn1326

4.1 Clone to satisfy the borrow checker

借用チェッカーのためだけにclone()してはいけない。
その代わりとして、mem::takeや、RcArcを使うようにするとよい。
ちなみに、Rcclone()すると参照のみがクローンされ、元のオブジェクトは維持され続ける。

fkunn1326fkunn1326

4.2 #![deny(warnings)]

lintやビルド時に警告を消したいときに使うが、基本的には避けた方が良い。
ただし、以下のルールはdenyしても深刻な影響がないため、利用しても良い(推奨はされない)。

#![deny(
    bad_style,
    const_err,
    dead_code,
    improper_ctypes,
    non_shorthand_field_patterns,
    no_mangle_generic_items,
    overflowing_literals,
    path_statements,
    patterns_in_fns_without_body,
    private_in_public,
    unconditional_recursion,
    unused,
    unused_allocation,
    unused_comparisons,
    unused_parens,
    while_true
)]