この記事について
Rust Design Patternsで紹介されているRustでのプログラミングにおけるイディオムをまとめました。
format!マクロを使用したstringの結合
所有権の制約によりRustでのstringの結合はとてもめんどくさいです。
let mut result = "Hello".to_owned();
let mut name = "World".to_owned();
result.push_str(name);
result.push('!');
format!マクロを使えばかんたんに結合できます。
let mut name = "World".to_owned();
let mut result = format!("Hello {}!", name);
Defaultトレイト
Rustの多くの型がその型独自のコンストラクタ(new()メソッド)を持っています。しかし、Rustは「new()メソッドを持つ型全てに渡って再帰的に適用可能」という抽象化を行っていません。
なので、new()を定義するときに全てのメンバの値を書いてやらなければいけません。
struct Hako
{
one : i32,
two : i64,
fone : f32,
ftwo : f64,
}
impl Hako {
fn new() -> Self
{
Hako {one : 0, two : 0, fone : 0.0, ftwo : 0.0}
}
}
見ての通りnew内で一つ一つ要素の初期値を指定するのはめんどくさいです。
そこで、Defaultトレイトを使います。
#[derive(Default)]
struct Hako
{
one : i32,
two : i64,
fone : f32,
ftwo : f64,
}
fn main() -> ()
{
let a : Hako = Default::default();
}
#[derive(Default)]を継承するだけで実装可能。rustのプリミティブ型ならすでにDefaultトレイトが実装されているので、new()のように要素の初期値を指定する必要はありません。
さらに、Defaultトレイトは再帰的に適用されるので自分で定義した構造体をメンバとして持つ構造体でもDefaultトレイトが実装されていれば#[derive(Default)]を継承するだけで実装可能です。
#[derive(Default)]
struct Hako
{
one : i32,
two : i64,
fone : f32,
ftwo : f64,
}
#[derive(Default)]
struct LHako
{
one : Hako,
two : Hako,
}
fn main() -> ()
{
let a : LHako = Default::default();
}
個人的にはnew()っていらなくね?と感じました。
デストラクタで後始末
関数内で複数の返すところがあると、処理の流れがわかりにくい上に、同じような処理を繰り返す可能性が高いです。Javaなどではfinalizeブロック、メソッドこういった事を行うのですが、このような機能はRustにはありません。そのかわり、関数内でローカルの構造体とそのメソッドを定義する機能を提供しています。その機能を利用して、関数が抜けたときに実行される後始末処理を定義します。
fn hoge() -> i32
{
struct Final;
impl Drop for Final
{
fn drop(&mut self)
{
println!("Bye Bye");
}
}
let finalizer = Final;
/*ここで処理を実行*/
/*関数から抜けるとかならず"Bye Bye"してくれる*/
}
Option型でイテレート
Option型は実はIntoIteratorトレイトを実装しています。
このためSomeでラップするとイテレータに対して定義された関数が使えるようになります。
let hoge = Some("hoge");
let mut mojo = vec!["buzz", "fizz"];
mojo.extend(hoge);
for elem in mojo.iter().charin(hoge.iter())
{
println!("{}", elem);
}
拡張性のためのプライベート
pubで修飾しないフェールドを用いて構造体を拡張しても、動作の安定性が失われないようにします。
mod a {
// パブリックな構造体
pub struct S {
pub foo: i32,
// プライベートなメンバ変数
bar: i32,
}
}
fn main(s: a::S) {
// 公開されていない部分に関しては".."で初期化する
let hoge = a::S { foo: _, ..};
// 構造体Sにメンバが新しく追加されてもこの部分は修正しなくてもよい。
}
コンストラクタ
Rustにはコンストラクタがありません。代わりにnewメソッドを使ってオブジェクトを生成することが慣例になっています。
pub struct Hoge {
fuga : i32,
}
impl Hoge {
pub fn new() -> Self
{
Hoge {fuga : 42,}
}
}
mem::replaceによるenumの書き換え
Rustのenumを使っているときにenumのデータを書き換えたくなるときがあります。
ちょうど下のような感じです。
enum MyEnum
{
A{ name : String},
B{ name : String},
}
fn overwrite(e : &mut MyEnum) -> ()
{
*e = match *e {
A(ref mut name) => B {name : String::new("Bだぜ")},
B(ref mut name) => A {name : String::new("Aだぜ")},
}
}
しかし、これは以下のエラーによりコンパイルできません。
cannot move out of borrowed content
eはnameを参照しているからです。nameを変更するとeの参照先が消えるのでボローチェッカがキレます。
そこで、mem::replaceを使います。mem::replaceを使うとボローチェッカを怒らせることなく参照先を変更することができます。(これって安全な操作なのか?)
enum MyEnum
{
A{ name : String},
B{ name : String},
}
fn overwrite(e : &mut MyEnum) -> ()
{
*e = match *e {
A(ref mut name) => B {name : mem::replace(name, String::new("Bだぜ"))},
B(ref mut name) => A {name : mem::replace(name, String::new("Aだぜ"))},
}
}
クロージャに変数渡し
デフォルトではクロージャには借用か、ムーブで環境の変数を渡すことができます。しかし、参照やコピーで渡したいときがあります。そのような場合は、まず以下のように変数をRcでラップし、クロージャをブロックの中に入れた上で、ブロック内部で各変数を再バインディングします。
let num1 = Rc::new(1);
let num2 = Rc::new(2);
let num3 = Rc::new(3);
let closure = {
// num1はムーブ
let num2 = num2.clone(); // num2はコピー
let num3 = num3.as_ref(); // num3は借用
move || {
*num1 + *num2 + *num3;
}
};
参考文献
Rust Design Patterns
ラストの非公式デザインパターン集
Defaultトレイト
Defaultトレイトの公式ドキュメント
mem::replace関数
mem::replace関数の公式ドキュメント
救援要請
「Collections are smart pointers」イディオムがちょっと意味がわかりませんでした。
わかった人がいたらコメントで説明していただければ幸いです。