6. Enums and Pattern Matching
enumはenumerateの略で,取りうる値の集合が有限集合である変数の型である.
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::None
はOption<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;
として,Option
のSome
は整数と文字列を保持している.
また,Option
がNone
を取る場合には,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 件のコメント:
コメントを投稿