関数の引数として文字列(&String)、Vec(&Vec)、またはボックス(&Box)への参照を受け入れることが推奨されないのはなぜですか?


126

私は&String引数としてaを取るRustコードをいくつか書きました:

fn awesome_greeting(name: &String) {
    println!("Wow, you are awesome, {}!", name);
}

Vecまたはへの参照を取り込むコードも作成しましたBox

fn total_price(prices: &Vec<i32>) -> i32 {
    prices.iter().sum()
}

fn is_even(value: &Box<i32>) -> bool {
    **value % 2 == 0
}

しかし、私はこのようにするのは良い考えではないというフィードバックを受け取りました。何故なの?

回答:


161

TL; DR:を代わりに使用&str&[T]たり&T、より一般的なコードを許可したりできます。


  1. a Stringまたはa を使用する主な理由の1つは、Vec容量を増減できるからです。ただし、不変の参照を受け入れる場合、Vecまたはでこれらの興味深いメソッドを使用することはできませんString

  2. 受け入れる&String&Vecまたは&Boxもすることは必要で関数を呼び出すことができます前に、ヒープ上に割り当てられる引数を。aを受け入れると&str、文字列リテラル(プログラムデータに保存)が許可され、&[T]またはを受け入れると&T、スタックに割り当てられた配列または変数が許可されます。不要な割り当てはパフォーマンスの低下です。これは通常、テストまたはメソッドでこれらのメソッドを呼び出そうとするとすぐに公開されますmain

    awesome_greeting(&String::from("Anna"));
    total_price(&vec![42, 13, 1337])
    is_even(&Box::new(42))
  3. もう1つのパフォーマンスの考慮事項は、&String&Vecあり&Box、を逆参照して&Stringを取得しString、2番目の逆参照を実行して最終的にになるため、不必要な間接層を導入し&strます。

代わりに、文字列スライス&str)、スライス&[T])、または参照(&T)のみを受け入れる必要があります。A &String&Vec<T>または&Box<T>自動的に強制変換されます&str&[T]または&Tそれぞれ。

fn awesome_greeting(name: &str) {
    println!("Wow, you are awesome, {}!", name);
}
fn total_price(prices: &[i32]) -> i32 {
    prices.iter().sum()
}
fn is_even(value: &i32) -> bool {
    *value % 2 == 0
}

これで、これらのメソッドをより幅広いタイプのセットで呼び出すことができます。たとえばawesome_greeting、文字列リテラル("Anna"または割り当てられたを使用して呼び出すことができますStringtotal_price配列(&[1, 2, 3]または割り当てられたを参照して呼び出すことができますVec


追加またはから項目を削除したい場合StringVec<T>、あなたが取ることができる可変の参照を&mut Stringまたは&mut Vec<T>):

fn add_greeting_target(greeting: &mut String) {
    greeting.push_str("world!");
}
fn add_candy_prices(prices: &mut Vec<i32>) {
    prices.push(5);
    prices.push(25);
}

特にスライスの場合は、&mut [T]またはも受け入れることができます&mut str。これにより、スライス内の特定の値を変更できますが、スライス内のアイテム数を変更することはできません(つまり、文字列に対して非常に制限されています)。

fn reset_first_price(prices: &mut [i32]) {
    prices[0] = 0;
}
fn lowercase_first_ascii_character(s: &mut str) {
    if let Some(f) = s.get_mut(0..1) {
        f.make_ascii_lowercase();
    }
}

5
最初はtl; drはどうですか?この答えはすでにいくらか長いです。「&str機能は制限されず、より一般的です(制限が少ないなど)」?また、ポイント3は多くの場合、それほど重要ではないと思います。通常、VecsとStringsはスタック上にあり、多くの場合、現在のスタックフレームの近くのどこかにさえ存在します。スタックは通常ホットで、逆参照はCPUキャッシュから提供されます。
Lukas Kalbertodt、2016年

3
@Shepmaster:割り当てコストに関しては、必須の割り当てについて話すときに、部分文字列/スライスの特定の問題について言及する価値があるかもしれません。total_price(&prices[0..4])スライスに新しいベクトルを割り当てる必要はありません。
Matthieu M.16年

4
これは素晴らしい答えです。私はRustを使い始めたばかりで、a &strとその理由(Pythonから来たので、通常は明示的に型を扱っていません)をいつ使用する必要があるを把握するのに専念しています。そのすべてを完璧にクリアしました
C.Nivs

2
パラメータに関する素晴らしいヒント。ちょうど1つの疑いが必要:「あなたがメソッドを呼び出すことができます前に、&文字列を受け入れ、&​​Vecをまたは&ボックスも割り当てが必要です。」 ...それはそうであるのはなぜ?私がこれを詳細に読むことができるドキュメントの部分を指摘していただけませんか?(私は初心者です)。また、戻り値の型についても同様のヒントがありますか?
Nawaz

2
追加の割り当てが必要な理由に関する情報が不足しています。文字列はヒープに格納され、&Stringを引数として受け入れると、スタックに格納されたヒープ領域を指すポインターを単にRustに渡さないだけでなく、&Stringを渡すと追加の割り当てが必要になり、文字列を渡す必要がある理由がわかりませんまた、スライスは、ヒープ領域を指すスタックに格納されたポインタを送信する必要がありますか?
cjohansson

22

Shepmasterの回答に加えて、&str(および同様に&[T])を受け入れるもう1つの理由は、他のすべてのタイプのほか String&strそれも満たすためDeref<Target = str>です。最も注目すべき例の1つはですCow<str>。これにより、所有データと借用データのどちらを処理するかについて非常に柔軟に対応できます。

あなたが持っている場合:

fn awesome_greeting(name: &String) {
    println!("Wow, you are awesome, {}!", name);
}

しかし、これをで呼び出す必要がありCow<str>ます。これを行う必要があります。

let c: Cow<str> = Cow::from("hello");
// Allocate an owned String from a str reference and then makes a reference to it anyway!
awesome_greeting(&c.to_string());

引数の型をに変更すると&str、次のようCowに、不要な割り当てなしでシームレスに使用できますString

let c: Cow<str> = Cow::from("hello");
// Just pass the same reference along
awesome_greeting(&c);

let c: Cow<str> = Cow::from(String::from("hello"));
// Pass a reference to the owned string that you already have
awesome_greeting(&c);

受け入れる&strと、関数の呼び出しがより均一で便利になり、「最も簡単な」方法も最も効率的になります。これらの例も同様に動作しますCow<[T]>

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.