はじめに
The Rust Programming Languageを頭から読み進めてみる。読みながら要点をまとめていった記事です。
- The Rust Programming Languageを読み進める(1〜2章)
- The Rust Programming Languageを読み進める(3章)
- The Rust Programming Languageを読み進める(4章)
- The Rust Programming Languageを読み進める(5章)
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と比べて関数の重複を避けられる
- 拡張性:新しい定義を追加する際は、新しいバリアントを追加するだけで済む
- 型安全:各データ型を1つの型 (
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)
に到達するまでマッチせず、最終的にstate
にUsState::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
は全てのパターンを書き下す必要があり、例えばOption
でNone
に関する定義を書かない、ということはできない
_というプレースホルダー
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
で強制させていたパターン網羅ができない