Rust Design Patternsを読む


2.1 Use borrowed types for arguments
引数には所有型よりも借用型(参照型)を使ったほうがいい(String
より&str
がいい)。
借用型にすることで、暗黙的に型変換されて幸せになれる。
❌ fn some_func(word: &String)
⭕ fn some_func(word: &str)
下の関数では、String
を入れたとしても強制的に&str
に変換される

2.2 Concatenating strings with format!
文字列の結合はformat!
を使うのが楽。
format!("Hello {name}!")

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

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()
};
}
bool
はDefault
がfalse
となるので、このコードの場合a
がtrue
、b
がfalse
になる。
それぞれの型がDefaultがどのようになるかはここにまとまっている。

2.5 Collections are smart pointers
そもそもスマートポインタとは、メモリ管理の自動化などを行ってくれる便利なポインタのこと。
Rustでは特殊なトレイト(Deref
、Drop
)を有した構造体として実装されていて、例として、Vec<T>
やString
があげられる。
自作の構造体にDeref
を実装することで、参照外し演算子を扱えるようになり、暗黙的な型変換が利用できるようになる。
この記事が結構わかりやすかった。

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

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()
}
}
}

2.8 On-Stack Dynamic Dispatch
あとでかく

2.9 FFI
FFIやりたくなったら読む

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);
}

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;
}
};

2.12 #[non_exhaustive] and private fields for extensibility
あとでよむ

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
};
こう書くことで、意図しない値の変更を防いだりもできる

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

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

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
)]