引数には借用型(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

Last change: 2024-07-09, commit: 317c88e