2017年6月26日月曜日

The Rust Programming Language 2nd 07日目 EnumとPattern

6. Enums and Pattern Matching

enumenumerateの略で,取りうる値の集合が有限集合である変数の型である.

Defining an Enum

例えば,現在IPアドレスはIP4vかIP6vしかないので,IPアドレスを取り扱うプログラムのIPアドレスバージョンを入れる変数は

enum IpAddrKind {
  V4,
  V6,
}

のように定めたenum型を使える.

Enum Values

IpAddrKindのinstanceは

let four = IpAddrKind::V4;
let six = IpAddrKind::V6;

のようにして定義できる.IpAddrKindは一つの型だから,このinstanceを引数に取る関数を定義できる.
fn route(ip_type: IpAddrKind) {}
この関数は

route(IpAddrKind::V4);
route(IpAddrKind::V6);

のようにして呼び出せる.

enum IpAddrKind {
  V4,
  V6,
}

struct IpAddr {
  kind: IpAddrKind,
  address: String,
}

IpAddrというstructを定義するとき,

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

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

としてそのinstanceを与えられる.しかし,より簡便な方法に

enum IpAddr {
  V4(String),
  V6(String),
}

とすれば,

let home = IpAddr::V4(String::from("127.0.0.1"));
let loopback = IpAddr::V6(String::from("::01"));

によって同じ機能を実現できる.また,

struct Ipv4Addr {
  // details elided
}

struct Ipv6Addr {

}

enum IpAddr {
  V4(Ipv4Addr),
  V6(Ipv6Addr),
}

ともできる.つまり,enumの中の変数にはstruct型も入れられる.
さらに複雑な中身をもつenumの例を与える.

enum Message {
  Quit,
  Move {x: i32, y: i32},
  write(String),
  ChangeColor(i32, i32, i32),
}

これと同じ機能をstructで実現しようとすると,

struct QuitMessage;                       // unit struct
struct MoveMessage{
  x: i32,
  y: i32,
}
struct WriteMessage(String);              // tuple struct
struct ChangeColorMessage(i32, i32, i32); // tuple struct

と煩雑になるし,これらのstructのinstanceを引数にする関数を定義するのは手間がかかる.

structと同様,enumでもimplを使ってmethodを定義できる.例えば,

enum Message {
  // details elided.
}

impl Message {
  fn call(&self) {
    // method body would be defined here.
  }
}

let m = Message::Write(String::from("Hello"));
m.call();

と,Messageにmethodを定義して呼び出すことができる.

The Option Enum and Its Advantages Over Null Values

RustはNull,すなわちその変数が何でもないということを表現する機能を安全性のために実装していないが,その危険性を回避しつつ似たようなことをOptionによって行える.Option

enum Option<T> {
  Some(T),
  None,
}

と実装されている.これは標準ライブラリに入っている特別なenumで,Option::<T>Option::NoneOption<T>のinstanceである.
<T>generic type parameterで,Chap.10で論じる.ここでは,<T>は,OptionのinstanceがSomeの値を取っているなら,Someにはさらに任意の方の変数を入れられるということ.例えば

let some_number = Some(5);
let some_string = Some("a string");
let absent_number: Option<i32> = None;

として,OptionSomeは整数と文字列を保持している.
また,OptionNoneを取る場合には,Someを取った場合それが保持する変数の型を指定しなければならない.
Option<T><T>の型は異なるので,直接演算できない.例えば

let x: i8 = 5;
let y: Option<i8> = Some(5);
let sum = x + y;              // xとyは違う型

は異なる型の変数を足そうとしているからコンパイルエラーが生じる.演算をするにはOption<T>Tに変換しなければならず,したがって『Nullでないと考えて操作を行ったが,実はNullであった』というありがちなバグを取り除くことができる.
enumは取る値それぞれについて行われる処理を書くべきで,それにはmatch構文がよく使われる.

The match Control Flow Operator

matchは条件分岐の構文のひとつ.
match 変数名 { パターン1 => expression1, ..., }と定義する.{}の中を,armという.
例えば

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

fn value_in_cents(coin: Coin) -> i32 {
  match coin {
    Coin::Penny => 1,
    Coin::Nickel => 5,
    Coin::Dime => 10,
    Coin::Quarter => 25,
  }
}

matchによって複数行の処理を行いたいときは{}によってその処理を囲む.そのとき,最終行のexpressionが最終的な返り値となる.

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

Patterns that Binds to Values

matchはまた,enumの値の一部をその後の処理の中で利用できる.

#[derive(Debug)]
enum UsState{
  Alabama,
  Alaska,
  // ... etc.
}

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

fn value_in_cents (coin: Coin) -> i32 {           // coinの型はenum
  match coin {
    Coin::Penny => 1,
    Coin::Nickel => 5,
    Coin::Dime => 10,
    Coin::Quarter(state) => {
      println!("State quarter from {:?}", state); // stateの型はUsState::hoge
      25
    }
  }
}

Matching with Option<T>

上の性質から,matchによってOption<T>からTを取り出せる.Option<i32>を扱う関数を定義してみよう.

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

let five = Some(5);
let six = plus_one(five);       // iはSomeの中の値に結びつき,したがってi=5,
let none = plus_one(None);      // None => Noneから,none = None

Matches Are Exhaustive

さらにmatchは変数の取りうる値それぞれにマッチした場合の処理を明示しなければコンパイルエラーが生じる._Placeholderを使うと,パターンを網羅させる処理が簡単に書ける._は最後のarmに書き,それ以前のすべての場合にマッチが起きなかったときにマッチする.例えば

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

によって,some_u8_valueが1,3,5,7のどれでもなかった場合は何もしないという処理を実現している.
matchは実現したい分岐が少ない場合にはいちいち書くのは冗長なので,そういうときはif let構文を使う.

Concise Control Flow with if let

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

のかわりに

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

でも同じ処理を実現できる.
if letは変数の取りうる値すべてを網羅する必要はないが,そのぶんバグが起きやすくなる.
if letにはelse節を加えられる._と同じ働きをする.たとえば

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

let mut count = 0;
if let Coin::Quarter(state) = coin {
  println!("State quarter from {:?}", state);
} else {
  count += 1;
}

と同じ働きをする.

0 件のコメント:

コメントを投稿