引数には借用型(borrowed types)を使用する
説明
関数の引数にどの型を使うかを決めるときに、参照外し型強制(deref coercion)の対象を使うことで、コードの柔軟性を高めることができます。この方法により、関数はより多くの型を入力として受け入れられるようになります。
これはスライス可能な型やファットポインタ型に限ったことではありません。実際、所有型の借用よりも 借用型 を常に使用することをお勧めします。例えば、 &String
よりも &str
の方が、 &Vec<T>
よりも &[T]
の方が、 &Box<T>
よりも &T
の方が優れています。
借用型を使えば、所有型がすでに間接的なレイヤーを提供しているような場合に、そのインスタンスに対する多重の間接的なレイヤーを避けることができます。例えば String
には間接的なレイヤーがあるので、 &String
は2つの間接的なレイヤーを持つことになってしまいます。 代わりに &str
を使用し、関数呼び出し毎に &String
を &str
に型強制することでこれを避けることができます。
例
この例では、関数の引数に &String
を使用した場合に対して、 &str
を使用した場合との違いを説明します。なお、ここで示すものは Vec<T>
に対しての &[T]
や、 &Box<T>
に対しての &T
、などあっても同様のことが言えます。
例として、ある単語が連続した3つの母音を含むかどうかを調べたい場合を考えてみましょう。調べるにあたって、対象の文字列を所有する必要はありません。私たちは参照を使うでしょう。
コードは次のようになります:
fn three_vowels(word: &String) -> bool { let mut vowel_count = 0; for c in word.chars() { match c { 'a' | 'e' | 'i' | 'o' | 'u' => { vowel_count += 1; if vowel_count >= 3 { return true; } } _ => vowel_count = 0, } } false } fn main() { let ferris = "Ferris".to_string(); let curious = "Curious".to_string(); println!("{}: {}", ferris, three_vowels(&ferris)); println!("{}: {}", curious, three_vowels(&curious)); // これはうまくいくが、次の2行は失敗する: // println!("Ferris: {}", three_vowels("Ferris")); // println!("Curious: {}", three_vowels("Curious")); }
これは &String
型をパラメータとして渡しているため、問題なく動作します。最後の2行のコメントを削除すると、この例は失敗します。これは &str
型が &String
型に型強制されないからです。この問題を、単に引数の型を変更することで修正できます。
例えば、関数宣言を次のように変更します:
fn three_vowels(word: &str) -> bool {
すると、上述の例のどちらのバージョンもコンパイルされ、同じ出力が表示されます。
Ferris: false
Curious: true
でも待ってください!この話にはまだ続きがあります。もしかしたら、あなたは「 &'static str
を(上述の例での "Ferris"
で行ったように)入力に使うことは絶対ない・・・どうでもいいかな」と思っているかもしれません。または、この特別な例を無視しても、&str
を使用する方が&'static str
を使用するよりも柔軟性があることに気づくかもしれません。
では、誰かが私たちに文章を与えたとして、その中に3つの母音が連続する単語があるかどうかを調べる例を考えてみましょう。この場合、すでに定義した関数を利用すべきで、単に文中の単語をこの関数に与えるべきでしょう。
この例は次のようなものです:
fn three_vowels(word: &str) -> bool { let mut vowel_count = 0; for c in word.chars() { match c { 'a' | 'e' | 'i' | 'o' | 'u' => { vowel_count += 1; if vowel_count >= 3 { return true; } } _ => vowel_count = 0, } } false } fn main() { let sentence_string = "Once upon a time, there was a friendly curious crab named Ferris".to_string(); for word in sentence_string.split(' ') { if three_vowels(word) { println!("{word} has three consecutive vowels!"); } } }
引数を &str
として宣言した関数を使用してこの例を実行すると、次のようになるでしょう
curious has three consecutive vowels!
しかし私たちが関数の引数型を &String
として宣言していた場合には、この例は実行できません。これは、文字列スライスが &str
であり、&String
ではないためです。 &str
は &String
に変換するためにはアロケーションを必要とし、暗黙期には変換されません。対して String
は &str
に安価かつ暗黙的に変換されます。
See also
- 型強制(Type coercions)に関するRust言語リファレンス
String
と&str
の扱い方に関する議論は、Herman J. Radtke III の このブログのシリーズ(2015) を見てください