LabEx における Rust 関数の定義

Beginner

This tutorial is from open-source community. Access the source code

はじめに

関数へようこそ。この実験は、Rust Bookの一部です。LabEx で Rust のスキルを練習することができます。

この実験では、fnキーワードと慣用的なスネークケースの命名規則を使って、Rust で関数を定義して呼び出す方法を学びます。

関数

関数は Rust コードにおいて非常に一般的です。この言語の中で最も重要な関数の 1 つであるmain関数を既に見てきました。これは多くのプログラムのエントリポイントです。また、新しい関数を宣言するためのfnキーワードも見てきました。

functionsという名前の新しいプロジェクトを作成しましょう。

cargo new functions
cd functions

Rust コードでは、関数名と変数名の慣用的なスタイルとして「スネークケース」を使用しており、すべての文字が小文字で、単語を区切るためにアンダースコアが使われます。以下は、関数定義の例を含むプログラムです。

ファイル名:src/main.rs

fn main() {
    println!("Hello, world!");

    another_function();
}

fn another_function() {
    println!("Another function.");
}

Rust では、fnの後に関数名と 1 組の丸括弧を入力することで関数を定義します。波括弧は、関数本体の始まりと終わりをコンパイラに知らせます。

定義した関数は、関数名の後に 1 組の丸括弧を入力することで呼び出すことができます。another_functionはプログラム内に定義されているため、main関数の中から呼び出すことができます。ソースコードでは、main関数の後にanother_functionを定義しましたが、前に定義しても構いません。Rust は関数をどこで定義するかは気にしません。ただ、呼び出し元が参照できるスコープ内のどこかに定義されていることが重要です。

関数をさらに詳しく調べるために、新しいバイナリプロジェクトfunctionsを作成しましょう。src/main.rsanother_functionの例を配置して実行します。以下の出力が表示されるはずです。

$ cargo run
   Compiling functions v0.1.0 (file:///projects/functions)
    Finished dev [unoptimized + debuginfo] target(s) in 0.28s
     Running `target/debug/functions`
Hello, world!
Another function.

行はmain関数内に表示される順序で実行されます。最初に「Hello, world!」のメッセージが表示され、その後another_functionが呼び出され、そのメッセージが表示されます。

パラメータ

関数には「パラメータ」を持たせることができます。これは、関数のシグネチャの一部である特別な変数です。関数にパラメータがある場合、それらのパラメータに対して具体的な値を提供することができます。技術的には、具体的な値は「引数」と呼ばれますが、日常会話では、人々は関数の定義内の変数または関数を呼び出す際に渡される具体的な値の両方に対して、「パラメータ」と「引数」の言葉を交換して使用する傾向があります。

このバージョンのanother_functionでは、パラメータを追加します。

ファイル名:src/main.rs

fn main() {
    another_function(5);
}

fn another_function(x: i32) {
    println!("The value of x is: {x}");
}

このプログラムを実行してみてください。以下の出力が得られるはずです。

$ cargo run
   Compiling functions v0.1.0 (file:///projects/functions)
    Finished dev [unoptimized + debuginfo] target(s) in 1.21s
     Running `target/debug/functions`
The value of x is: 5

another_functionの宣言には、xという名前の 1 つのパラメータがあります。xの型はi32と指定されています。another_function5を渡すと、println!マクロは、フォーマット文字列の中のxを含む波括弧のペアの場所に5を置きます。

関数のシグネチャでは、各パラメータの型を宣言する「必要があります」。これは、Rust の設計における意図的な決定です。関数定義に型注釈を必要とすることは、コンパイラがコードの他の場所であなたが何を意味する型を理解するために型注釈を使用する必要がほとんどないことを意味します。関数が期待する型をコンパイラが知っている場合、コンパイラはより有益なエラーメッセージを与えることもできます。

複数のパラメータを定義する場合、パラメータの宣言をコンマで区切ります。次のようになります。

ファイル名:src/main.rs

fn main() {
    print_labeled_measurement(5, 'h');
}

fn print_labeled_measurement(value: i32, unit_label: char) {
    println!("The measurement is: {value}{unit_label}");
}

この例では、2 つのパラメータを持つprint_labeled_measurementという名前の関数を作成しています。最初のパラメータはvalueといい、i32型です。2 番目はunit_labelといい、型はcharです。その後、関数はvalueunit_labelの両方を含むテキストを表示します。

このコードを実行してみましょう。functionsプロジェクトのsrc/main.rsファイルに現在あるプログラムを前の例に置き換え、cargo runを使用して実行します。

$ cargo run
   Compiling functions v0.1.0 (file:///projects/functions)
    Finished dev [unoptimized + debuginfo] target(s) in 0.31s
     Running `target/debug/functions`
The measurement is: 5h

valueの値として5unit_labelの値として'h'を関数に渡したので、プログラムの出力にはそれらの値が含まれています。

文と式

関数の本体は、オプションで式で終わる一連の文で構成されています。これまで見てきた関数では、終わりの式は含まれていませんでしたが、文の一部として式を見てきました。Rust は式ベースの言語であるため、これは理解する重要な違いです。他の言語には同じ違いはありませんので、文と式とは何か、その違いが関数の本体にどのように影響するか見てみましょう。

  • :何らかのアクションを実行し、値を返さない命令です。
  • :結果の値に評価されます。いくつかの例を見てみましょう。

実際、既に文と式を使ってきました。letキーワードを使って変数を作成し、それに値を割り当てるのは文です。リスト 3-1 のlet y = 6;は文です。

ファイル名:src/main.rs

fn main() {
    let y = 6;
}

リスト 3-1:1 つの文を含むmain関数の宣言

関数の定義も文です。前述の例全体自体が文です。

文は値を返しません。したがって、次のコードのようにlet文を別の変数に割り当てることはできません。エラーが発生します。

ファイル名:src/main.rs

fn main() {
    let x = (let y = 6);
}

このプログラムを実行すると、次のようなエラーが表示されます。

$ cargo run
   Compiling functions v0.1.0 (file:///projects/functions)
error: expected expression, found statement (`let`)
 --> src/main.rs:2:14
  |
2 |     let x = (let y = 6);
  |              ^^^^^^^^^
  |
  = note: variable declaration using `let` is a statement

error[E0658]: `let` expressions in this position are unstable
 --> src/main.rs:2:14
  |
2 |     let x = (let y = 6);
  |              ^^^^^^^^^
  |
  = note: see issue #53667 <https://github.com/rust-lang/rust/issues/53667> for
more information

let y = 6の文は値を返さないため、xがバインドするものがありません。これは、C や Ruby などの他の言語とは異なります。それらの言語では、代入は代入の値を返します。それらの言語では、x = y = 6と書くことができ、xyの両方に値6が入ります。Rust ではそうではありません。

式は値に評価され、Rust で書くコードの残りの大部分を構成します。数学演算、たとえば5 + 6は、値11に評価される式です。式は文の一部になることができます。リスト 3-1 の文let y = 6;6は、値6に評価される式です。関数を呼び出すことは式です。マクロを呼び出すことは式です。波括弧で作成された新しいスコープブロックは式です。たとえば:

ファイル名:src/main.rs

fn main() {
  1 let y = {2
        let x = 3;
      3 x + 1
    };

    println!("The value of y is: {y}");
}

式[2]はブロックで、この場合、値4に評価されます。その値は、let文[1]の一部としてyにバインドされます。最後にセミコロンがない行[3]に注意してください。これは、これまで見てきたほとんどの行とは異なります。式には末尾のセミコロンは含まれません。式の末尾にセミコロンを追加すると、文に変換され、値を返さなくなります。次に関数の戻り値と式を調べる際には、これを覚えておいてください。

戻り値を持つ関数

関数は、それを呼び出すコードに値を返すことができます。戻り値には名前を付けませんが、矢印 (->) の後にその型を宣言する必要があります。Rust では、関数の戻り値は、関数本体のブロック内の最終式の値と同義です。return キーワードを使って値を指定することで、関数から早期に戻ることができますが、ほとんどの関数は最後の式を暗黙的に返します。戻り値を持つ関数の例を以下に示します。

ファイル名:src/main.rs

fn five() -> i32 {
    5
}

fn main() {
    let x = five();

    println!("The value of x is: {x}");
}

five 関数には関数呼び出しもマクロも、さらに let 文すらありません。ただ数字 5 だけです。これは Rust では完全に有効な関数です。関数の戻り型も -> i32 と指定されていることに注意してください。このコードを実行してみてください。出力は以下のようになるはずです。

$ cargo run
   Compiling functions v0.1.0 (file:///projects/functions)
    Finished dev [unoptimized + debuginfo] target(s) in 0.30s
     Running `target/debug/functions`
The value of x is: 5

five の中の 5 は関数の戻り値です。これが戻り型が i32 である理由です。もう少し詳しく見てみましょう。重要な点は 2 つあります。まず、let x = five(); の行は、関数の戻り値を使って変数を初期化していることを示しています。関数 five5 を返すので、この行は以下と同じです。

let x = 5;

2 番目に、five 関数にはパラメータがなく、戻り値の型が定義されていますが、関数本体はセミコロンのない孤独な 5 です。なぜなら、それは戻したい値の式だからです。

もう 1 つの例を見てみましょう。

ファイル名:src/main.rs

fn main() {
    let x = plus_one(5);

    println!("The value of x is: {x}");
}

fn plus_one(x: i32) -> i32 {
    x + 1
}

このコードを実行すると、The value of x is: 6 が表示されます。しかし、x + 1 を含む行の末尾にセミコロンを置くと、式を文に変えてしまうので、エラーが発生します。

ファイル名:src/main.rs

fn main() {
    let x = plus_one(5);

    println!("The value of x is: {x}");
}

fn plus_one(x: i32) -> i32 {
    x + 1;
}

このコードをコンパイルすると、以下のようなエラーが発生します。

$ cargo run
   Compiling functions v0.1.0 (file:///projects/functions)
error[E0308]: mismatched types
 --> src/main.rs:7:24
  |
7 | fn plus_one(x: i32) -> i32 {
  |    --------            ^^^ expected `i32`, found `()`
  |    |
  |    implicitly returns `()` as its body has no tail or `return` expression
8 |     x + 1;
  |          - help: remove this semicolon

主なエラーメッセージである mismatched types は、このコードの核心問題を明らかにしています。plus_one 関数の定義は、i32 を返すと言っていますが、文は値に評価されません。これは、単位型である () で表されます。したがって、何も返されず、関数定義と矛盾してエラーが発生します。この出力では、Rust がこの問題を修正するためのメッセージを提供しています。セミコロンを削除することでエラーが解消されます。

まとめ

おめでとうございます!あなたは関数の実験を完了しました。あなたのスキルを向上させるために、LabEx でさらに多くの実験を行って練習してください。