Rustでは、値に名前を付けることを バインディング と呼びます。バインディングは、mutキーワードを付けて宣言しない限りイミュータブルです。Rustは静的型付け言語なので、それぞれのバインディングにはコンパイル時に判明する型があります。
バインディングは、たいていletキーワードを使って定義します。ほとんどのバインディングでは、型の指定は任意です。Rustの 型推論 が、値から型を推測してくれるからです。バインディングは、次のように書きます。
// Automatically inferred type
let fingers = 10;
関数は アイテム です。バインディングが特定の値を指すのに対して、アイテムはコードを整理する単位を指し、ふつうは関数やモジュールで、プログラムのライフタイムを通じて利用できます。関数は、最後の式の結果を自動的に返します。関数は0個以上の仮引数を持てます。仮引数は、その関数呼び出しのあいだだけ生きるバインディングです。
型推論は、関数でも理論上は可能ですが、言語設計上の意図的な選択として無効になっています。つまり、コードを書くときには、関数の入力と出力の型を正確に指定するために少し時間がかかりますが、コードを読むときには、入力と出力の型がすべて明示されているので、その分の時間を節約できます。
fn add(x: i32, y: i32) -> i32 {
x + y
}
関数を呼び出すには、関数名のあとに括弧(())を書きます。関数が仮引数を必要とする場合は、括弧の中にそれぞれの引数を指定します。
let five = add(2, 3);
バインディングの型を推測できない場合、コンパイラーはエラーを報告します。これを解決するには、バインディングに型注釈を明示的に付けます。
// Explicit type annotation
let fingers: i32 = 10;
Rustのアイテムは、定義する前でも後でも使えます。静的なライフタイムを持つからです。一方、バインディングは、定義した 後 でしか使えません。定義前にバインディングを使うと、コンパイルエラーになります。
fn main() {
// `fn add` hasn't yet been defined, but that's perfectly ok
dbg!(add(3, 4));
}
fn add(x: i32, y: i32) -> i32 {
x + y
}
// this won't compile; `a` is used before its binding is defined
let b = a;
let a = x + y;
Rustでは、波括弧({})を使ってスコープを定義します。スコープの中で定義されたバインディングは、そのスコープから抜け出すことはできません。
let a = 1;
dbg!(a); // 1
{
// Here, we re-bind `a` to a new value, which is still immutable.
// This technique is called _shadowing_. The new binding is constrained to
// this anonymous scope. Outside this scope, the previous binding still
// applies.
let a = 2;
let b = 3;
dbg!(a, b); // 2, 3
}
// can't use `b` anymore because it is out of scope
// dbg!(b);
// The shadowed `a` in the inner scope above has fallen out of scope,
// leaving us with our original binding.
dbg!(a); // 1
Rustのアイテムは、よくモジュールにまとめられます。クレートは暗黙的にモジュールですが、その中に任意の深さのサブモジュールを定義できます。モジュールは関連する機能をひとまとめにするもので、modキーワードを使って定義します。
mod calc_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 div(a: i32, b: i32) -> i32 { a / b }
}
Rustには2種類のコメントがあります。//というキーワードは1行コメントを表し、キーワードから行末までのすべてが無視されます。/*と*/というキーワードは複数行コメントを表し、その2つのキーワードの間にあるすべてが無視されます。1行コメントを好むのが慣習的で、よい習慣です。
Rustはドキュメントコメントもサポートしています。これはcargo docが生成するドキュメントに表示されます。外側ドキュメントコメントは///というキーワードで書き、//キーワードとまったく同じはたらきをします。続くアイテム(関数など)に適用されます。
/// The `add` function produces the sum of its arguments.
fn add(x: i32, y: i32) -> i32 { x + y }
内側ドキュメントコメントは//!というキーワードで書き、//キーワードとまったく同じはたらきをします。それを囲むアイテム(モジュールなど)に適用されます。
mod my_cool_module {
//! This module is the bee's knees.
}
ドキュメントコメントは長さに制限がなく、Markdownを含めることができ、生成されるドキュメントにレンダリングされます。
この演習では、お気に入りの料理本に載っている最高のラザニアを作るために、コードを書いていきます。
タスクは4つあり、どれもラザニアの調理にかかる時間に関するものです。
expected_minutes_in_ovenという束縛を定義して、ラザニアをオーブンに入れておくべき時間が何分かを確認できるようにします。料理本によると、オーブンに入れておくべき時間は40分です。
expected_minutes_in_oven()
// Returns: 40
remaining_minutes_in_oven関数を定義します。この関数は、ラザニアが実際にオーブンに入っていた分数を入力として受け取り、前のタスクで求めたオーブンに入れておくべき時間をもとに、ラザニアをあと何分オーブンに入れておく必要があるかを返します。
remaining_minutes_in_oven(30)
// Returns: 10
preparation_time_in_minutes関数を定義します。この関数は、ラザニアに重ねた層の数を入力として受け取り、1層あたり2分かかると仮定して、ラザニアの準備に何分かかったかを返します。
preparation_time_in_minutes(2)
// Returns: 4
elapsed_time_in_minutes関数を定義します。この関数は2つの入力を受け取ります。1つ目の入力はラザニアに重ねた層の数で、2つ目の入力はラザニアがオーブンに入っていた分数です。この関数は、ラザニアの調理に何分取り組んだかを返します。これは、準備にかかった分数と、その時点でラザニアがオーブンに入っていた分数の合計です。
elapsed_time_in_minutes(3, 20)
// Returns: 26