引数には借用型を使用する
説明
関数の引数に使用する引数型を決めるとき、Deref 型強制の対象を使用すると、コードの柔軟性を高めることができます。このようにすると、関数はより多くの入力型を受け付けるようになります。
これは、スライス化可能な型やファットポインター型に限りません。実際には、常に所有型を借用することよりも借用型を使用することを優先すべきです。たとえば、&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" を使ったときのように)、と。この特殊な例を無視したとしても、&String を使用するよりも &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 ではないためです。&String に変換するにはアロケーションが必要であり、それは暗黙的には行われません。一方、String から &str への変換は低コストで暗黙的に行われます。
関連項目
- 型強制に関する Rust 言語リファレンス
Stringと&strの扱い方に関するさらなる議論については、Herman J. Radtke III による このブログシリーズ(2015) を参照してください- Steve Klabnik のブログ記事「When should I use String vs &str?」