404 Not Found

404 Not Found


nginx

Rustの関数とスコープ:定義、引数、戻り値、および変数のシャドウイング

Rustでは関数は第一級オブジェクトであり、その引数型と戻り値型がコンパイラの最初の防衛線として機能します。

関数は、コードを構成する基本的な要素です。Rustの関数構文はC系言語のそれと似ていますが、引数のパターンマッチング式の戻り値に関しては、独自のスタイルを持っています。


1. 学習内容


2. あるコーヒーショップの物語

(1) 課題:コーヒーの淹れ方に決まった手順はない

リサはコーヒーショップを開店し、商売はますます好調になってきているが、一方で問題も生じてきた:

「コーヒーを淹れることが、関数を呼び出すのと同じくらい一貫性があれば素晴らしいだろう。つまり、同じ入力に対して常に同じ出力が得られるように。」

(2) Rustの関数に対するアプローチ

RUST
// How to Make a Latte"Function":Input Parameters,Brew Coffee
fn make_latte(milk_ml: i32, coffee_ml: i32) -> String {
    let result = format!("Latte Complete:{}ml Milk + {}ml Coffee,Fragrant and Delicious!", milk_ml, coffee_ml);
    result  // No semicolons——Expression Return Value
}

fn main() {
    let my_coffee = make_latte(200, 30);
    println!("{}", my_coffee);

    let another = make_latte(150, 45);
    println!("{}", another);
}

関数はコーヒーのレシピのようなものです。引数は材料(ミルクの量、コーヒーの量)であり、戻り値は出来上がったコーヒーです。同じ入力であれば、必ず同じ出力が得られます。これが関数の決定性です。


3. 関数の定義

(1) 基本的な構文

RUST
fn function_name(param1: Type1, param2: Type2) -> ReturnType {
    // Function Body
    return_value  // Expression returns(No semicolons)
}
100%
graph TB
    A[fn Function Name] --> B[(List of Parameters)]
    A --> C[-> Return Type]
    A --> D[{ Function Body }]
    B --> E[Each parameter: name: Type]
    D --> F[Final expression = Return Value]
    D --> G[return Keywords = Return Early]

(2) パラメータと戻り値のさまざまな形式

フォーム 構文
引数なし、戻り値なし fn foo() fn greet() { println!("hi"); }
引数を受け取り、何も返さない fn foo(x: i32) fn show(n: i32) { println!("{}", n); }
引数と戻り値あり fn foo(x: i32) -> i32 fn double(x: i32) -> i32 { x * 2 }
複数の戻り値(タプル) fn foo() -> (i32, bool) fn stats() -> (i32, bool) { (42, true) }
戻る return value; if x < 0 { return 0; }

基本ルール:関数本体の最後の式が戻り値となります(セミコロンは不要)。早期リターンを行うには、return キーワードを使用してください。

(3) パラメータの渡し方

引数渡し方法 構文 所有権の変更 適用可能なシナリオ
値渡し fn foo(s: String) 所有権は関数に移転される 関数はその値を消費する必要がある
不変参照による渡し fn foo(s: &String) 借用、所有権は変わらない 読み取り専用
参照渡し fn foo(s: &mut String) データを借用するが、所有権は変わらない データを変更する必要がある

(4) 戻り値の比較

メソッド 構文 所有権の変更
式の戻り値 行末にセミコロンがない 所有権の移転 fn f() -> String { s }
戻る return value; 所有権の移転 if err { return None; }
複数のタプルの返却 -> (T1, T2) 複数の所有権移転 fn f() -> (i32, bool)
出力パラメータ &mut T 所有権の移転なし fn fill(buf: &mut Vec<i32>)

4. 範囲と陰影

(1) ブロックのスコープ

Rustにおけるスコープは{}によって定義されています。各ブロックには独自のスコープがあります:

RUST
fn main() {
    let x = 10;          // Outer Scope

    {
        let y = 20;      // Inner Scope,y Available only here
        println!("Inside: x={}, y={}", x, y);  // ✅ Can access the outer layer x
    }

    // println!("{}", y);  // ❌ Compilation Error:y Not in this scope

    println!("External: x={}", x);  // ✅
}

(2) 変数のシャドウイング

外部変数は、同じ名前の内部変数によってシャドウイングされる可能性があり、内部のコードからは内部変数にしかアクセスできません:

RUST
fn main() {
    let x = 1;            // Outer layer x = 1

    {
        let x = 2;        // Inner layer x Covered the outer layer x
        println!("Inside x = {}", x);  // Output: Inside x = 2
    }

    println!("External x = {}", x);       // Output: External x = 1
}

(3) 関数パラメータの範囲

関数のパラメータは、関数本体全体で参照可能です:

RUST
fn foo(x: i32) {
    // x Available throughout the entire function body
    let y = x + 1;
    println!("x={}, y={}", x, y);
}  // End of function, x and y are both destroyed

5. 関数の例

(1) ▶ サンプル:さまざまな関数のシグネチャ (難易度 ⭐)

RUST
// ============================================
// Functions with Different Combinations of Parameters and Return Types
// ============================================

// No parameters, no return value
fn print_separator() {
    println!("-------------------");
}

// Single-parameter functions with return values
fn square(x: i32) -> i32 {
    x * x  // No semicolons,Expression returns
}

// Multi-parameter
fn area(width: u32, height: u32) -> u32 {
    width * height
}

// Return multiple values(Tuple)
fn min_max(a: i32, b: i32) -> (i32, i32) {
    if a < b {
        (a, b)  // Tuples as Return Values
    } else {
        (b, a)
    }
}

fn main() {
    print_separator();
    println!("5 the square of: {}", square(5));
    println!("Area 10x20: {}", area(10, 20));

    let (min, max) = min_max(100, 50);
    println!("min={}, max={}", min, max);
}

出力:

TEXT
-------------------
5 the square of: 25
Area 10x20: 200
min=50, max=100

min_max はタプルを返します。呼び出されると、そのタプルは分解され、let (min, max) を使用して代入されます。これが Rust における「複数の値を返す」ための標準的な方法です。


(2) ▶ サンプル:早期帰還とガードモード(難易度 ⭐⭐)

RUST
// ============================================
// Return Early:Inspection Criteria,If you're not satisfied, leave.
// ============================================

fn safe_divide(a: f64, b: f64) -> f64 {
    if b == 0.0 {
        println!("Error:The divisor cannot be 0");
        return f64::NAN;  // Return Early NaN(Not a Number)
    }
    a / b  // Under normal circumstances,Expression returns
}

fn get_grade(score: i32) -> &'static str {
    if score < 0 || score > 100 {
        return "Invalid Fractions";
    }
    if score >= 90 { return "A"; }
    if score >= 80 { return "B"; }
    if score >= 70 { return "C"; }
    if score >= 60 { return "D"; }
    "F"
}

fn main() {
    println!("10 / 3 = {}", safe_divide(10.0, 3.0));
    println!("10 / 0 = {}", safe_divide(10.0, 0.0));
    println!("Fractions 85: {}", get_grade(85));
    println!("Fractions -5: {}", get_grade(-5));
}

出力:

TEXT
10 / 3 = 3.3333333333333335
Error:The divisor cannot be 0
10 / 0 = NaN
Fractions 85: B
Fractions -5: Invalid Fractions

早期リターン(return)は、「まず確認してから実行する」というパターンでよく使われます。エラーやエッジケースを最初の段階で処理することで、その後のロジックは「セーフゾーン」内でスムーズに進行します。


(3) ▶ サンプル:ブロック式とスコープの検証(難易度 ⭐⭐)

RUST
// ============================================
// Scope and Return Values of Block Expressions
// ============================================

fn main() {
    let outer = "Outer-scope variables".to_string();

    let inner_result = {
        let inner = "Nested variables".to_string();
        println!("Accessible internally: {}", outer);  // ✅ The inner layer can access the outer layer

        // Shading Variables with the Same Name
        let outer = 42;
        println!("After masking outer: {}", outer);  // 42,Not a string

        inner.len()  // Block expression return values:inner length
    };  // inner Destroyed here

    println!("The value returned by the block: {}", inner_result);
    println!("External outer: {}", outer);  // ✅ Outer layer outer Still

    // println!("{}", inner);  // ❌ Compilation Error:inner Not in this scope
}

出力:

TEXT
Accessible internally: Outer-scope variables
After masking outer: 42
The value returned by the block: 8
External outer: Outer-scope variables

ブロックスコープのルール:内側のブロックからは外側の変数にアクセスできますが、外側のブロックからは内側の変数にアクセスできません。シェーディングは、外側の変数を一時的に「マスク」するだけです。内側のブロックを抜けると、外側の変数は元の状態に戻ります。


(4) ▶ サンプル:関数ポインタと高階関数(難易度 ⭐⭐⭐)

RUST
// ============================================
// Passing Functions as Arguments——An Introduction to Higher-Order Functions
// ============================================

type MathOp = fn(i32, i32) -> i32;

fn add(a: i32, b: i32) -> i32 { a + b }
fn sub(a: i32, b: i32) -> i32 { a - b }
fn mul(a: i32, b: i32) -> i32 { a * b }

fn calculate(op: MathOp, a: i32, b: i32) -> i32 {
    op(a, b)
}

fn apply_twice(f: fn(i32) -> i32, x: i32) -> i32 {
    f(f(x))
}

fn main() {
    let result1 = calculate(add, 10, 5);
    let result2 = calculate(sub, 10, 5);
    let result3 = calculate(mul, 10, 5);
    println!("10 + 5 = {}", result1);
    println!("10 - 5 = {}", result2);
    println!("10 * 5 = {}", result3);

    let double = |x: i32| x * 2;
    let result4 = apply_twice(double, 3);
    println!("double(double(3)) = {}", result4);

    let increment = |x: i32| x + 1;
    let result5 = apply_twice(increment, 5);
    println!("increment(increment(5)) = {}", result5);

    let alice_points = 80;
    let bonus = |base: i32| -> i32 { base + 10 };
    println!("Alice After the bonus points are added: {}", bonus(alice_points));
}

出力:

TEXT
10 + 5 = 15
10 - 5 = 5
10 * 5 = 50
double(double(3)) = 12
increment(increment(5)) = 7
Alice After the bonus points are added: 90

関数ポインタ(型 fn)とクロージャ(型 |x| x * 2)は、いずれも高階関数の引数として渡すことができます。関数ポインタはコンパイル時に決定されるコードのアドレスを指すのに対し、クロージャは環境変数をキャプチャすることができます。


(5) ▶ サンプル:総合演習—学生の成績処理(難易度 ⭐⭐⭐)

RUST
// ============================================
// Comprehensive Example:Function,Combining Scope and Shading
// ============================================

fn average(scores: &[i32]) -> f64 {
    if scores.is_empty() {
        return 0.0;
    }
    let sum: i32 = scores.iter().sum();
    sum as f64 / scores.len() as f64
}

fn classify(score: i32) -> &'static str {
    match score {
        90..=100 => "A",
        80..=89  => "B",
        70..=79  => "C",
        60..=69  => "D",
        _        => "F",
    }
}

fn report(names: &[&str], scores: &[i32]) {
    let avg = average(scores);
    println!("Class Average Score: {:.1}", avg);

    for (i, name) in names.iter().enumerate() {
        let score = scores[i];
        let grade = classify(score);
        let status = if score >= 60 { "Through" } else { "Failed" };
        println!("{}: {} pts, Level {}, {}", name, score, grade, status);
    }

    let passing = scores.iter().filter(|&&s| s >= 60).count();
    println!("Number of people who passed: {}/{}", passing, scores.len());
}

fn main() {
    let names = ["Alice", "Bob", "Charlie", "David"];
    let scores = [95, 72, 58, 88];

    println!("=== Report Card ===");
    report(&names, &scores);

    println!("\n--- Scope Demonstration ---");
    let result = {
        let scores = [100, 90, 80];
        let avg = average(&scores);
        format!("Average of the top three: {:.1}", avg)
    };
    println!("{}", result);

    let avg = "Outer layer avg";
    println!("Outer-scope variables: {}", avg);
}

出力:

TEXT
=== Report Card ===
Class Average Score: 78.2
Alice: 95 pts, Level A, Through
Bob: 72 pts, Level C, Through
Charlie: 58 pts, Level F, Failed
David: 88 pts, Level B, Through
Number of people who passed: 3/4

--- Scope Demonstration ---
Average of the top three: 90.0
Outer-scope variables: Outer layer avg

この例では、関数定義、スライスパラメータ、matchの分類、イテレータメソッド、およびブロックスコープでの変数のシャドウイングを組み合わせています。&[T]スライスを関数パラメータとして使用することで最大の柔軟性が得られ、ブロックスコープ内で同名の変数をシャドウイングしても、外側のスコープには影響しません。


❓ よくある質問

Q 最後の行の式には、戻り値としてセミコロンが含まれていません。セミコロンを付け忘れる傾向がある場合、どうすればよいでしょうか?
A セミコロンを付け忘れると、コンパイラはエラーを発生させ、それが文なのか式なのかを指定するよう促します。
Q returnと式による戻り値の違いは何ですか?
A returnは早期リターンに使用され、式による戻り値は関数の末尾で使用されます。
Q なぜ関数の引数には型を指定しなければならないのですか?
A Rustでは関数の引数の型を推論しないからです。
Q シャドウイングは悪い習慣ですか?
A 適度なシャドウイングは良い習慣です。
Q 関数はネストできますか?
A はい、Rustでは関数内に関数を定義することができます。

📖 まとめ


📝 練習問題

  1. 難易度 ⭐:整数が偶数かどうかを判定する関数 fn is_even(n: i32) -> bool を記述し、次に main を記述してその関数を呼び出し、1 から 10 までの各数の偶奇を出力してください。
  2. 難易度 ⭐⭐: 3つの数値のうち最大の値を返す関数 fn max_of_three(a: i32, b: i32, c: i32) -> i32 を作成してください。(標準ライブラリの .max() は使用できません。独自の比較ロジックを実装してください。)
  3. 難易度 ⭐⭐⭐: パラメータ op ('+' '-' '*' '/')に基づいて対応する演算を実行し、除数が 0 の場合は f64::NAN を返す関数 fn calculator(op: char, a: f64, b: f64) -> f64 を作成してください。これら 4 つの演算を main でテストしてください。
Web-Tutorial.com

Web-Tutorial 技術チーム

複数の開発者によって共同維持されているプログラミングチュートリアルプラットフォーム。各チュートリアルは専門分野の開発者が執筆・レビューしています。正確で信頼性の高いコンテンツを目指しています — 問題を見つけた場合はお知らせください。

100%