目次
1. 列挙の定義
列挙型 (列挙型)、 列挙型とも呼ばれます。列挙を使用すると、考えられる メンバー (バリアント) を列挙して定義できます。タイプ。まず、列挙を定義して使用し、データとともに情報をエンコードする方法を示します。次に、 Option
と呼ばれる特に便利な列挙型を調べます。これは、何かまたは何もない値を表します。次に、 match
式でパターン マッチングを使用して、さまざまな列挙値に対して実行される対応するコードを作成する方法について説明します。最後に、コード内で列挙を処理するための別の簡潔で便利な構造である if let
を紹介します。
次の例を見てみましょう。
#[derive(Debug)]
enum Sex {
Man,
Woman,
}
fn main() {
let var = Sex::Man;
println!("value is {:?}", var)
}
上記のコード例から、性別を列挙できます。列挙型の特定の値を参照する場合、列挙名の後にコロンを追加することで、列挙内の特定の属性値を参照できます。
次の例では、メンバーが複数の型を持つことができる列挙を使用できます。
enum op {
name(String),
time(i32),
People { name: String, age: i32 },
}
値が関連付けられた列挙型は、列挙型で struct
キーワードが使用されず、そのすべてのメンバーが結合される点を除いて、異なるタイプの複数の構造を定義するのとよく似ています。
構造体と列挙型にはもう 1 つの類似点があります。たとえば、メソッドの定義と同様に
impl
を使用できます。構造体に対して、列挙型に対してメソッドを定義することもできます。
enum Op {
Name(String),
Time(i32),
People { name: String, age: i32 },
}
impl Op {
fn say(&self) {}
}
標準ライブラリにある別の非常に一般的で便利な列挙型を見てみましょう:Option
。
1.1 オプションの列挙と null 値に対するその利点
このパートでは、 Option
のケースを分析します。 Option
は、標準ライブラリによって定義された別の列挙です。 Option
型は、値に値があるか値がないという非常に一般的なシナリオをエンコードするため、広く使用されています。
たとえば、空ではないリストの最初の項目をリクエストした場合は値を取得しますが、空のリストをリクエストした場合は何も取得しません。この概念を型システムの観点から表現すると、他のプログラミング言語でよくあるバグを回避できるように、処理すべきすべてのケースが処理されているかどうかをコンパイラーがチェックする必要があることを意味します。
プログラミング言語は、どの機能が含まれるかを考慮して設計されることがよくありますが、どの機能が除外されるかを考慮することも重要です。 Rustには他の言語にあるようなnull値機能があまりありません。 Null 値 (Null ) は、値がないことを表す値です。 null 値を持つ言語では、変数は常に null と null 以外の 2 つの状態のいずれかを持ちます。
ただし、null が伝えようとしている概念には依然として意味があります。つまり、null 値は、現在無効であるか、何らかの理由で欠落している値です。
問題は概念ではなく、具体的な実装です。このため、Rust には null 値はありませんが、存在または不在の概念をエンコードできる列挙型はあります。この列挙は Option<T>
であり、 は標準ライブラリ で次のように定義されています。
fn main() {
enum Option<T> {
None,
Some(T),
}
}
Option<T>
は依然として通常の列挙であり、 Some(T)
と None
は引き続き Option<T>
のメンバーです。 <T>
構文は、まだ取り上げていない Rust の機能です。これはジェネリック型パラメータであるため、知っておく必要があるのは、 <T>
が列挙型の メンバーが実行できる Option
ことを意味するということだけです。任意のタイプのデータが含まれており、各 位置に特定のタイプが使用されるため、 全体が異なるタイプになります。以下に、数値と文字列の 値を含む例をいくつか示します。Some
T
Option<T>
Option
enum Option<T> {
None,
Some(T),
}
let some_number = Some(5000);
let some_char = Some('e');
let some_boolean = Some(true);
もう一度次の例を見てみましょうが、追加する次の 2 つの値を定義するとどうなるでしょうか。
fn main() {
enum Option<T> {
None,
Some(T),
}
let some_number: i8 = 5;
let absent_number: Option<i8> = Some(5);
let plus = some_number + absent_number;
}
実行結果は次のとおりです。
ここには 2 つの重大な問題があります。
最初の問題は、let missing_number です: Option<i8> = Some(5); ここで値を割り当てるとエラーが報告されます。これら 2 つの型名は似ていますが、実際には異なる型であり、割り当てることはできません。
2 つ目は、異なる型を追加する場合です。Rust で i8
のような型の値がある場合、コンパイラは、その値が常に有効な値であることを確認します。 null チェックを行わずに安心して使用できます。 Option<i8>
(または使用される任意の型) を使用する場合にのみ、値が存在しない可能性を考慮する必要があり、コンパイラーは値を使用する前に null の場合を処理することを確認します。
2. マッチ制御フロー構造
Rust には、 match
と呼ばれる非常に強力な制御フロー オペレーターがあり、値を一連のパターンと比較し、一致するパターンに基づいて対応するコードを実行できます。パターンは、リテラル、変数、ワイルドカード、その他多くのもので構成できます。
match の役割をより明確に理解するために、次の例を見てみましょう。
fn main() {
enum Coin {
Penny,
Nickel,
Dime,
Quarter,
}
fn value_in_cents(coin: Coin) -> u8 {
match coin {
Coin::Penny => 1,
Coin::Nickel => 5,
Coin::Dime => 10,
Coin::Quarter => 25,
}
}
let res = value_in_cents(Coin::Nickel);
print!("result {}", res) // result 5
}
match の関数は他の言語 (JavaScript など) の switch に似ています。上記のコードでは、メソッドは列挙型を受け取り、match は列挙型の異なるメンバーに従って異なる値を返します。異なるブランチです。条件を満たすブランチのみが最後に返されます。特定のブランチが一致し、他のロジックを実行したい場合は、中括弧のペアを追加し、その中に対応するロジックを記述することができます。
fn value_in_cents(coin: Coin) -> u8 {
match coin {
Coin::Penny => {
print!("res: 执行到这了");
1
}
Coin::Nickel => 5,
Coin::Dime => 10,
Coin::Quarter => 25,
}
}
2.1 マッチオプション<T>
Option<i32>
を取得する関数を以下に記述し、値が含まれている場合は値を追加します。値が存在しない場合、関数は None
値を返す必要があります。
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);
let none = plus_one(None);
println!("{:?} {:?} {:?}", five, six, none) // Some(5) Some(6) None
2.2 徹底的なマッチング
match
議論すべきもう 1 つの側面があります。これらのブランチはすべての可能性をカバーする必要があります。そうしないとコンパイルできません。
fn plus_one(x: Option<i32>) -> Option<i32> {
match x {
Some(i) => Some(i + 1),
}
}
上記のエラー メッセージによると、Rust での一致マッチングは徹底的でなければならず、そうしないとコンパイルが成功しないことがわかります。
2.3 ワイルドカード パターンと _ プレースホルダー
次の例を見てみましょう。
fn main() {
let dice_roll = 9;
match dice_roll {
3 => 3,
7 => 7,
hello => 9,
};
}
3 と 7 は対応する値と一致します。hello などの変数を定義すると、他の状況でも値と一致します。
u8
のすべての可能な値をリストしていなくても、最後のパターンは特にリストされていないすべての値に一致するため、このコードはコンパイルされます。このワイルドカード パターンは、 match
をすべて網羅する必要があるという要件を満たしています。パターンは順番に照合されるため、ワイルドカード分岐を最後に置く必要があることに注意してください。ワイルドカードブランチの後に別のブランチを追加すると、それ以降のブランチは決して一致しないため、Rust は警告を出します。
Rust にはモードも用意されています。ワイルドカード モードで取得した値を使用したくない場合は、 である を使用してください。特別なパターン。バインドせずに任意の値と一致します。これにより、この値を使用しないことが Rust に伝えられるため、Rust は未使用の変数について警告しません。 _
fn main() {
let dice_roll = 9;
match dice_roll {
3 => 3,
7 => 7,
_ => 9,
};
}
他のものと一致する場合、この場合はコードを実行したくありません。空のタプルは次のように返すことができます。
fn main() {
let dice_roll = 9;
match dice_roll {
3 => three(),
7 => seven(),
_ => (),
}
fn three() {}
fn seven() {}
}
3. 制御フローを簡潔にすると
まず例を見てみましょう。
fn main() {
let config_max = Some(3u8);
match config_max {
Some(max) => println!("The maximum is configured to be {}", max),
_ => (),
}
}
値が Some
の場合、 Some
メンバーの値を出力します。このメンバーは、 < にバインドされています。変数内のパターン a i=3>。 値については何もしたくありません。 式 (網羅性) の要件を満たすには、この唯一のメンバーを処理した後に を追加する必要があり、これにより多くの煩わしい定型コードも追加されます。 max
None
match
_ => ()
コードを簡素化するには、 if let を使用して簡素化できます。
fn main() {
let config_max = Some(3u8);
if let Some(max) = config_max {
println!("res {}", max)
}
}
if let
を使用すると、記述するコード、インデント、定型コードが少なくなります。ただし、これにより、 match
で義務付けられた網羅性チェックが失われます。 match
と if let
のどちらを選択するかは、特定の環境と、簡潔性の向上と徹底的なチェックの喪失との間のトレードオフによって異なります。
言い換えると、 if let
は match
の糖衣構文と考えることができ、値が特定のパターンに一致し、すべてを無視した場合にコードを実行します。他の値です。
下のループのマッチング パターンについては、以下に示すように、if let else を通じて実現できます。
fn main() {
let mut count = 0;
let config_max = Some(3u8);
if let Some(max) = config_max {
println!("res {}", max)
} else {
count += 1;
}
}