Charaken 技術系ブログ

技術系に関して学んだことや、気になったことを記事にしていく。

【Rust】がばがばRust独学 - 6. Enum / Pattern Matching

f:id:charaken:20191223210559p:plain

Rustの公式ドキュメントを通して、Rustを独学していく記事になります。(がば要素あり)

doc.rust-lang.org

今回は、EnumとPattern mattingについて学びつつ、気になった点をコード確認した結果を記事にしています。

  • versions
$ rustc --version
rustc 1.40.0 (73528e339 2019-12-16)


Enum

基本的な使用方法

公式ドキュメント(doc.rust-lang.org)の例より、IPアドレスのv4, v6を考えた場合、IpAddrKind により、v4, v6のenumを定義するとともに、 IpAddr により、IPアドレスの制御をできるようなStructを定義することが可能です。debug出力をさせるために、 #[derive(Debug)]enumstruct の両方につけています。

#[derive(Debug)]
enum IpAddrKind {
    V4,
    V6,
}

#[derive(Debug)]
struct IpAddr {
    kind: IpAddrKind,
    address: String,
}

fn main() {
    let home = IpAddr {
        kind: IpAddrKind::V4,
        address: String::from("127.0.0.1"),
    };

    let loopback = IpAddr {
        kind: IpAddrKind::V6,
        address: String::from("::1"),
    };

    println!("home -> {:#?}", home);
    println!("loopback -> {:#?}", loopback);
}
home -> IpAddr {
    kind: V4,
    address: "127.0.0.1",
}
loopback -> IpAddr {
    kind: V6,
    address: "::1",
}

enum variant

enumに対して、値を持たせることも可能です。

下記のように、v4の場合はIPアドレス、v6の場合はリダイレクト先などを設定したい場合は、V4(u8, u8, u8, u8)V6(String) のように、tapleのように記述し、データを定義することが可能です。

#[derive(Debug)]
enum IpAddr {
    V4(u8, u8, u8, u8),
    V6(String),
}

fn main() {
    let home = IpAddr::V4(127, 0, 0, 1);
    let loopback = IpAddr::V6(String::from("::1"));

    println!("home -> {:#?}", home);
    println!("loopback -> {:#?}", loopback);
}
home -> V4(
    127,
    0,
    0,
    1,
)
loopback -> V6(
    "::1",
)

V4(u8, u8, u8, u8) はStruct Ipv4Addr が存在していた場合 V4(Ipv4Addr) のように、列挙型(enum)のvariantとして構造体をもたせることも可能です。

Option

Rustにはnullは無いです。それは、 Option により、値が有るか否かを判別することができるためです。

下記の例のように、値がある場合を Some 、ない場合は None として制御するためです。

fn main() {
    let x = Some(5);
    let s = Some("a string");
    let n: Option<i32> = None;

    println!("x -> {:#?}", x);
    println!("s -> {:#?}", s);
    println!("n -> {:#?}", n);
}
x -> Some(
    5,
)
s -> Some(
    "a string",
)
n -> None

公式ドキュメント(doc.rust-lang.org)引用の、nullの発明者であるTony Hoareの「Null References:The Billion Dollar Mistake」より、nullによる無数のエラーや脆弱性、システムクラッシュが発生していることによる損害を引き起こさないために、一般的なバグを防ぐ意味合いでOptionの利用がされている背景があります。

また、Optionは列挙型であるため、下記のように単純に計算はできません。しかしながら、 nullを許容する言語であった場合、もしx がnullであるとすると、バグになってしまいます。

fn main() {
    let x: Option<u8> = Some(5);
    let y: u8 = 3;

    let z = x + y;
}
error[E0369]: binary operation `+` cannot be applied to type `std::option::Option<u8>`
 --> demo.rs:5:12
  |
5 |     let z = x + y;
  |             - ^ - u8
  |             |
  |             std::option::Option<u8>
  |
  = note: an implementation of `std::ops::Add` might be missing for `std::option::Option<u8>

match

Option を利用した単純な加算の場合、下記のように x がSome/Noneによって計算を分けることが可能です。

fn main() {
    let x: Option<u8> = Some(5);
    let y: u8 = 3;

    let z = match x {
        None => y,
        Some(i) => i + y,
    };

    println!("z -> {}", z);
}
z -> 8

match の網羅性

先程の match から None を除外した場合、下記のようにコンパイルエラーとなり、事前にエラーを防ぐことが可能です。

fn main() {
    let x: Option<u8> = Some(5);
    let y: u8 = 3;

    let z = match x {
        Some(i) => i + y,
    };

    println!("z -> {}", z);
}
error[E0004]: non-exhaustive patterns: `None` not covered
 --> demo.rs:5:16
  |
5 |     let z = match x {
  |                   ^ pattern `None` not covered
  |
  = help: ensure that all possible cases are being handled, possibly by adding wildcards or more match arms

_ プレースホルダ

下記のように、Some であれば対象の処理をするのような可能です。

fn main() {
    let x: Option<u8> = Some(5);
    let y: u8 = 3;

    let z = match x {
        None => y,
        Some(_) => 100 + y,
    };

    println!("z -> {}", z);
}
z -> 103

また、公式ドキュメント(The match Control Flow Operator - The Rust Programming Language)に記載されているデモコード(少し改変しています)のように、caseではないものの判別としても利用可能です。

fn main() {
    let some_u8_value = 1u8;
    match some_u8_value {
        1 => println!("one"),
        3 => println!("three"),
        5 => println!("five"),
        7 => println!("seven"),
        _ => (),
    }
}
one

if let

公式ドキュメント(Concise Control Flow with if let - The Rust Programming Language)に記載されているデモコード(少し改変しています)のように、ただ Some(3) であれば、 println!("three") を処理するという式であった場合、冗長になってしまいます。

fn main() {
    let some_u8_value = Some(3u8);
    match some_u8_value {
        Some(3) => println!("three"),
        _ => (),
    }
}
three

そのため、 if let を利用することにより、網羅性を無くし、冗長的では無くすことが可能です。

fn main() {
    let some_u8_value = Some(3u8);
    if let Some(3) = some_u8_value {
        println!("three");
    }
}
three

単純に if 制御であるため、 else else if も利用可能です。


最後に

enummatchScala本当にお世話になった存在です。DBで取ってきた値やweb APIから取ってきた値に対して、null許容のデータに対して処理させたりなど、有効活用ができるとても便利で堅牢性が高くなる構文です。とてもありがたい・・・

次はPackages, Crates, Modules, Paths辺りの理解を深めていきます。