Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

変更後の enum 内に所有値を保持するための mem::{take(_), replace(_)}

説明

&mut MyEnum があり、これに(少なくとも)2 つのバリアント、 A { name: String, x: u8 }B { name: String } があるとします。ここで、 MyEnum::B はそのままにしつつ、x がゼロの場合に MyEnum::AB に変更したいとします。

これは name を clone せずに実現できます。

#![allow(unused)]
fn main() {
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 {
        // これは `name` を取り出し、代わりに空の String を入れます
        // (空文字列はアロケーションしないことに注意してください)。
        // その後、新しい enum バリアントを構築します(これは
        // `*e` に代入されます)。
        *e = MyEnum::B {
            name: mem::take(name),
        }
    }
}
}

これは、より多くのバリアントがある場合にも機能します。

#![allow(unused)]
fn main() {
use std::mem;

enum MultiVariateEnum {
    A { name: String },
    B { name: String },
    C,
    D,
}

fn swizzle(e: &mut MultiVariateEnum) {
    use MultiVariateEnum::*;
    *e = match e {
        // 所有権ルールにより、`name` を値として取り出すことは許可されていませんが、
        // 置き換えない限り、可変参照から値を取り出すことはできません。
        A { name } => B {
            name: mem::take(name),
        },
        B { name } => A {
            name: mem::take(name),
        },
        C => D,
        D => C,
    }
}
}

動機

enum を扱うとき、enum の値をその場で、場合によっては別のバリアントへ変更したいことがあります。これは通常、借用チェッカーを満足させるために 2 つのフェーズで行われます。最初のフェーズでは、既存の値を観察し、その部分を見て次に何をするかを決定します。2 番目のフェーズでは、条件に応じて値を変更できます(上の例のように)。

借用チェッカーは、enum から name を取り出すことを許可しません(なぜなら、何か がそこになければならないからです)。もちろん、name を .clone() して、その clone を MyEnum::B に入れることもできますが、それは 借用チェッカーを満足させるための Clone アンチパターンの一例になります。いずれにせよ、可変借用だけで e を変更することで、余分なアロケーションを避けることができます。

mem::take を使うと、値をそのデフォルト値で置き換え、以前の値を返すことで、値を入れ替えることができます。String の場合、デフォルト値は空の String であり、これはアロケーションを必要としません。その結果、元の name所有値として取得できます。その後、これを別の enum でラップできます。

注: mem::replace は非常によく似ていますが、値を何で置き換えるかを指定できます。上記の mem::take の行と等価なものは、mem::replace(name, String::new()) になります。

ただし、Option を使用していて、その値を None に置き換えたい場合は、Optiontake() メソッドがより短く、より慣用的な代替手段を提供します。

利点

見てください、アロケーションなしです!また、これを行っている間、インディ・ジョーンズのような気分になれるかもしれません。

欠点

これは少し冗長になります。何度も間違えると、借用チェッカーが嫌いになるでしょう。コンパイラーが二重のストアを最適化で取り除けない可能性があり、その結果、unsafe な言語で行う場合と比べてパフォーマンスが低下することがあります。

さらに、取り出そうとしている型は Default トレイトを実装している必要があります。ただし、扱っている型がこれを実装していない場合は、代わりに mem::replace を使用できます。

議論

このパターンが関心の対象となるのは Rust だけです。GC 付き言語では、デフォルトで値への参照を取得するでしょう(そして GC が参照を追跡します)。また、C のような他の低レベル言語では、単にポインターに別名を付けて、後で物事を修正するでしょう。

しかし Rust では、これを行うためにもう少し作業が必要です。所有値は 1 つの所有者しか持てないため、それを取り出すには、何かを代わりに戻す必要があります。ちょうどインディ・ジョーンズが、秘宝を砂袋と置き換えるようにです。

関連項目

これは、特定の場合に 借用チェッカーを満足させるための Clone アンチパターンを取り除きます。