web-dev-qa-db-ja.com

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

私はいくつかのRust &Stringを引数として:

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
}

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

96
Shepmaster

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


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

  2. &String&Vecまたは&Boxも受け入れます必須関数を呼び出す前にヒープに割り当てられる引数。 &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を取得してから実行する必要があるため、不要な間接レイヤーを導入することです&strで終わる2番目の逆参照。

代わりに、string slice&str)、slice&[T])、または単なる参照(&T)。 &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"または割り当てられたStringで呼び出すことができます。 total_priceは、配列への参照で呼び出すことができます(&[1, 2, 3]または割り当てられたVec


StringまたはVec<T>からアイテムを追加または削除する場合は、mutable reference&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();
    }
}
124
Shepmaster

Shepmaster's answer に加えて、&str(および同様に&[T]など)を受け入れる別の理由は、他のすべてのタイプによるものです。他に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]>などでも機能します。

14
Peter Hall