LoginSignup
0
0

The Rust Programming Languageを読み進める(6章)

Posted at

はじめに

The Rust Programming Languageを頭から読み進めてみる。読みながら要点をまとめていった記事です。

6章:Enumとパターンマッチング

6.1章:Enumを定義する

Enumの基本的な使い方

// enum
#[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);
    println!("{:?}", loopback);
}
IpAddr { kind: V4, address: "127.0.0.1" }
IpAddr { kind: V6, address: "::1" }
  • よくある形のenum定義
  • 他の言語だと列挙型の名前.値という書き方を見ることもあるが、Rustの場合は列挙型の名前::値でアクセス可能

Enumに値を直接定義

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

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

fn main() {
    let home = IpAddrDirect::V4(String::from("127.0.0.1"));
    let loopback = IpAddrDirect::V6(String::from("::1"));

    println!("{:?}", home);
    println!("{:?}", loopback);

    let home = IpAddrDiff::V4(127, 0, 0, 1);
    let loopback = IpAddrDiff::V6(String::from("::1"));

    println!("{:?}", home);
    println!("{:?}", loopback);
}
V4("127.0.0.1")
V6("::1")
V4(127, 0, 0, 1)
V6("::1")
  • 列挙子に直接データを定義することが可能
    • 例:V4(String)に対して、IpAddrDirect::V4(String::from("..."))
  • IpAddrDirectは、各列挙子に直接データを定義している例で、余計に構造体を作らなくても良くなっている
  • また、各列挙子に紐付けるデータの型は異なっていても良くIpAddrDiffのように異なる型の値を定義することが可能

Enumに構造体を定義

#[derive(Debug)]
enum IpAddr {
    V4(Ipv4Addr),
    V6(Ipv6Addr),
}

#[derive(Debug)]
struct Ipv4Addr {
    address: String,
    maxbit: i32,
}

#[derive(Debug)]
struct Ipv6Addr {
    address: String,
    maxbit: i32,
}

fn main() {
    let ipv4 = IpAddr::V4(Ipv4Addr {
        address: String::from("127.0.0.1"),
        maxbit: 32,
    });

    let ipv6 = IpAddr::V6(Ipv6Addr {
        address: String::from("2001:0000:130F:0000:0000:09C0:876A:130B"),
        maxbit: 128,
    });
    println!("{:?}", ipv4);
    println!("{:?}", ipv6);
}
V4(Ipv4Addr { address: "127.0.0.1", maxbit: 32 })
V6(Ipv6Addr { address: "2001:0000:130F:0000:0000:09C0:876A:130B", maxbit: 128 })
  • enumの列挙子に数字や文字を格納できる例を1つ前に示したが、構造体も格納することが可能

enumとstructの比較

enum Message {
    Quit,
    Move { x: i32, y: i32 },
    Write(String),
    ChangeColor(i32, i32, i32),
}
struct QuitMessage; // ユニット構造体
struct MoveMessage {
    x: i32,
    y: i32,
}
struct WriteMessage(String); // タプル構造体
struct ChangeColorMessage(i32, i32, i32); // タプル構造体
  • 上記2つは、enum or structで4つの機能を定義した例
  • enumを使用することで下記のメリットがあり
    • 型安全:各データ型を1つの型 (Message) として扱える
    • 簡素化:structと比べて関数の重複を避けられる
    • 拡張性:新しい定義を追加する際は、新しいバリアントを追加するだけで済む

implを使ったメソッドの定義

#[derive(Debug)]
enum Message {
    Quit,
    Move { x: i32, y: i32 },
    Write(String),
    ChangeColor(i32, i32, i32),
}

impl Message {
    fn call(&self) {
        println!("{:?}", &self);
    }
}

fn main() {
    let m = Message::Write(String::from("hello"));
    let n = Message::Move{x: 10, y: 20};

    m.call();
    n.call();
}
Write("hello")
Move { x: 10, y: 20 }
  • 構造体と同様に、enumでもimplを使ってメソッドを定義可能
  • Messageの列挙子はそれぞれ型が異なるが、implの引数に&selfが取れるため、どのような値に対しても柔軟にprintln!を呼ぶことができている

Option型

fn is_even(number: &i32) -> Option<i32> {
    if number % 2 == 0 {
        return Some(*number);
    }
    None
}

fn main() {
    let number = 10;
    let result = is_even(&number);

    match result {
        Some(even) => println!("{} is even number", even),
        None => println!("even number not found"),
    }
}
10 is even number
  • Optionは、値が存在するかどうかを表現するenum
  • Rustではnullが存在しないため、値が存在しないものに対してはOptionを使用することが慣習

6.2章:match制御フロー演算子

match制御によるenum分岐

enum Coin {
    Penny,
    Nickel,
    Dime,
    Quarter,
}

fn value_in_cents(coin: Coin) -> u32 {
    match coin {
        Coin::Penny => {
            println!("Lucky penny!");
            1
        },
        Coin::Nickel => 5,
        Coin::Dime => 10,
        Coin::Quarter => 25,
    }
}

fn main() {
    let penny = value_in_cents(Coin::Penny);
    println!("{}", penny);
}
Lucky penny!
1
  • enumの列挙子によって処理を行う場合、matchを使える
  • ifの場合だとboolで判断するのに対して、matchの場合はどのような型でも良い

パターンマッチした値に束縛

#[derive(Debug)]
enum UsState {
    Alabama,
    Alaska,
    Carifornia,
}

enum Coin {
    Penny,
    Nickel,
    Dime,
    Quarter(UsState),
}

fn value_in_cents(coin: Coin) -> u32 {
    match coin {
        Coin::Penny => 1,
        Coin::Nickel => 5,
        Coin::Dime => 10,
        Coin::Quarter(state) => {
            println!("State quarter from {:?}!", state);
            25
        }
    }
}

fn main() {
    let alaska_quarter = value_in_cents(Coin::Quarter(UsState::Alaska));
    println!("{:?}", alaska_quarter);
}
State quarter from Alaska!
25
  • Quarter(UsState)をenumに追加し、matchの中の条件式に取り込んでいる
  • この時、Coin::Quarter(state)に到達するまでマッチせず、最終的にstateUsState::Alaskaが束縛される

Optionとのマッチ

fn plus_one(x: Option<i32>) -> Option<i32> {
    match x {
        None => None,
        Some(i) => Some(i + 1),
    }
}

fn main() {
    let five = Some(5);
    println!("{:?}", five);

    let six = plus_one(five);
    println!("{:?}", six);

    let none = plus_one(None);
    println!("{:?}", none);
}
Some(5)
Some(6)
None
  • Optionとマッチさせ、Someに含まれる値を足し算して、新しいSomeの値を生成可能
  • matchとenumは相性がよいため、頻出パターン
  • matchは全てのパターンを書き下す必要があり、例えばOptionNoneに関する定義を書かない、ということはできない

_というプレースホルダー

fn main() {
    let some_u8_value = 0u8; // 0
    let result = match some_u8_value {
        1 => println!("one"),
        3 => println!("three"),
        5 => println!("five"),
        7 => println!("seven"),
        _ => (),
    };
    
    println!("{:?}", result);
}
()
  • matchで全てのパターンを列挙できないときは、_を使用
  • 1つのパターンしか無い場合にmatchは長すぎるので、その際はこの後に述べるif letを用いる

6.3章:if letによる制御フロー

#[derive(Debug)]
enum UsState {
    Alabama,
    Alaska,
    Carifornia,
}

enum Coin {
    Penny,
    Nickel,
    Dime,
    Quarter(UsState),
}

fn main() {
    let mut count = 0;
    let coin = Coin::Penny;

    // match coin {
    //     Coin::Quarter(state) => println!("State quarter from {:?}!", state),
    //     _ => count += 1,
    // }

    // 上記match式と同じの糖衣構文
    if let Coin::Quarter(state) = coin {
        println!("State quarter from {:?}!", state);
    } else {
        count += 1;
    }

    println!("count: {}", count)
}
count: 1
  • if letはパターンが1つの場合に、matchを簡略化した糖衣構文 (syntax sugar)
    • 余談:等位構文の誤字かと最初は思ったが、syntax sugar(読み書きやすく導入された構文)を糖衣構文と書く
  • デメリットとしては、matchで強制させていたパターン網羅ができない
0
0
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
0
0