Rustの関数のメモ。
Rustでは、関数(やメソッド)はfnで宣言する。(fnはfunctionの略だろう)
〔可視性〕fn 関数名(引数名: 型, …) { 文; 〜 文; }
〔可視性〕fn 関数名(引数名: 型, …) -> 戻り型 { 文; 〜 文; 式 // 最後の式の値が戻り値になる。このとき、式の末尾にセミコロン「;」は付けない(ブロックと同様) }
関数名はsnake_caseで命名する。
関数名をアンダースコア「_」で始めると、未使用関数の警告が出なくなる。
関数の戻り値がある場合は、関数内の最後の式の値が戻り値になる。
returnで早期リターンすることも出来る。
関数やメソッドはオーバーロードできない。(同名の関数を定義できない)
ただし、異なるトレイトが同名の関数を持っている場合には、両方とも実装することが出来る。(結果として、同名の関数を複数持っている状態になる)
→オーバーロードの例
非同期関数の場合は可視性とfnの間にasyncを入れる。[2024-12-14]
関数の呼び出しは以下のようにする。
関数名(値, …)
let 変数 = 関数名(値, …);
非同期関数を呼び出す場合は末尾にawaitを付ける。[2024-12-14]
let 変数 = 関数名(値, …).await;
println!()
のように名前の末尾に「!
」が付いているものは、関数呼び出しのように見えるが関数ではなく、マクロの呼び出し(展開)である。
Rustの関数の引数には、可変長引数は無い。
似たことを実現するには、スライスを受け取るようにするらしい。
また、マクロは可変長引数を受け取れる。
(println!()
なんかは可変長引数である)
fnの前にconstを付けると、その関数の呼び出しはコンパイル時に評価される。[2024-09-18]
〔可視性〕const fn 関数名(引数名: 型, …) -> 戻り型 { 〜 }
const fnの場合、処理本体に書ける内容は、通常の関数と比べて制限がある。
関数の例。
例 | 説明 | Java相当 | Scala相当 |
---|---|---|---|
fn main() { my_func(123); } fn my_func(n: i32) { println!("my_func({})", n); } |
戻り値の無い関数の例。 |
〜 void main(String... args) { myFunc(123) } void myFunc(int n) { System.out.printf("myFunc(%d)\n", n) } |
def main(args: Array[String]): Unit = { myFunc(123) } def myFunc(n: Int): Unit = { println(s"myFunc($n)") } |
fn main() { let r = my_func(123); println!("r = {}", r); } fn my_func(n: i32) -> i32 { n + 1 // 末尾にセミコロンを付けない } |
戻り値のある関数の例。 |
〜 void main(String... args) { var r = myFunc(123) System.out.printf("r = %d\n", r) } int myFunc(int n) { return n + 1; } |
def main(args: Array[String]): Unit = { val r = myFunc(123) println(s"r = $r") } def myFunc(n: Int): Int = n + 1 |
引数の定義方法は「引数名: 型
」だが、値の受け取り方に関していくつかの方法がある。
内容 | 定義方法 | 説明 |
---|---|---|
不変 |
fn f(…, 引数名: 型, …) |
所有権の対象となる型の場合は呼び出し元の変数から所有権を奪うので、基本的には使わない方が良さそう。 所有権の対象にならない型(主に基本データ型(整数や浮動小数など))の場合はこの形式で問題ない。 |
不変参照 |
fn f(…, 引数名: &型, …) |
参照を受け取る。 所有権の対象となる型の場合、所有権は呼び出し側にそのまま残る。 |
可変 |
fn f(…, 引数名: mut 型, …) |
型が構造体の場合、そのフィールドの値を変更したい場合はmutを付ける。 |
可変参照 |
fn f(…, 引数名: &mut 型, …) |
mutが付いていない場合、その引数は読み取り専用である。
参照を受け取る関数を呼び出す際は、呼び出す方の変数にも「&」を付ける。
let s = 〜; f(…, &s, …);
src/main.rsに書かれたmain関数は、アプリケーションの実行時に呼ばれる。
fn main() { println!("アプリケーション実行時に呼ばれる"); }
main関数にasyncを付けたい場合は、#[tokio::main]を使う。[2024-12-15]
多くのプログラミング言語では、mainの引数にコマンドライン引数(アプリケーション実行時に指定されたパラメーター)が渡ってくる。
(例えばJavaではmainメソッドの定義はmain(String... args)
となっている。C言語由来の伝統かな^^;)
しかしRustではmain関数の引数は無い。
Rustでコマンドライン引数を受け取りたい場合は、std::envのargs()を使用する。
use std::env; fn main() { let args: Vec<String> = env::args().collect(); println!("{:?}", args); }
↓Windowsでの実行例
> cargo run aa bb cc 〜 ["target\\debug\\rust-example1.exe", "aa", "bb", "cc"]
main関数からResultを返すことが出来る。
use std::num::ParseIntError; fn main() -> Result<(), ParseIntError> { let number_str = "10"; let number = match number_str.parse::<i32>() { Ok(number) => number, Err(e) => return Err(e), }; println!("{}", number); Ok(()) }
never型は、発散する関数の戻り型として利用できる。
(never型はScalaのNothing相当)
発散する関数(diverging function)とは、関数の呼び出し元に返らない関数(returnしない関数)のこと。
例えばエラー(panic)を起こして異常終了する関数は、関数の呼び出し元に制御が返らない。
こういう関数の戻り値の型にnever型を指定する。
never型は「!
」で表す。
fn my_func() -> ! { panic!("never型の実験"); }
上記の概要で述べた「戻り値の無い関数」は、厳密には戻り値が無いわけではない。
戻り値の型を宣言していない場合、実際には「()
」(ユニット型)が指定されており、空のタプルという値が返る。
(Scalaで戻り値を返さない場合にUnitを指定し、Unit唯一の値である()
が返るのと同様)
(戻り値が無いというのは、Javaで言えば「戻り型がvoidである」という意味)