Mengapa tidak disarankan untuk menerima referensi ke String (& String), Vec (& Vec), atau Box (& Box) sebagai argumen fungsi?

127

Saya menulis beberapa kode Rust yang mengambil &Stringargumen:

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

Saya juga telah menulis kode yang mengacu pada a Vecatau Box:

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

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

Namun, saya menerima beberapa tanggapan bahwa melakukannya seperti ini bukanlah ide yang baik. Kenapa tidak?

Shepmaster
sumber

Jawaban:

162

TL; DR: Seseorang dapat menggunakan &str, &[T]atau &Tuntuk mengizinkan kode yang lebih umum.


  1. Salah satu alasan utama menggunakan a Stringatau a Vecadalah karena memungkinkan peningkatan atau penurunan kapasitas. Namun, saat Anda menerima referensi yang tidak dapat diubah, Anda tidak dapat menggunakan salah satu metode menarik tersebut di Vecatau String.

  2. Menerima a &String, &Vecatau &Boxjuga membutuhkan argumen untuk dialokasikan di heap sebelum Anda dapat memanggil fungsi tersebut. Menerima a &strmemungkinkan string literal (disimpan dalam data program) dan menerima &[T]atau &Tmengizinkan array atau variabel yang dialokasikan tumpukan. Alokasi yang tidak perlu adalah hilangnya kinerja. Ini biasanya langsung terungkap saat Anda mencoba memanggil metode ini dalam pengujian atau mainmetode:

    awesome_greeting(&String::from("Anna"));
    total_price(&vec![42, 13, 1337])
    is_even(&Box::new(42))
  3. Pertimbangan kinerja lainnya adalah itu &String, &Vecdan &Boxmemperkenalkan lapisan tipuan yang tidak perlu karena Anda harus melakukan dereferensi &Stringuntuk mendapatkan Stringdan kemudian melakukan dereferensi kedua untuk berakhir di &str.

Sebagai gantinya, Anda harus menerima string slice ( &str), slice ( &[T]), atau hanya referensi ( &T). A &String, &Vec<T>atau &Box<T>akan secara otomatis dipaksa menjadi &str, &[T]atau &T, masing-masing.

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
}

Sekarang Anda dapat memanggil metode ini dengan kumpulan tipe yang lebih luas. Misalnya, awesome_greetingdapat dipanggil dengan string literal ( "Anna") atau dialokasikan String. total_pricebisa dipanggil dengan referensi ke array ( &[1, 2, 3]) atau yang dialokasikan Vec.


Jika Anda ingin menambah atau menghapus item dari Stringatau Vec<T>, Anda dapat menggunakan referensi yang dapat diubah ( &mut Stringatau &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);
}

Khusus untuk irisan, Anda juga dapat menerima &mut [T]atau &mut str. Ini memungkinkan Anda untuk mengubah nilai tertentu di dalam slice, tetapi Anda tidak dapat mengubah jumlah item di dalam slice (yang artinya sangat dibatasi untuk string):

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();
    }
}
Shepmaster
sumber
5
Bagaimana kalau tl; dr pada awalnya? Jawaban ini sudah agak panjang. Sesuatu seperti " &strlebih umum (seperti: memberlakukan lebih sedikit batasan) tanpa mengurangi kemampuan"? Juga: poin 3 seringkali tidak terlalu penting menurut saya. Biasanya Vecs dan Strings akan hidup di stack dan bahkan sering berada di dekat frame stack saat ini. Tumpukan biasanya panas dan dereferensi akan disajikan dari cache CPU.
Lukas Kalbertodt
3
@Shepmaster: Mengenai biaya alokasi, mungkin ada baiknya menyebutkan masalah khusus substring / irisan ketika berbicara tentang alokasi wajib. total_price(&prices[0..4])tidak perlu mengalokasikan vektor baru untuk irisan.
Matthieu M.
4
Ini jawaban yang bagus. Saya baru saja memulai di Rust dan sedang sibuk mencari tahu kapan saya harus menggunakan a &strdan mengapa (berasal dari Python, jadi saya biasanya tidak secara eksplisit berurusan dengan jenis). Menghapus semua itu dengan sempurna
C.Nivs
2
Kiat mengagumkan tentang parameter. Hanya perlu satu keraguan: "Menerima & String, & Vec atau & Box juga memerlukan alokasi sebelum Anda dapat memanggil metode." ... Mengapa demikian? Bisakah Anda menunjukkan bagian dalam dokumen tempat saya dapat membaca ini secara detail? (Saya pemula). Juga, bisakah kita memiliki tip serupa tentang tipe pengembalian?
Nawaz
2
Saya kehilangan informasi tentang mengapa alokasi tambahan diperlukan. String disimpan di heap, saat menerima & String sebagai argumen, mengapa Rust tidak meneruskan pointer yang disimpan di stack yang mengarah ke ruang heap, saya tidak mengerti mengapa melewatkan & String akan membutuhkan alokasi tambahan, meneruskan string slice juga harus meminta pengiriman pointer yang disimpan di stack yang mengarah ke ruang heap?
cjohansson
22

Selain jawaban Shepmaster , alasan lain untuk menerima a &str(dan yang serupa &[T]dll) adalah karena semua jenis lain selain String dan &stritu juga memuaskan Deref<Target = str>. Salah satu contoh yang paling menonjol adalah Cow<str>, yang memungkinkan Anda menjadi sangat fleksibel tentang apakah Anda berurusan dengan data yang dimiliki atau dipinjam.

Jika Anda memiliki:

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

Tetapi Anda harus menyebutnya dengan Cow<str>, Anda harus melakukan ini:

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

Saat Anda mengubah tipe argumen menjadi &str, Anda dapat menggunakan dengan Cowmulus, tanpa alokasi yang tidak perlu, seperti dengan 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);

Menerima &strmembuat pemanggilan fungsi Anda lebih seragam dan nyaman, dan cara "termudah" sekarang juga paling efisien. Contoh ini juga akan berfungsi dengan Cow<[T]>dll.

Peter Hall
sumber