Rustについて基本からしっかり学んでいく本連載。第4回は関数や引数の基本について。
この記事は会員限定です。会員登録(無料)すると全てご覧いただけます。
Rustについて基本からしっかり学んでいく本連載。連載第4回は、関数についてRustでどう書くかを紹介します。
Rustにおける関数は、C/C++のようなプログラミング言語における関数と変わりません。ある特定の機能を実現するものとして、引数を受け取り、処理の結果としての戻り値を返します。引数がない関数、戻り値のない関数がある点も同様です。
引数も戻り値もない関数は以下のように定義します。関数はネストできないので、main関数の外などグローバルに定義する必要があります。また、関数定義の場所はコンパイラが見つけることができる限り、どこでも構いません。main関数の後でも構いません。
fn function_name() { 関数の処理… }
関数定義は「fn」(fnはfunctionの意)と関数名を記述します。変数名やパッケージ名もそうであったように、関数名はスネークケース(全て小文字の英単語をアンダースコア〈_〉で区切った形式)を用いるのがRustの流儀です。引数も戻り値もない関数simple_functionを定義し、呼び出す例を示します。
fn main() { println!("シンプルな関数を呼び出します。"); simple_function(); // simple_function関数を呼び出す println!("関数呼び出しが終了しました。"); } // simple_functionの定義 fn simple_function() { // simple_functionの処理内容 println!("simple_functionは何も受け取らないし何も返しません。"); }
% cargo run --bin func1 シンプルな関数を呼び出します。 simple_functionは何も受け取らないし何も返しません。 関数呼び出しが終了しました。
main関数はバイナリが実行されるときに最初に呼び出される関数でもあります(「エントリーポイント」と呼ばれます)。main関数に引数はなく、戻り値もありません。C/C++のようにmain関数の引数にコマンドライン引数が渡されることはありません。Rustでは、コマンドライン引数の取得には専用のモジュールを使用しますが、これは入出力の回で取り上げる予定です。
simple_function関数も引数はありません。戻り値もないため、単純に何かをするだけという関数になっています。
Cでは、関数呼び出しより後に関数の定義がある場合には、あらかじめプロトタイプ宣言を関数呼び出しの前に記述する必要がありましたが、Rustではこのような二重定義は不要となっています。
引数のある関数は以下のように定義します。
fn function_name(arg: type[, ...]) { 関数の処理… }
引数(parameter:仮引数)は関数名に続く小かっこの中に型修飾とともに列挙します。引数の型修飾は、変数の宣言と異なり省略できません(型推論は利用できません)。引数が2個以上になる場合には、カンマで区切ります。i32型の引数が2個ある関数を定義し、それを呼び出す例を示します。
fn main() { display_sum(10, 5); // display_sum関数を実引数10と5で呼び出す } // display_sumの定義 fn display_sum(a: i32, b: i32) { // 仮引数はi32型のaとb println!("2つの引数の和は{}です。", a + b); // display_sumの処理内容 // 「2つの引数の和は15です。」 }
display_sumにはi32型のaとbという名前の引数が渡されます。aとbは仮引数としてdisplay_sumの中で利用され、処理の結果としてaとbの和の値が表示されます。main関数では、display_sum関数に引数(argument:実引数)として「10」と「5」を与えて呼び出します。
戻り値のある関数は以下のように定義します。
fn function_name(arg: type[, ...]) -> type { 関数の処理… 戻り値 }
関数に戻り値がある場合、アロー演算子「->」を利用して戻り値の型を指定します。また、戻り値はreturn式で返すか、最後に評価された式の値で返されます。関数の途中で明示的に抜けたいということでない限りは、後者の方法がRustらしいといえるでしょう。
fn main() { let a = 5; let b = 10; println!("{}の二乗は{}、{}の二乗は{}です。", a, square1(a), b, square2(b)); // 「5の二乗は25、10の二乗は100です。」 } // square1関数の定義 fn square1(x: i32) -> i64 { // 仮引数はi32型のx、戻り値はi64 return x as i64 * x as i64; // return式で返す } // square2関数の定義 fn square2(x: i32) -> i64 { // 仮引数はi32型のx、戻り値はi64 x as i64 * x as i64 // 式で返す }
square1関数は、return式で関数の処理結果を返す、C/C++でもおなじみの形式です。square2関数は、計算式がぽつんとあるだけで違和感があるかもしれませんが、x*xの評価結果が関数の戻り値になります。
引数はi32型ですが、その2乗を返すのでオーバフローを考慮して戻り値はi64型としています。計算式を見ると「as」というキーワードが出てきますが、これは「型キャスト」のための演算子です。なぜここで型キャストが必要なのかというと、戻り値がi64型なので、あらかじめi64型として乗算する必要があるからです。
ちなみに、オーバフローを考慮せず戻り値の型をi64型としたい場合には、以下のように書きます。intoは「トレイト」と呼ばれる関数の一つで、コンテキストに合った型(この場合はi64型)に自動的に変換されます。トレイトは、構造体の回で取り上げる予定です。
(x * x).into()
なお、戻り値がない関数は、空のタプルである「()」を返す関数となっています。すなわち、以下のように書くのと同義です。「()」を返すからといって、関数を呼び出した側でそれを受け取る必要はありません。
fn square1() -> () …
引数のある関数の定義では、関数が引数を受け取って計算式に利用できることを示しました。では次のコードを見てください。このコードは、コンパイルするとエラーになります。
// main()関数は省略 // xのy倍を繰り返しで求める関数 fn multiplier(x: i32, y: i32) -> i32 { let mut r = 0; // 戻り値の変数 while y > 0 { r += x; y -= 1; } r // 戻り値 }
% cargo run --bin func4 Compiling functions v0.1.0 (/Users/nao/Documents/atmarkit_rust/functions) error[E0384]: cannot assign to immutable argument `y` --> src/bin/func4.rs:10:9 | 6 | fn multiplier(x: i32, y: i32) -> i32 { | - help: consider making this binding mutable: `mut y` ... 10 | y -= 1; | ^^^^^^ cannot assign to immutable argument
不変変数yへの代入はできないというエラーです。というのも、関数定義の引数においてmut修飾子が記述されていないので、この引数も不変変数になるのです。関数定義の引数にもmut修飾子を付けることで解決できます。
// main()関数は省略 fn multiplier(x: i32, mut y: i32) -> i32 { // yにmutを付加 let mut r = 0; while y > 0 { r += x; y -= 1; } r }
関数から戻り値を返すreturn式は、その場所で関数から抜ける動作になります。return式が置かれた場所より下にあるコードは一切実行されません。
Copyright © ITmedia, Inc. All Rights Reserved.