Подтвердить что ты не робот

Почему не рекомендуется принимать ссылку на строку (& 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
}

Однако, я получил некоторую обратную связь, что делать это, как будто это не очень хорошая идея. Почему бы и нет?

4b9b3361

Ответ 1

TL; DR: вместо этого можно использовать &str, &[T] или &T для более общего кода.


  1. Одна из основных причин использования String или Vec заключается в том, что они позволяют увеличивать или уменьшать емкость. Однако, когда вы принимаете неизменную ссылку, вы не можете использовать любой из этих интересных методов в 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. Другое соображение производительности заключается в том, что &String, &Vec и &Box вводят ненужный уровень косвенности, поскольку вы должны разыменовать &String чтобы получить String а затем выполнить вторую разыменование, чтобы в конечном итоге получить &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") или выделенной String. total_price может быть вызван со ссылкой на массив (&[1, 2, 3]) или выделенный Vec.


Если вы хотите добавить или удалить элементы из String или Vec<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();
    }
}

Ответ 2

В дополнение к ответу Shepmaster, другой причиной для принятия &str (и аналогично &[T] т.д.) Deref<Target = str> все другие типы, кроме String и &str которые также удовлетворяют Deref<Target = str>. Одним из наиболее ярких примеров является 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);

Accepting &str делает вызов вашей функции более единообразным и удобным, и "самый простой" способ теперь также является наиболее эффективным. Эти примеры также будут работать с Cow<[T]> и т.д.