#20 Rustで学ぶ関数の応用(デフォルト引数・可変長引数)の基本
はじめに
PythonやJavaScriptで関数を書いていると、「引数を省略したら自動でデフォルトの値が使われる」機能や、「引数の数を自由に増やせる」機能を使ったことがある人は多いと思います。たとえばPythonなら def greet(name, greeting="こんにちは") のように、引数に最初から値を入れておく「デフォルト引数」が書けます。また、def total(*numbers) のように、いくつでも数値を渡せる「可変長引数」も気軽に書けます。
ところが、Rustにはこの2つの機能が言語として用意されていません。最初は「不便だな」と感じるかもしれませんが、Rustには代わりの書き方がいくつも用意されています。今回は、Rustで「デフォルト引数っぽいこと」「可変長引数っぽいこと」を実現する方法を、初心者向けにやさしく紹介します。
なぜRustにはデフォルト引数がないのか
Rustは「関数のシグネチャ(引数の型や数などの定義)を見ただけで、呼び出し方を誤解なく判断できる」ことを重視している言語です。デフォルト引数を許すと、呼び出す側がどの引数を省略したのか読み取りにくくなる場合があるため、あえて言語機能として採用していません。その代わり、次のような書き方で似たことを実現できます。
方法1: Option型を使う
Option型とは、「値があるかもしれないし、ないかもしれない」ことを表すRust特有の型です。値がある場合はSome(値)、ない場合はNoneという形で表現します。これを引数に使うと、「省略したい場合はNoneを渡す」という形でデフォルト引数のような動きを再現できます。
fn greet(name: &str, greeting: Option<&str>) {
let greeting = greeting.unwrap_or("こんにちは");
println!("{}、{}さん", greeting, name);
}
fn main() {
greet("太郎", None); // こんにちは、太郎さん
greet("花子", Some("やあ")); // やあ、花子さん
}
unwrap_or(値)は、「Someに中身があればその値を、Noneであれば指定した値を使う」というメソッドです。省略時のデフォルト値をここでまとめて指定できます。
方法2: 関数を複数用意する(オーバーロード風)
Rustは同じ名前の関数を引数の数だけ用意する「オーバーロード」(同名で引数の型や数が違う関数を複数定義する仕組み)も直接はサポートしていません。ただし、引数が少ないパターン専用の関数を別名で用意し、その中から本体の関数を呼ぶという方法はよく使われます。
fn greet_default(name: &str) {
greet(name, "こんにちは");
}
fn greet(name: &str, greeting: &str) {
println!("{}、{}さん", greeting, name);
}
シンプルな場面ではこちらのほうが読みやすいこともあります。
方法3: Builderパターンを使う
設定項目が多い場合は、Builderパターンという設計方法もよく使われます。これは、値を1つずつメソッドで設定していき、最後に組み立てて完成させるという書き方です。設定しなかった項目には、あらかじめ決めておいたデフォルト値が使われます。
struct Config {
width: u32,
height: u32,
}
struct ConfigBuilder {
width: u32,
height: u32,
}
impl ConfigBuilder {
fn new() -> Self {
ConfigBuilder { width: 800, height: 600 } // デフォルト値
}
fn width(mut self, w: u32) -> Self {
self.width = w;
self
}
fn build(self) -> Config {
Config { width: self.width, height: self.height }
}
}
設定項目が多いライブラリのコードなどでよく見かける形なので、名前だけでも覚えておくと読みやすくなります。
可変長引数の代わりにできること
次に、「引数をいくつ渡してもいい」可変長引数についてです。RustにはPythonの*argsのような機能はありませんが、代わりに次の方法があります。
スライスやベクタを渡す
スライスとは、配列やベクタの中身を「参照」の形でひとまとめに受け取れる型です(&[i32]のように書きます)。ベクタ(Vec)は、要素数を後から自由に増減できる配列のようなものです。関数の引数にスライスを受け取るようにしておけば、呼び出す側で好きな数の値をまとめて渡せます。
fn total(numbers: &[i32]) -> i32 {
numbers.iter().sum()
}
fn main() {
println!("{}", total(&[1, 2, 3])); // 6
println!("{}", total(&[10, 20, 30, 40])); // 100
}
呼び出し側で配列やベクタを作ってから渡す、というひと手間は必要ですが、これがRustにおける「可変長引数っぽい」書き方の基本形です。
マクロを使う方法
もう少し踏み込むと、Rustにはマクロという仕組みもあります。マクロとは、コードを生成するためのコードのことで、標準ライブラリのvec!もマクロの一種です。vec![1, 2, 3]と書くと、いくつ要素を並べてもベクタが作れますよね。これと同じ仕組みを自分で定義することで、独自の「可変長引数風」の書き方を作ることも可能です。ただし、マクロは初心者のうちは無理に使う必要はなく、「そういう仕組みがある」と知っておく程度で十分です。
まとめ
Rustにはデフォルト引数や可変長引数といった言語機能はありませんが、Option型、関数の使い分け、Builderパターン、スライスやVec、そしてマクロといった道具があります。これらを組み合わせることで、同じような使い勝手を実現できます。一見遠回りに見えますが、これらはどれも「呼び出し方があいまいにならないようにする」というRustらしい考え方に基づいた工夫です。最初はPythonなどとの違いに戸惑うかもしれませんが、慣れてくると「明示的に書けるからこそ安心できる」と感じられるはずです。まずはOption型とスライスの書き方から、実際に手を動かして試してみてください。
関連記事
本ページはプロモーションを含みます。
参考書籍(PR)
Rustをもっと学びたい方には「実践Rust入門[言語仕様から開発手法まで]」がおすすめです。
コメント
コメントを投稿