Rustの高度なパターンマッチング:ガード、@バインディング、および複数のパターン
パターンマッチングはRustの最も強力な言語機能の一つであり、
matchガード、@バインディング、マルチパターンといった高度なテクニックを用いることで、マッチングをよりきめ細かく制御できるようになります。つまり、単に「値をマッチングする」だけでなく、「条件をマッチングする」ことも可能になるのです。
基本的なパターンマッチングが多肢選択問題(実行する分岐を1つ選ぶ)のようなものだとすれば、高度なパターンマッチングは複合問題のようなものです。つまり、値のチェック、参照の束縛、追加の条件の評価をすべて同時に行うことができるのです。
1. 学習内容
ifマッチガードを使用して、マッチ分岐に追加の条件を設定する- 一致が見つかった場合、
@を使用して値を同時にバインドする |を使用して、複数のパターンを組み合わせる(マルチパターンマッチング)- 範囲一致には
..および..=を使用してください - 簡潔なブール式の一致チェックには、
matches!マクロを使用してください - 構造体と列挙型の入れ子構造の分解
2. 社員の業績評価をめぐるエピソード
(1) 課題:包括的な評価を行うためのif-else連鎖の使用
トムは同社の人事マネージャーであり、従業員の業績評価と出勤記録に基づいて総合的な評価を付ける必要があります。
ルールは以下の通りです:
| レベル | 成績評価 | 出席要件 |
|---|---|---|
| A+ | 95以上 | 皆勤(22日以上) |
| A | ≥85 | 出席日数 ≥ 20日 |
| B | ≥70 | 出席日数 ≥ 18日 |
| C | ≥60 | — |
| D | <60 | — |
最初は、彼はif-elseという文字を延々と書き連ねた:
fn rate_employee(score: u32, attendance: u32) -> &'static str {
if score >= 95 && attendance >= 22 {
"A+"
} else if score >= 85 && attendance >= 20 {
"A"
} else if score >= 70 && attendance >= 18 {
"B"
} else if score >= 60 {
"C"
} else {
"D"
}
}
このコードは動作しますが、いくつかの問題があります。条件分岐が一連のif-else文のあちこちに散らばっているため可読性が低く、また網羅的なチェックが行われていないため、将来的に評価ルールが追加された場合、それを見落とす可能性が高くなります。さらに、スコアと出席状況の関連性が十分に明確ではありません。
(2) Rustにおけるパターンマッチングの高度なアプローチ
fn rate_employee(score: u32, attendance: u32) -> &'static str {
match score {
// Guard Requirements: Meeting the Score Requirement + Attendance Requirements Met
s if s >= 95 && attendance >= 22 => "A+",
s if s >= 85 && attendance >= 20 => "A",
s if s >= 70 && attendance >= 18 => "B",
s if s >= 60 => "C",
_ => "D", // Fallback: score < 60
}
}
fn main() {
let employees = [
("Alice", 98, 23),
("Bob", 88, 21),
("Charlie", 88, 17), // High scores but insufficient attendance
("Diana", 72, 19),
("Eve", 55, 20),
];
for (name, score, attendance) in &employees {
let rating = rate_employee(*score, *attendance);
println!("{:8} | Score: {:2} | Attendance: {:2} days | Rating: {}",
name, score, attendance, rating);
}
}
出力:
Alice | Score: 98 | Attendance: 23 days | Rating: A+
Bob | Score: 88 | Attendance: 21 days | Rating: A
Charlie | Score: 88 | Attendance: 17 days | Rating: B
Diana | Score: 72 | Attendance: 19 days | Rating: B
Eve | Score: 55 | Attendance: 20 days | Rating: D
matchこのガードにより、各分岐の条件が即座に明確になります。スコアと出席状況の間の「AND」関係がはっきりと見て取れます。_包括的な分岐により、網羅性が確保されます。さらに、異なるスコア範囲におけるs if s >= ...の意味合いは、else ifのものよりも直感的です。
3. 高度なパターンマッチングの概要
(1) コンセプトマップ
graph TB
A[Advanced Pattern Matching] --> B[match Guard]
A --> C[@ Bind]
A --> D[Multimode |]
A --> E[Range Matching]
A --> F[matches! Macro]
A --> G[Deconstruction Patterns]
B --> B1["match x { n if n > 10 => ... }"]
C --> C1["match x { e @ 1..=5 => ... }"]
D --> D1["match x { 1 | 3 | 5 => ... }"]
E --> E1["match x { 1..=10 => ... }"]
F --> F1["if matches!(x, 1..=5) { ... }"]
G --> G1["let Point { x, y } = p;"]
G --> G2["match opt { Some(v @ 1..=10) => ... }"]
(2) マッチング手法の比較
| メソッド | 構文 | 使用場面 | 例 |
|---|---|---|---|
| 基本一致 | match x { PAT => .. } |
単純な値の一致 | match x { 1 => "one" } |
| マッチガード | match x { PAT if COND => .. } |
マッチ + 追加条件 | n if n > 10 && n % 2 == 0 |
| @ バインディング | e @ PAT => .. |
一致時のバインディング値 | e @ 1..=10 => e |
| マルチモード | | PAT1 | PAT2 => .. |
複数のモードが同じ分岐を共有する | 1 | 3 | 5 => "odd" |
| 範囲 ..= | PAT1..=PAT2 => .. |
値の範囲に一致する | 1..=5 => "small" |
| matches! マクロ | matches!(x, PAT) |
ブール値の結果のみ | if matches!(x, 1..=5) |
| 分解 | let PAT = value |
複合型の分解 | let (a, b) = pair |
(3) パターンタイプのクイックリファレンス
| パターンの種類 | 構文 | 一致対象 | 例 |
|---|---|---|---|
| リテラル | 1 / "hello" |
正確な値 | match x { 1 => ... } |
| 変数の束縛 | x |
任意の値(x に束縛) | match x { n => ... } |
| ワイルドカード | _ |
任意の値、無視される | match x { _ => ... } |
| マルチモード | 1 | 2 | 3 |
複数値 | match x { 1 | 2 => ... } |
| 範囲 | 1..=5 |
閉区間 | match x { 1..=5 => ... } |
| タプルの分解 | (a, b) |
タプル | match pair { (x, y) => ... } |
| 構造の解体 | Point { x, y } |
構造 | match p { Point { x, y } => ... } |
| 列挙型の分解 | Some(v) |
列挙型のバリエーション | match opt { Some(v) => ... } |
| @ バインディング | e @ 1..=10 |
範囲 + バインディング | match x { e @ 1..=10 => ... } |
| ガード | x if x > 0 |
モードと条件 | match x { n if n > 0 => ... } |
| 残りを無視 | .. |
一部のフィールドを無視 | match p { Point { x, .. } => ... } |
4. 高度なマッチングの例
(1) ▶ サンプル:マッチガード + @ バインディング + マルチモード(難易度 ⭐⭐)
// ============================================
// Comprehensive Example: Employee Performance Rating System
// Display match Guard, @ Bind, Multi-pattern
// ============================================
#[derive(Debug)]
enum Department {
Engineering,
Sales,
HR,
Management,
}
#[derive(Debug)]
struct Employee {
name: String,
score: u32,
attendance: u32,
department: Department,
}
impl Employee {
/// Rate employee using advanced pattern matching.
/// Returns (rating, bonus_percentage).
fn rate(&self) -> (&'static str, u32) {
match (self.score, self.attendance) {
// @ binding: capture the matched score value
s @ 95..=100 if self.attendance >= 22 => {
println!(" [@ binding] score {} captured for A+ rating", s);
("A+", 30)
}
s @ 85..=94 if self.attendance >= 20 => {
println!(" [@ binding] score {} captured for A rating", s);
("A", 20)
}
s @ 70..=84 if self.attendance >= 18 => {
println!(" [@ binding] score {} captured for B rating", s);
("B", 10)
}
// Multi-pattern: 60..=69 OR exactly 70 with low attendance
60..=69 | 70..=84 => {
("C", 5) // No @ binding needed here
}
// match guard with combined conditions
s if s < 60 => {
println!(" [guard] score {} is below 60, rating D", s);
("D", 0)
}
// Catch-all: should not reach normally
_ => {
println!(" [wildcard] unexpected combination");
("Unknown", 0)
}
}
}
/// Check if this employee qualifies for special bonus
/// using matches! macro.
fn has_special_bonus(&self) -> bool {
// matches! returns true if the pattern matches
matches!(self.department, Department::Engineering | Department::Management)
&& self.score >= 90
}
}
fn main() {
let employees = vec![
Employee {
name: String::from("Alice"),
score: 97,
attendance: 23,
department: Department::Engineering,
},
Employee {
name: String::from("Bob"),
score: 88,
attendance: 21,
department: Department::Sales,
},
Employee {
name: String::from("Charlie"),
score: 88,
attendance: 17,
department: Department::HR,
},
Employee {
name: String::from("Diana"),
score: 65,
attendance: 20,
department: Department::HR,
},
Employee {
name: String::from("Eve"),
score: 42,
attendance: 15,
department: Department::Management,
},
];
for emp in &employees {
println!("{}:", emp.name);
let (rating, bonus) = emp.rate();
let special = emp.has_special_bonus();
println!(" Rating: {}, Bonus: {}%, Special: {}",
rating, bonus, special);
println!();
}
}
出力:
Alice:
[@ binding] score 97 captured for A+ rating
Rating: A+, Bonus: 30%, Special: true
Bob:
[@ binding] score 88 captured for A rating
Rating: A, Bonus: 20%, Special: false
Charlie:
[@ binding] score 88 captured for B rating
Rating: B, Bonus: 10%, Special: false
Diana:
Rating: C, Bonus: 5%, Special: false
Eve:
[guard] score 42 is below 60, rating D
Rating: D, Bonus: 0%, Special: true
この例では、3つの高度なマッチング手法を包括的に示しています。
@は、後で使用するためにキャプチャされた値を束ねています。ifは、ガードを使用して追加の条件(出席要件など)を追加しています。|は、マルチモードを使用して、異なる範囲が同じ分岐を共有できるようにしています。そして、matches!は、マクロを使用して、「それがいくつかのバリエーションのいずれかに属するかどうか」を簡潔に判定しています。
(2) ▶ サンプル:ネストされた構造体と列挙型の分解(難易度 ⭐⭐⭐)
// ============================================
// Nested Deconstruction: Matching Enums in Structures, Tuples in Enumerations
// ============================================
#[derive(Debug)]
enum TaskStatus {
Pending,
InProgress,
Completed { finished_at: String, reviewer: String },
Blocked { reason: String, blocked_by: String },
}
#[derive(Debug)]
struct Task {
id: u32,
title: String,
status: TaskStatus,
priority: u8, // 1 (highest) to 5 (lowest)
}
fn analyze_task(task: &Task) {
// Destructure the Task struct directly in match
match task {
// Multi-pattern: priority 1 or 2 with any status
Task { priority: 1..=2, .. } => {
println!(" [range] High priority task (level {})", task.priority);
}
// Destructure both Task and TaskStatus::Completed
Task {
id,
title,
status:
TaskStatus::Completed {
finished_at,
reviewer,
},
priority,
} => {
println!(" [nested destructure] Task #{} '{}' completed by {} at {} (priority {})",
id, title, reviewer, finished_at, priority);
}
// Destructure TaskStatus::Blocked with @ binding on reason
Task {
status:
TaskStatus::Blocked {
reason,
blocked_by,
},
..
} => {
println!(" [@ binding] Task '{}' blocked by {}: {}",
task.title, blocked_by, reason);
}
// Match guard on enum variant
Task {
status: TaskStatus::InProgress,
priority,
..
} if *priority <= 3 => {
println!(" [guard] Important in-progress task (priority {})", priority);
}
// Wildcard for remaining
_ => {
println!(" [wildcard] Task '{}': status {:?}", task.title, task.status);
}
}
}
fn main() {
let tasks = vec![
Task {
id: 1,
title: String::from("Security audit"),
status: TaskStatus::Completed {
finished_at: String::from("2026-07-01"),
reviewer: String::from("Alice"),
},
priority: 1,
},
Task {
id: 2,
title: String::from("Update dependencies"),
status: TaskStatus::Blocked {
reason: String::from("Waiting for approval"),
blocked_by: String::from("Manager"),
},
priority: 2,
},
Task {
id: 3,
title: String::from("Write documentation"),
status: TaskStatus::InProgress,
priority: 3,
},
Task {
id: 4,
title: String::from("Fix typo in README"),
status: TaskStatus::Pending,
priority: 5,
},
];
for task in &tasks {
println!("Task #{}: {}", task.id, task.title);
analyze_task(task);
println!();
}
}
出力:
Task #1: Security audit
[nested destructure] Task #1 'Security audit' completed by Alice at 2026-07-01 (priority 1)
Task #2: Update dependencies
[range] High priority task (level 2)
Task #3: Write documentation
[guard] Important in-progress task (priority 3)
Task #4: Fix typo in README
[wildcard] Task 'Fix typo in README': status Pending
ネストされたデストラクチャリングは、Rustのパターンマッチングにおける強力な機能です。単一の
match分岐内で、構造体、列挙型、タプルを同時にデストラクチャリングすることができます。..は不要なフィールドを無視し、@は内部の値を束縛・キャプチャします。なお、Task { priority: 1..=2, .. }は優先度が1または2の任意のタスクに一致することに注意してください。これは、Blockedがパターンの後半に位置しているにもかかわらず、rangeが優先度2のタスクを最初にマッチングするためです。
(3) ▶ サンプル:matches! マクロと if let の組み合わせ(難易度:⭐⭐)
// ============================================
// matches! Macro: Simple Boolean Pattern Matching
// ============================================
#[derive(Debug, PartialEq)]
enum HttpStatus {
Ok,
NotFound,
ServerError(u16),
Redirect(u16),
}
/// Check if a status is a success (2xx).
fn is_success(status: &HttpStatus) -> bool {
matches!(status, HttpStatus::Ok)
}
/// Check if a status is a server error (5xx).
fn is_server_error(status: &HttpStatus) -> bool {
matches!(status, HttpStatus::ServerError(_))
}
/// Check if a status is a redirect (3xx) with specific code.
fn is_redirect_to(status: &HttpStatus, code: u16) -> bool {
matches!(status, HttpStatus::Redirect(c) if *c == code)
}
/// Get status category using multiple matches! checks.
fn categorize(status: &HttpStatus) -> &'static str {
if matches!(status, HttpStatus::Ok) {
"Success"
} else if matches!(status, HttpStatus::Redirect(301 | 302)) {
"Temporary Redirect"
} else if matches!(status, HttpStatus::Redirect(_)) {
"Other Redirect"
} else if matches!(status, HttpStatus::NotFound) {
"Not Found (404)"
} else if matches!(status, HttpStatus::ServerError(500..=599)) {
"Server Error"
} else {
"Unknown"
}
}
fn main() {
let statuses = vec![
HttpStatus::Ok,
HttpStatus::NotFound,
HttpStatus::Redirect(301),
HttpStatus::Redirect(307),
HttpStatus::ServerError(500),
HttpStatus::ServerError(503),
];
for status in &statuses {
println!("{:?}:", status);
println!(" is_success: {}", is_success(status));
println!(" is_server_error: {}", is_server_error(status));
println!(" is_redirect_to_301: {}", is_redirect_to(status, 301));
println!(" category: {}", categorize(status));
// if let with matches!-style pattern
if let HttpStatus::ServerError(code) = status {
println!(" >> Server error code: {}", code);
}
println!();
}
}
出力:
Ok:
is_success: true
is_server_error: false
is_redirect_to_301: false
category: Success
NotFound:
is_success: false
is_server_error: false
is_redirect_to_301: false
category: Not Found (404)
Redirect(301):
is_success: false
is_server_error: false
is_redirect_to_301: true
category: Temporary Redirect
Redirect(307):
is_success: false
is_server_error: false
is_redirect_to_301: false
category: Other Redirect
ServerError(500):
is_success: false
is_server_error: true
is_redirect_to_301: false
category: Server Error
ServerError(503):
is_success: false
is_server_error: true
is_redirect_to_301: false
category: Server Error
matches!マクロはboolを返します。これはifの条件式におけるパターンマッチングに適しています。このマクロは、ifガード、複数パターン、範囲マッチングを含む、すべてのパターン構文をサポートしています。if letとは異なり、matches!は変数を束縛せず、true/falseのみを返します。これは、値を取得せずに条件を評価するだけでよい場合に、最も簡潔な選択肢となります。
(4) ▶ サンプル:総合演習—式評価器(難易度 ⭐⭐⭐)
// ============================================
// Comprehensive Example: Implementing Expression Evaluation Using Nested Pattern Matching
// ============================================
#[derive(Debug, Clone)]
enum Expr {
Number(f64),
Add(Box<Expr>, Box<Expr>),
Sub(Box<Expr>, Box<Expr>),
Mul(Box<Expr>, Box<Expr>),
Div(Box<Expr>, Box<Expr>),
Neg(Box<Expr>),
}
fn eval(expr: &Expr) -> Option<f64> {
match expr {
Expr::Number(n) => Some(*n),
Expr::Add(a, b) => Some(eval(a)? + eval(b)?),
Expr::Sub(a, b) => Some(eval(a)? - eval(b)?),
Expr::Mul(a, b) => Some(eval(a)? * eval(b)?),
Expr::Div(a, b) => {
let divisor = eval(b)?;
if divisor == 0.0 { None } else { Some(eval(a)? / divisor) }
}
Expr::Neg(a) => Some(-eval(a)?),
}
}
fn expr_to_string(expr: &Expr) -> String {
match expr {
Expr::Number(n) => format!("{:.0}", n),
Expr::Add(a, b) => format!("({} + {})", expr_to_string(a), expr_to_string(b)),
Expr::Sub(a, b) => format!("({} - {})", expr_to_string(a), expr_to_string(b)),
Expr::Mul(a, b) => format!("({} * {})", expr_to_string(a), expr_to_string(b)),
Expr::Div(a, b) => format!("({} / {})", expr_to_string(a), expr_to_string(b)),
Expr::Neg(a) => format!("-{}", expr_to_string(a)),
}
}
fn main() {
let expr1 = Expr::Add(
Box::new(Expr::Number(10.0)),
Box::new(Expr::Mul(Box::new(Expr::Number(3.0)), Box::new(Expr::Number(4.0)))),
);
let expr2 = Expr::Div(
Box::new(Expr::Sub(Box::new(Expr::Number(20.0)), Box::new(Expr::Number(5.0)))),
Box::new(Expr::Number(3.0)),
);
let expr3 = Expr::Neg(Box::new(Expr::Number(42.0)));
let expr4 = Expr::Div(Box::new(Expr::Number(10.0)), Box::new(Expr::Number(0.0)));
let exprs = [expr1, expr2, expr3, expr4];
for (i, expr) in exprs.iter().enumerate() {
let s = expr_to_string(expr);
match eval(expr) {
Some(val) => println!("Expression{}: {} = {:.2}", i + 1, s, val),
None => println!("Expression{}: {} = Error (Division by Zero)", i + 1, s),
}
}
}
出力:
Expression1: (10 + (3 * 4)) = 22.00
Expression2: ((20 - 5) / 3) = 5.00
Expression3: -42 = -42.00
Expression4: (10 / 0) = Error (Division by Zero)
再帰的な列挙とネストされた分解を組み合わせたものは、パターンマッチングの最も強力な応用例の一つである。
Box<Expr>これにより、列挙型が自分自身を参照できるようになる(Boxは固定サイズのポインタであるため)。eval?演算子を使用して、ゼロ以外のエラーを洗練された方法で伝播させる。
(5) ▶ サンプル:パターンマッチングとJSON風のデータ構造 (難易度 ⭐⭐⭐)
// ============================================
// Processing Dynamically Typed Data Using Pattern Matching
// ============================================
#[derive(Debug, Clone)]
enum Value {
Null,
Bool(bool),
Number(f64),
String(String),
Array(Vec<Value>),
Object(Vec<(String, Value)>),
}
fn type_name(val: &Value) -> &str {
match val {
Value::Null => "null",
Value::Bool(_) => "bool",
Value::Number(_) => "number",
Value::String(_) => "string",
Value::Array(_) => "array",
Value::Object(_) => "object",
}
}
fn deep_count(val: &Value) -> usize {
match val {
Value::Array(items) => items.iter().map(deep_count).sum::<usize>() + 1,
Value::Object(entries) => entries.iter().map(|(_, v)| deep_count(v)).sum::<usize>() + 1,
_ => 1,
}
}
fn find_key<'a>(val: &'a Value, key: &str) -> Option<&'a Value> {
match val {
Value::Object(entries) => entries.iter().find(|(k, _)| k == key).map(|(_, v)| v),
_ => None,
}
}
fn main() {
let data = Value::Object(vec![
("name".into(), Value::String("Alice".into())),
("age".into(), Value::Number(30.0)),
("active".into(), Value::Bool(true)),
("scores".into(), Value::Array(vec![
Value::Number(95.0),
Value::Number(88.0),
Value::Number(92.0),
])),
("address".into(), Value::Object(vec![
("city".into(), Value::String("Beijing".into())),
("zip".into(), Value::Number(100000.0)),
])),
]);
println!("Type: {}", type_name(&data));
println!("Number of nodes: {}", deep_count(&data));
if let Some(Value::String(name)) = find_key(&data, "name") {
println!("Name: {}", name);
}
if let Some(Value::Array(scores)) = find_key(&data, "scores") {
let avg: f64 = scores.iter()
.filter_map(|v| if let Value::Number(n) = v { Some(*n) } else { None })
.sum::<f64>() / scores.len() as f64;
println!("Average Score: {:.1}", avg);
}
if let Some(Value::Object(addr)) = find_key(&data, "address") {
if let Some(Value::String(city)) = find_key(&Value::Object(addr.clone()), "city") {
println!("City: {}", city);
}
}
}
出力:
Type: object
Number of nodes: 11
Name: Alice
Average Score: 91.7
City: Beijing
列挙型を使用して動的型付けデータ(JSONなど)をシミュレートし、パターンマッチングを用いて安全にデータを抽出・反復処理します。
find_key安全なアクセスにはif letを使用し、配列内の値をフィルタリングおよび変換するにはfilter_mapとパターンマッチングを組み合わせて使用します。
❓ よくある質問
matchガード(if guard)とmatch分岐の優先順位はどのように決まるのですか?matchガードは分岐内の条件です。まずパターンマッチが行われ、その後ガードが評価されます。ガードが false を返した場合、プログラムは次の分岐に進みます(次の分岐へフォールスルーすることはありません!)。パターンマッチによって束縛された変数は、ガード条件内で使用できます。e @ 1..=10 は「値が 1 から 10 の間であれば、その値を e にバインドする」という意味です。x と直接記述すると、任意の値に一致して x にバインドされます。@ を使用すると、範囲を制限しつつその値を利用することができます。.. は任意の数の要素に一致し(それらを無視します)、_ は単一の位置に一致します。構造体のデストラクチャリングでは、Struct { a, .. } は他のすべてのフィールドを無視します。タプルでは、(x, .., z) は最初と最後の要素に一致します。_ は 1 つの位置のみを無視します。.. は、同じパターン内で 1 回しか使用できません。matches! マクロと if let の違いは何ですか?matches! は(変数に代入せずに)bool を返しますが、if let は変数に代入してコードブロックを実行します。 一致した値は必要なく、「一致するかどうか」を確認するだけでよい場合は、matches!を使用してください。値を展開して使用する必要がある場合は、if letまたはmatchを使用してください。Some(1) | Some(2) は有効ですが、Some(1) | None は無効です。これは、Some にはデータ要素があるのに対し、None にはデータ要素がないためです。PartialOrd (i8-u128、u8-u128) を実装する任意の整数型および char がサポートされています。例えば、match c { 'a'..='z' => "lowercase", 'A'..='Z' => "uppercase", _ => "other" } などです。f64 に対する範囲一致はサポートされていません。📖 まとめ
- マッチガード(ガードがある場合) パターンマッチングの後に条件チェックを追加し、「パターン+条件」によるきめ細かな制御を可能にします。
- @ バインディング 特定のパターンに一致した際に値をキャプチャし、重複した計算や再マッチングを防ぐ
- | マルチモード 複数のモードで同じコードブランチを共有できるため、コードの重複を削減できます
- .. および ..= 範囲指定では、整数の範囲や文字の範囲を指定できます。
..は、分解処理中に残りのフィールドを無視します - matches! マクロ ブール値を返します。if 文での簡潔なパターンマッチングのチェックに最適です。
- ネストされたデストラクチャリング を使用すると、
matchステートメント内で、構造体、列挙型、タプルなどの複合型を複数のレベルにわたって同時に展開することができます。
📝 練習問題
-
難易度 ⭐:
matchと範囲照合を使用する関数fn describe_number(n: i32) -> &'static strを作成してください。1..=10は「small」を返し、11..=100は「medium」を返し、101..=1000は「large」を返し、それ以外はすべて「out of range」を返します。main関数内で、5、50、500、5000を用いてテストしてください。 -
難易度 ⭐⭐:
Celsius(f64)とFahrenheit(f64)の 2 つのバリエーションを含む列挙型Temperatureを定義してください。matches!マクロとifガードを使用して、以下の条件を判定する関数fn describe_temp(temp: &Temperature) -> &'static strを作成してください。摂氏温度が 30 より大きい、または華氏温度が 86 より大きい場合は「hot」を返し、摂氏温度が 0 より小さい、または華氏温度が 32 より小さい場合は「cold」を返し、それ以外の場合は「moderate」を返します。main関数内で、4つの温度についてテストを行ってください。 -
難易度 ⭐⭐⭐:
id: u32、items: Vec<String>、total: f64、およびstatus: OrderStatusを含む構造体Orderを定義してください (列挙型:Pending、Shipped { tracking: String }、Delivered { date: String })。ネストされたデストラクチャリングとmatchガードを使用して、ステータスと合計金額に基づいて異なる要約メッセージを返すメソッドfn summary(&self) -> Stringを実装してください(例:合計金額が 1,000 を超える出荷済み注文の場合、「高額注文が出荷されました。追跡番号:xxx」と表示する)。関数main内で、3 件の注文を作成し、それぞれの概要を出力してください。



