(rust の)enum ってどうやって比較すんの

初歩的な記事だなぁ?と思ったやってみろ!

🎵 本日の一曲

制服可不ちゃんかわいいね。
同位体メンバーの中でも特に有名なのが可不ちゃんだと思うけれど、可不ちゃんってセイシュン!って雰囲気があまりない気がします。
でも可不ちゃんとセイシュン!したかったなぁ(※決してやましい意味ではない)

enum で比較してみろよ!

え?これだけでしょ?

enum 同位体 {
    可不,
    星界,
    裏命,
    狐子,
    羽累,
}

fn main() {
    println!("{}", 同位体::可不 == 同位体::可不);
}

できた?できなかった?

derive でトレイトをつける

ああ、そうだ。 思い出した。
ユーザー定義型(struct/enum)は初期では一切のトレイトがついてないから、自分でつけないといけないんだったな

じゃあ、==演算子に対応するトレイトはなんだっけ...

PartialEqだな

#[derive(PartialEq)]
enum 同位体 {
    可不,
    星界,
    裏命,
    狐子,
    羽累,
}

fn main() {
    println!("{}", 同位体::可不 == 同位体::可不);
}

よしできた。 てか、日本語も変数名に使えるんだな。(今更)

derive を展開して意味を説明して?って言われた

derive は確かに便利なマクロだが、たまに手動で実装したくなることもあるよな

じゃあ、手動で実装してみよう。

enum 同位体 {
    可不,
    星界,
    裏命,
    狐子,
    羽累,
}
impl PartialEq for 同位体 {
    fn eq(&self, other: &Self) -> bool {
        self==other
    }
}

fn main() {
    println!("{}", 同位体::可不 == 同位体::可不);
}

...なんかコードがオーバーフローしたぞ?
あそっか。 そもそもそのコードは再帰関数を含んでるもんな

enum 同位体 {
    可不,
    星界,
    裏命,
    狐子,
    羽累,
}
impl PartialEq for 同位体 {
    fn eq(&self, other: &Self) -> bool {
        self.eq(other) // 再帰関数になってる
    }
}

fn main() {
    println!("{}", 同位体::可不 == 同位体::可不);
}

...ん? NN? え、じゃあどうやって比較しろと?

enum を一意に比較する

discriminant という関数があるらしい。 これはenum の値を一意に識別できるようだ。

use std::mem;

enum 同位体 {
    可不,
    星界,
    裏命,
    狐子,
    羽累,
}

fn main() {
    println!("{:?}", mem::discriminant(&同位体::可不));
    println!("{:?}", mem::discriminant(&同位体::星界));
    println!("{:?}", mem::discriminant(&同位体::裏命));
    println!("{:?}", mem::discriminant(&同位体::狐子));
    println!("{:?}", mem::discriminant(&同位体::羽累));
}

これは比較可能なDiscriminantオブジェクトを返してくれる。 これで比較すれば良さそうだ。

enum 同位体 {
    可不,
    星界,
    裏命,
    狐子,
    羽累,
}
impl PartialEq for 同位体 {
    fn eq(&self, other: &Self) -> bool {
        std::mem::discriminant(self) == std::mem::discriminant(other)
    }
}

fn main() {
    println!("{}", 同位体::可不 == 同位体::可不);
}

できた!

std::mem の紹介

std::memは少し高度な用途で使う。 名前の通り、メモリ操作に特化している。

drop: 手動で変数を drop する

// 引用: https://doc.rust-lang.org/std/ops/trait.Drop.html
struct HasDrop(i32);

impl Drop for HasDrop {
    fn drop(&mut self) {
        println!("Dropping HasDrop {}!", self.0);
    }
}

fn main() {
    let x = HasDrop(0);
    let y = HasDrop(1);
    std::mem::drop(x); // xを手動で初期化する
    println!("hello");
}

swap: 内容を入れ替える

fn main() {
    let mut x = 10;
    let mut y = 20;
    println!("x: {}, y: {}", x, y);
    std::mem::swap(&mut x, &mut y);
    println!("x: {}, y: {}", x, y);
}

単純な関数に見えてしまうが、この関数の存在は、ここでは書ききれないほど大きい。
まず、両方の型が同じであればどの型でも使用できる

note

pub const fn swap<T>(x: &mut T, y: &mut T) {
    // SAFETY: `&mut` guarantees these are typed readable and writable
    // as well as non-overlapping.
    unsafe { intrinsics::typed_swap_nonoverlapping(x, y) }
}

こいつはポインタを操作して入れ替える。 わざわざメモリを初期化しないので動作が早いのだ

ご意見募集中

当サイトのリポジトリにて、issue 募集中です!