İşlev bağımsız değişkeni olarak String (& String), Vec (& Vec) veya Box (& Box) başvurusunu kabul etmek neden önerilmiyor?


128

&StringArgüman olarak a alan bir Rust kodu yazdım :

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

Ayrıca a Vecveya şuna başvuran bir kod yazdım Box:

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

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

Ancak, bunu böyle yapmanın iyi bir fikir olmadığı konusunda bazı geri bildirimler aldım. Neden olmasın?

Yanıtlar:


163

TL; DR: Bir yerine kullanabilir &str, &[T]ya da &Tdaha genel kod için izin vermek.


  1. A Stringveya a kullanmanın ana nedenlerinden biri Vec, kapasiteyi artırmaya veya azaltmaya izin vermeleridir. Ancak, değişmez bir referansı kabul ettiğinizde, bu ilginç yöntemlerin hiçbirini Vecveya üzerinde kullanamazsınız String.

  2. Bir Kabul &String, &Vecya &Boxda gerektirir işlevi çağırabilir önce öbek üzerinde tahsis edilecek argüman. A'nın kabul edilmesi &str, bir dizgeye (program verilerine kaydedilen) izin verir ve bir yığın tahsisli dizi veya değişkene izin verir &[T]veya kabul eder &T. Gereksiz ayırma bir performans kaybıdır. Bu genellikle, bu yöntemleri bir test veya yöntemde çağırmaya çalıştığınızda hemen ortaya çıkar main:

    awesome_greeting(&String::from("Anna"));
    total_price(&vec![42, 13, 1337])
    is_even(&Box::new(42))
  3. Başka bir performans dikkate yani &String, &Vecve &Boxsen KQUEUE sahip olarak dolaylama gereksiz tabaka oluşturulur &Stringbir almak Stringve daha sonra en sonuna kadar ikinci bir dereference gerçekleştirmek &str.

Bunun yerine, bir dize dilimi ( &str), bir dilim ( &[T]) veya sadece bir referansı ( &T) kabul etmelisiniz . A &String, &Vec<T>veya &Box<T>otomatik olarak sırasıyla a &str, &[T]veya &T'ye zorlanacaktır.

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
}

Artık bu yöntemleri daha geniş bir tür kümesiyle çağırabilirsiniz. Örneğin, awesome_greetingbir dize değişmezi ( "Anna") veya tahsis edilmiş olarak çağrılabilir String. total_pricebir dizi ( &[1, 2, 3]) veya ayrılmış bir referansla çağrılabilir Vec.


Eklemek veya öğeleri kaldırmak istiyorsanız Stringya Vec<T>, bir alabilir değişken referans ( &mut Stringya &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);
}

Özellikle dilimler için bir &mut [T]veya &mut str. Bu, dilim içindeki belirli bir değeri değiştirmenize izin verir, ancak dilim içindeki öğelerin sayısını değiştiremezsiniz (bu, dizeler için çok kısıtlı olduğu anlamına gelir):

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
Başlangıçta bir tl; dr'ye ne dersiniz ? Bu cevap zaten biraz uzun. " &strDaha geneldir (olduğu gibi: daha az kısıtlama getirir), sınırlı yetenekler olmadan"? Ayrıca: 3. nokta genellikle sanırım o kadar da önemli değil. Genellikle Vecs ve Strings'ler yığın üzerinde ve hatta mevcut yığın çerçevesine yakın bir yerde yaşarlar. Yığın genellikle sıcaktır ve referans bir CPU önbelleğinden sunulacaktır.
Lukas Kalbertodt

3
@Shepmaster: Tahsisat maliyeti ile ilgili olarak, zorunlu tahsis hakkında konuşurken alt dizeler / dilimlerin özel sorunundan bahsetmeye değer olabilir. total_price(&prices[0..4])dilim için yeni bir vektör tahsis etmeyi gerektirmez.
Matthieu M.

4
Bu harika bir cevap. Rust'ta yeni başlıyorum ve ne zaman &strve neden kullanmam gerektiğini bulmaya çalışıyordum (Python'dan geliyor, bu yüzden genellikle türlerle açıkça ilgilenmiyorum). Tüm bunları mükemmel bir şekilde
düzeltti

2
Parametreler hakkında harika ipuçları. Tek bir şüpheye ihtiyacım var: "& String, & Vec veya & Box'ı kabul etmek ayrıca yöntemi çağırmadan önce bir ayırma gerektirir." ... Neden böyle? Belgelerde bunu ayrıntılı olarak okuyabileceğim kısma işaret eder misiniz? (Ben yeni başlayan biriyim). Ayrıca iade türleri konusunda benzer ipuçları alabilir miyiz?
Nawaz

2
Ekstra tahsisin neden gerekli olduğu konusunda bilgi eksik. Dize, & String'i bağımsız değişken olarak kabul ederken, öbek üzerinde depolanır, neden Rust yığında depolanan ve öbek boşluğuna işaret eden bir işaretçi geçirmiyor, neden bir & String geçmenin ek ayırmaya ihtiyaç duyduğunu anlamıyorum, bir dize geçiriyor dilim ayrıca yığın boşluğuna işaret eden yığın üzerinde depolanan bir işaretçi göndermeyi gerektirmelidir?
cjohansson

22

Shepmaster'ın cevabına ek olarak , bir &str(ve benzer şekilde &[T]vb.) Kabul etmenin bir başka nedeni , diğer tüm türlerin yanı sıra String ve &strbu da tatmin etmektir Deref<Target = str>. En dikkate değer örneklerden biri Cow<str>, sahip olunan veya ödünç alınan verilerle ilgilenip ilgilenmediğiniz konusunda çok esnek olmanızı sağlayan şeydir.

Eğer varsa:

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

Ama onu a ile aramanız gerekiyor Cow<str>, bunu yapmanız gerekecek:

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());

Bağımsız değişken türünü olarak değiştirdiğinizde, &straşağıdaki Cowgibi, gereksiz ayırma olmadan sorunsuz bir şekilde kullanabilirsiniz 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);

Kabul &stretmek, işlevinizi daha tekdüze ve kullanışlı hale getirir ve "en kolay" yol da artık en verimli yöntemdir. Bu örnekler, Cow<[T]>vb. İle de çalışacaktır .

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.