17
9

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Rust勉強中 - その16 -> 列挙とパターン

Last updated at Posted at 2019-10-17

自己紹介

出田 守と申します。
しがないPythonプログラマです。
情報セキュリティに興味があり現在勉強中です。CTFやバグバウンティなどで腕を磨いています。主に低レイヤの技術が好きで、そっちばかり目が行きがちです。

Rustを勉強していくうえで、読んで学び、手を動かし、記録し、楽しく学んでいけたらと思います。

環境

新しい言語を学ぶということで、普段使わないWindowsとVimという新しい開発環境で行っています。
OS: Windows10 Home 64bit 1903
CPU: Intel Core i5-3470 @ 3.20GHz
Rust: 1.38.0
RAM: 8.00GB
Editor: Vim 8.1.1
Terminal: PowerShell

前回

前回は構造体について学びました。
Rust勉強中 - その15

列挙型

単純な列挙型は以下のように書きます。

enum Enum {
    Variant1,
    Variant2,
    ...
}

列挙型に列挙した値をvariant(ヴァリアント)といいます。構造体は、値のすべてで構成されているのに対して、列挙型はこのヴァリアントのいずれかの値をとります。つまり、私の理解ですが、列挙型とは値に対して分かりやすい名前を付けた型だと思っています。(間違っていたらすみません)
以下に列挙型の例を載せます。

enum Binary {
    Black,
    White
}
fn binary(a: u8) -> Binary {
    if a<128 {
        Binary::Black
    } else {
        Binary::White
    }
}

fn main() {
    println!("{:?}", binary(25));  // Black
    println!("{:?}", binary(225)); // White
}

このような列挙型はメモリ上で整数として表現されるようです。この整数を指定することができます。もし、指定しない場合、0から順番に割り当てられます。

enum GoodsId {
    Cup      = 10000,
    Mouse    = 218,
    Bolt     = 508
}

上記のような単純な列挙型は、整数型へキャストできます。

fn main() {
    println!("{}",   GoodsId::Cup as usize);     // 10000
} 

また、その逆へのキャストはasではなく、match式を用いて行います。

fn cast_goods_from_id(id: usize) -> Option<GoodsId> {
    match id {
        10000 => Some(GoodsId::Cup),
        218   => Some(GoodsId::Mouse),
        508   => Some(GoodsId::Bolt),
        _     => None
    }
}

fn main() {
    ...
    println!("{:?}", cast_goods_from_id(10000)); // Some(Cup)
    println!("{:?}", cast_goods_from_id(218));   // Some(Mouse)
    println!("{:?}", cast_goods_from_id(508));   // Some(Bolt)
    println!("{:?}", cast_goods_from_id(0));     // None
    println!("{:?}", cast_goods_from_id(10000).unwrap() as usize); // 10000
}

ここまでの列挙型をprintln!で表示していますが、実は#[derive]属性を付けて標準的なトレイトを実装しています。

#[derive(Debug)]
enum Binary {
    Black,
    White
}
...
#[derive(Debug)]
enum GoodsId {
    Cup      = 10000,
    Mouse    = 218,
    Bolt     = 508
}
impl GoodsId {
    pub fn price(self) -> Option<usize> {
        match self {
            GoodsId::Cup   => Some(1280),
            GoodsId::Mouse => Some(2800),
            GoodsId::Bolt  => Some(99999),
        }
    }
}

fn main() {
    ...
    println!("{}",   GoodsId::Cup.price().unwrap());   // 1280
    println!("{}",   GoodsId::Mouse.price().unwrap()); // 2800 
    println!("{}",   GoodsId::Bolt.price().unwrap());  // 99999 
}

さらに、構造体と同じように、implブロックでメソッドを定義できます。

Rustの列挙型では3種類のデータ(タプル型、構造体型、ユニット型)を持つvariantを定義できます。あまり良い例ではないかもしれませんが、以下のような感じで定義していきます。

タプル型

#[derive(Debug)]
enum Room {
    ARoom(String, u16),
    BRoom(String, u16)
}

fn main {
    ...
    let a_101 = Room::ARoom(String::from("A"), 101);
    let b_302 = Room::BRoom(String::from("B"), 302);
    println!("{:?}", a_101); // ARoom("A", 101);
    println!("{:?}", b_302); // BRoom("B", 302);
}

構造体型

#[derive(Debug)]
enum Operate {
    Swap {a: String, b: String},
    Move {from: u8, to: u8},
    Open {path: String}
}

fn main() {
    ...
    let swap_string   = Operate::Swap {a: String::from("Rust"), b: String::from("Python")};
    let move_zero     = Operate::Move {from: 0, to: 100};
    let open_tmp_file = Operate::Open {path: String::from("tmp.txt")};
    println!("{:?}", swap_string);   // Swap { a: "Rust", b: Python }
    println!("{:?}", move_zero);     // Move { from: 0, to: 100 }
    println!("{:?}", open_tmp_file); // Open { path: "tmp.txt" }
    // println!("{}", swap_string.a); // Error: no field 'a' on type 'Operate'
}

ジェネリック列挙型

列挙型でもジェネリックにできます。

#[derive(Debug)]
enum GenericEnum<T> {
    Variant(T)
}

このとき、型TをBoxなどのスマートポインタにするとGenericEnum分のタグフィールドが削除されるそうです。

fn main() {
    use std::mem::size_of;
    println!("{}=={}", size_of::<Box<i32>>(), size_of::<GenericEnum<Box<i32>>>()); // 8==8
}

パターンとパターンマッチ

列挙型のフィールドに直接アクセスしようとするとエラーを吐きます。

#[derive(Debug)]
enum Level {
    Stay,
    LevelUp   {n: i32},
    LevelDown {n: i32},
    MyLevel   (i32)
}

fn main() {
    let my_level   = Level::MyLevel (5);

    println!("my_level = {:?}", my_level.0); // no field `0` on type `Level`

こういった場合はmatch式を用いたパターンマッチをして、フィールドにアクセスします。match式は以下のようなフォーマットになります。

match value {
    pattern => expr,
    ...
}

valueがpatternにマッチした場合、exprを実行します。

...
impl Level {
    fn get_level(&self) -> Option<i32> {
        match *self {
            Level::Stay => None,
            Level::LevelUp {n: up} => Some(up),
            Level::LevelDown {n: down} => Some(down),
            Level::MyLevel(my_level) => Some(my_level)
        }
    }
}

fn main() {
    let stay       = Level::Stay;
    let level_up   = Level::LevelUp {n: 1};
    let level_down = Level::LevelDown {n: -1};
    let my_level   = Level::MyLevel (5);

    // println!("my_level = {:?}", my_level.0); // no field `0` on type `Level`
    println!("my_level   = {:?}", my_level.get_level());   // Some(5)
    println!("level_down = {:?}", level_down.get_level()); // Some(1)
    println!("level_up   = {:?}", level_up.get_level());   // Some(-1)
    println!("stay       = {:?}", stay.get_level());       // None

このほか、パターンの書き方がいくつかあります。

リテラル

これまでも何回か見ましたが、リテラルにマッチさせる書き方は以下です。

fn main() {
    ...
    // match: literals
    let code = 100;
    match code {
        0 => println!("success!"),
        1 => println!("error!"),
        n => println!("code:{} is unknown...", n)
    }
}

パターンのnはcodeが任意の値をとった場合にマッチします。
マッチした値は、コピー可能な値ならローカル変数(この場合変数n)にコピーされます。コピー不可であれば、所有権を移動します。

ワイルドカード

マッチした値を無視したい場合に、ワイルドカード(_)が使えます。

fn main() {
    ...
    // match: wildcard
    let goods = "key";
    match goods {
        "keyboard" => println!("On the 5th floor"),
        "keypad"   => println!("On the 2nd floor"),
        "key"      => println!("On the 99th floor"),
        _          => println!("Please go out")
    }
}

_は、変数を指定した場合と似ていますが、マッチした値を捨てることができます。

タプル

対象がタプルの場合の書き方です。

fn main() {
    ...
    // match: tuple
    let password = ("ticktack", "years");
    match password {
        ("ticktack", "days")  => println!("who?"),
        (_, "months")         => println!("he?"),
        ("ticktick", "years") => println!("her?"),
        (_, _)                => println!("Accept! (weak! weak! weak!)")
    }
}

構造体

構造体をマッチさせる場合、3種類の書き方があります。
素直に書くと以下のようになります。

fn main() {
    ...
    // match: struct
    struct Dim {
        id: i32,
        x: i32,
        y: i32,
        z: i32
    }
    let d = Dim {id: 0, x: 0, y: 0, z: 0};
    match d {
         Dim {id: id, x: 0, y: 0, z: 0} => println!("id={}", id),
        _ => println!("Unknown...")

    }
}

パターンには{フィールド名: 値}を、淡々と書きます。
もし、フィールドが多いと大変なので、省略する書き方ができます。

fn main() {
    ...
    // match: struct
    struct Dim {
        id: i32,
        x: i32,
        y: i32,
        z: i32
    }
    let d = Dim {id: 0, x: 0, y: 0, z: 0};
    match d {
        Dim {id, x, y, z} => println!("id={}", id),
        _ => println!("Unknown...")
    }
}

さらに、上記の場合はidのみほしいので、それ以降のフィールドを..を使って省略することもできます。

fn main() {
    ...
    // match: struct
    struct Dim {
        id: i32,
        x: i32,
        y: i32,
        z: i32
    }
    let d = Dim {id: 0, x: 0, y: 0, z: 0};
    match d {
        Dim {id, ..} => println!("id={}", id),
        _ => println!("Unknown...")
    }
}

参照

先ほどマッチしたコピー不可の値はローカル変数に所有権を移動させると書きました。以下はString型をマッチさせる例です。

fn main() {
    ...
    // match: reference
    let mut string = String::from("String!");
    match string {
        s => {
            println!("s={}", s);
            println!("string.len()={}", string.len());
        }
    }
}
error[E0382]: borrow of moved value: `string`
  --> .\src\main.rs:74:41
   |
70 |     let mut string = String::from("String!");
   |         ---------- move occurs because `string` has type `std::string::String`, which does not implement the `Copy
 trait
71 |     match string {
72 |         s => {
   |         - value moved here
73 |             println!("s={}", s);
74 |             println!("string.len()={}", string.len());
   |                                         ^^^^^^ value borrowed here after move

error: aborting due to previous error

この場合、変数sに所有権を移動させるので変数stringがドロップされます。そのドロップされた変数stringにアクセスしようとするのでエラーを吐きます。
このような場合は、値を借用すれば解決します。借用したい場合は、refを使います。

fn main() {
    ...
    // match: reference
    let mut string = String::from("String!");
    match string {
        ref s => {
            println!("s={}", s);
            println!("string.len()={}", string.len());
        }
    }
}

話が逸れるのですが、Stringをマッチさせたい場合は以下のような書き方もできます。

fn main() {
    ...
    match &*string {
        "String!" => println!("String!"),
        _         => println!("Unknown...")
    }
}

これは、stringを参照解決して、strにし、その借用&strにして、マッチさせます。このようにしないと、String型と&str型とのパターンマッチとなり型が異なるためエラーを吐きます。
このほかに、後述するガードを使うことでもString型をマッチさせることができます。

話を戻して、可変参照にしたい場合は以下のようにします。

fn main() {
    ...
    match string {
        ref mut s => {
            println!("s={}", s);
            println!("s.make_ascii_uppercase={}", {s.make_ascii_uppercase(); s});
        }
    }
}

このようにすれば、変数sに対して変更を加えることができます。

参照自体をマッチさせたい場合は&演算子を使います。

fn main() {
    ...
    let x = &0;
    match x {
        &0 => println!("zero"),
        &1 => println!("one"),
        _ => println!("Unknown...")
    }
}

or

|を使えば、orのようにパターンを並べられます。

fn main() {
    ...
    let password = "password!";
    match password {
        "password!"|"p@ssw0rd!"|"PASSWORD!" => println!("Accept!"),
        _ => println!("who?")
    }
}

範囲

..=を使えば範囲を指定できます。この場合、..と違って、終点の値が含まれます。

fn main() {
    ...
    let z = 'z';
    match z {
        '0'..='9' => println!("digit"),
        'a'..='z' => println!("char"),
        _ => println!("?")
    }
}

ガード

ガードはpattern if ~でif文がtrueの場合だけpatternにマッチします。値が移動する場合、ガードを付けることができません。この場合は、refを使って借用することで解決します。

fn main() {
    ...
    // match: guard
    let string = String::from("String!");
    match string {
        ref s if s=="String" => println!("s={}", s),
        ref s if s=="String!" => println!("s={}", s),
        _ => println!("Unknown...")
    }
}

@

@パターンを使うことでパターンにマッチした場合のみ、ローカル変数に値を格納します。

fn main() {
    ...
    // match: @
    let digit = 10;
    match digit {
        d @ 0..=9 => println!("{} is ok!", d),
        d @ 10..=100 => println!("{} is too much!", d),
        _ => println!("who?")
    }
}

ソース

enums
#[allow(unused)]
fn get_type<T>(_: T) -> &'static str {
    std::any::type_name::<T>()
}

#[derive(Debug)]
enum Binary {
    Black,
    White
}
fn binary(a: u8) -> Binary {
    if a<128 {
        Binary::Black
    } else {
        Binary::White
    }
}

#[derive(Debug)]
enum GoodsId {
    Cup      = 10000,
    Mouse    = 218,
    Bolt     = 508
}
fn cast_goods_from_id(id: usize) -> Option<GoodsId> {
    match id {
        10000 => Some(GoodsId::Cup),
        218   => Some(GoodsId::Mouse),
        508   => Some(GoodsId::Bolt),
        _     => None
    }
}
impl GoodsId {
    pub fn price(self) -> Option<usize> {
        match self {
            GoodsId::Cup   => Some(1280),
            GoodsId::Mouse => Some(2800),
            GoodsId::Bolt  => Some(99999),
        }
    }
}

#[derive(Debug)]
enum Room {
    ARoom(String, u16),
    BRoom(String, u16)
}

#[derive(Debug)]
enum Operate {
    Swap {a: String, b: String},
    Move {from: u8, to: u8},
    Open {path: String}
}

#[derive(Debug)]
enum GenericEnum<T> {
    Variant(T)
}

fn main() {
    println!("{:?}", binary(25));  // Black
    println!("{:?}", binary(225)); // White

    println!("{}",   GoodsId::Cup as usize);     // 10000
    println!("{:?}", cast_goods_from_id(10000)); // Some(Cup)
    println!("{:?}", cast_goods_from_id(218));   // Some(Mouse)
    println!("{:?}", cast_goods_from_id(508));   // Some(Bolt)
    println!("{:?}", cast_goods_from_id(0));     // None
    println!("{:?}", cast_goods_from_id(10000).unwrap() as usize); // 10000

    println!("{}",   GoodsId::Cup.price().unwrap());   // 1280
    println!("{}",   GoodsId::Mouse.price().unwrap()); // 2800 
    println!("{}",   GoodsId::Bolt.price().unwrap());  // 99999 

    let a_101 = Room::ARoom(String::from("A"), 101);
    let b_302 = Room::BRoom(String::from("B"), 302);
    println!("{:?}", a_101); // ARoom("A", 101);
    println!("{:?}", b_302); // BRoom("B", 302);

    let swap_string   = Operate::Swap {a: String::from("Rust"), b: String::from("Python")};
    let move_zero     = Operate::Move {from: 0, to: 100};
    let open_tmp_file = Operate::Open {path: String::from("tmp.txt")};
    println!("{:?}", swap_string);   // Swap { a: "Rust", b: Python }
    println!("{:?}", move_zero);     // Move { from: 0, to: 100 }
    println!("{:?}", open_tmp_file); // Open { path: "tmp.txt" }
    // println!("{}", swap_string.a); // Error: no field 'a' on type 'Operate'

    use std::mem::size_of;
    println!("{}=={}", size_of::<Box<i32>>(), size_of::<GenericEnum<Box<i32>>>()); // 8==8
}
Black
White
10000
Some(Cup)
Some(Mouse)
Some(Bolt)
None
10000
1280
2800
99999
ARoom("A", 101)
BRoom("B", 302)
Swap { a: "Rust", b: "Python" }
Move { from: 0, to: 100 }
Open { path: "tmp.txt" }
8==8
pattern
#[derive(Debug)]
enum Level {
    Stay,
    LevelUp   {n: i32},
    LevelDown {n: i32},
    MyLevel   (i32)
}
impl Level {
    fn get_level(&self) -> Option<i32> {
        match *self {
            Level::Stay => None,
            Level::LevelUp {n: up} => Some(up),
            Level::LevelDown {n: down} => Some(down),
            Level::MyLevel(my_level) => Some(my_level)
        }
    }
}

fn main() {
    let stay       = Level::Stay;
    let level_up   = Level::LevelUp {n: 1};
    let level_down = Level::LevelDown {n: -1};
    let my_level   = Level::MyLevel (5);

    // println!("my_level = {:?}", my_level.0); // no field `0` on type `Level`
    println!("my_level   = {:?}", my_level.get_level());   // Some(5)
    println!("level_down = {:?}", level_down.get_level()); // Some(1)
    println!("level_up   = {:?}", level_up.get_level());   // Some(-1)
    println!("stay       = {:?}", stay.get_level());       // None

    // match: literals
    let code = 100;
    match code {
        0 => println!("success!"),
        1 => println!("error!"),
        n => println!("code:{} is unknown...", n)
    }
    // match: wildcard
    let goods = "key";
    match goods {
        "keyboard" => println!("On the 5th floor"),
        "keypad"   => println!("On the 2nd floor"),
        "key"      => println!("On the 99th floor"),
        _          => println!("Please go out")
    }
    // match: tuple
    let password = ("ticktack", "years");
    match password {
        ("ticktack", "days")  => println!("who?"),
        (_, "months")         => println!("he?"),
        ("ticktick", "years") => println!("her?"),
        (_, _)                => println!("Accept! (weak! weak! weak!)")
    }
    // match: struct
    struct Dim {
        id: i32,
        x: i32,
        y: i32,
        z: i32
    }
    let d = Dim {id: 0, x: 0, y: 0, z: 0};
    match d {
        // Dim {id: id, x: 0, y: 0, z: 0} => println!("id={}", id),
        Dim {id, x, y, z} => println!("id={}", id),
        // Dim {id, ..} => println!("id={}", id),
        _ => println!("Unknown...")

    }
    // match: reference
    let mut string = String::from("String!");
    match string {
        ref s => {
            println!("s={}", s);
            println!("string.len()={}", string.len());
        }
    }
    match &*string {
        "String!" => println!("String!"),// Error: mismatched types
        _         => println!("Unknown...")
    }
    match string {
        ref mut s => {
            println!("s={}", s);
            println!("s.make_ascii_uppercase={}", {s.make_ascii_uppercase(); s});
        }
    }
    println!("string = {}", string);

    let x = &0;
    match x {
        &0 => println!("zero"),
        &1 => println!("one"),
        _ => println!("Unknown...")
    }

    // match: range
    let password = "password!";
    match password {
        "password!"|"p@ssw0rd!"|"PASSWORD!" => println!("Accept!"),
        _ => println!("who?")
    }
    let z = 'z';
    match z {
        '0'..='9' => println!("digit"),
        'a'..='z' => println!("char"),
        _ => println!("?")
    }

    // match: guard
    let string = String::from("String!");
    match string {
        ref s if s=="String" => println!("s={}", s),
        ref s if s=="String!" => println!("s={}", s),
        _ => println!("Unknown...")
    }

    // match: @
    let digit = 10;
    match digit {
        d @ 0..=9 => println!("{} is ok!", d),
        d @ 10..=100 => println!("{} is too much!", d),
        _ => println!("who?")
    }
}
my_level   = Some(5)
level_down = Some(-1)
level_up   = Some(1)
stay       = None
code:100 is unknown...
On the 99th floor
Accept! (weak! weak! weak!)
id=0
s=String!
string.len()=7
String!
s=String!
s.make_ascii_uppercase=STRING!
string = STRING!
zero
Accept!
char
s=String!
10 is too much!

今回はここまでー。
コード例を考えるのに時間が取られるんです。しかも、ピンとくる例でもないんです。大変なんです。

17
9
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
17
9

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?