Kebalikan dari sifat Pinjam untuk jenis Salinan?

Aug 18 2020

Saya telah melihat Borrowsifat yang digunakan untuk mendefinisikan fungsi yang menerima tipe yang dimiliki atau referensi, misalnya Tatau &T. The borrow()Metode ini kemudian disebut dalam fungsi untuk mendapatkan &T.

Adakah sifat yang memungkinkan kebalikannya (yaitu fungsi yang menerima Tatau &Tdan memperoleh T) untuk Copytipe?

Misal untuk contoh ini:

use std::borrow::Borrow;

fn foo<T: Borrow<u32>>(value: T) -> u32 {
    *value.borrow()
}

fn main() {
    println!("{}", foo(&5));
    println!("{}", foo(5));
}

Panggilan ini borrow()untuk mendapatkan referensi, yang kemudian segera direferensikan.

Apakah ada implementasi lain yang hanya menyalin nilai jika Tditeruskan, dan referensi jika &Tdiberikan? Ataukah cara idiomatis menulis hal semacam ini?

Jawaban

5 trentcl Aug 18 2020 at 14:09

Sebenarnya tidak ada sifat terbalik untuk Borrow, karena itu tidak benar-benar berguna sebagai terikat pada fungsi dengan cara yang sama Borrow. Alasannya berkaitan dengan kepemilikan.

Mengapa "invers Borrow" kurang bermanfaat dari Borrow?

Fungsi yang membutuhkan referensi

Pertimbangkan fungsi yang hanya perlu mereferensikan argumennya:

fn puts(arg: &str) {
    println!("{}", arg);
}

Menerima Stringakan menjadi konyol di sini, karena putstidak perlu mengambil alih kepemilikan data, tetapi menerima &strberarti terkadang kita mungkin memaksa pemanggil untuk menyimpan data lebih lama dari yang diperlukan:

{
    let output = create_some_string();
    output.push_str(some_other_string);
    puts(&output);
    // do some other stuff but never use `output` again
} // `output` isn't dropped until here

Masalahnya adalah itu outputtidak diperlukan setelah diteruskan ke puts, dan pemanggil mengetahui ini, tetapi putsmemerlukan referensi, jadi outputharus tetap hidup sampai akhir blok. Jelas Anda selalu dapat memperbaiki ini di pemanggil dengan menambahkan lebih banyak blok dan kadang-kadang let, tetapi putsjuga dapat dibuat umum agar pemanggil mendelegasikan tanggung jawab untuk membersihkan output:

fn puts<T: Borrow<str>>(arg: T) {
    println!("{}", arg.borrow());
}

Menerima T: Borrowuntuk putsmemberi pemanggil fleksibilitas untuk memutuskan apakah akan menyimpan argumen atau memindahkannya ke dalam fungsi.

Fungsi yang membutuhkan nilai yang dimiliki

Sekarang pertimbangkan kasus fungsi yang benar-benar perlu mengambil kepemilikan:

struct Wrapper(String);
fn wrap(arg: String) -> Wrapper {
    Wrapper(arg)
}

Dalam hal ini menerima &strakan menjadi konyol, karena wrapharus memanggilnya to_owned(). Jika pemanggil memiliki Stringyang tidak lagi digunakan, itu tidak perlu menyalin data yang baru saja dipindahkan ke fungsi tersebut. Dalam hal ini, menerima Stringadalah opsi yang lebih fleksibel, karena memungkinkan pemanggil untuk memutuskan apakah akan membuat klon atau meneruskan yang sudah ada String. Memiliki sifat "kebalikan Borrow" tidak akan menambah fleksibilitas apa pun yang arg: Stringbelum disediakan.

Tapi Stringtidak selalu yang paling argumen ergonomis, karena ada beberapa jenis string: &str, Cow<str>, Box<str>... Kita bisa membuat wrapsedikit lebih ergonomis dengan mengatakan ia menerima apa pun yang bisa dikonversi intomenjadi String.

fn wrap<T: Into<String>>(arg: T) -> Wrapper {
    Wrapper(arg.into())
}

Ini berarti Anda dapat menyebutnya seperti wrap("hello, world")tanpa harus .to_owned()menggunakan literal. Yang sebenarnya bukan kemenangan fleksibilitas - penelepon selalu dapat menelepon .into()tanpa kehilangan sifat umum - tetapi ini adalah kemenangan ergonomis .

Bagaimana dengan Copytipe?

Sekarang, Anda bertanya tentang Copytipe. Sebagian besar argumen di atas masih berlaku. Jika Anda menulis fungsi yang, seperti puts, hanya membutuhkan a &A, penggunaan T: Borrow<A>mungkin lebih fleksibel untuk pemanggil; untuk fungsi seperti wrapitu membutuhkan keseluruhan A, lebih fleksibel untuk menerima saja A. Tetapi untuk Copytipe, keuntungan ergonomis dari menerima T: Into<A>jauh lebih jelas.

  • Untuk tipe integer, karena generik mengacaukan inferensi tipe, menggunakannya biasanya membuatnya kurang ergonomis untuk menggunakan literal; Anda mungkin harus secara eksplisit menjelaskan jenisnya.
  • Karena &u32tidak diterapkan Into<u32>, trik khusus itu tidak akan berfungsi di sini.
  • Karena Copytipe sudah tersedia sebagai nilai yang dimiliki, penggunaannya dengan referensi pada awalnya kurang umum.
  • Terakhir, mengubah a &Amenjadi Akapan A: Copysemudah menambahkan *; bisa melewati langkah itu mungkin bukan kemenangan yang cukup menarik untuk mengimbangi kompleksitas tambahan menggunakan obat generik dalam banyak kasus.

Kesimpulannya, foohampir pasti harus menerima value: u32dan membiarkan penelepon memutuskan bagaimana mendapatkan nilai itu.

Lihat juga

  • Apakah lebih konvensional untuk melewatkan nilai atau melewati-dengan-referensi ketika metode membutuhkan kepemilikan nilai?
2 Sunreef Aug 18 2020 at 11:00

Dengan fungsi yang Anda miliki, Anda hanya dapat menggunakan u32atau jenis yang dapat dipinjam sebagai u32.

Anda bisa membuat fungsi Anda lebih umum dengan menggunakan argumen templat kedua.

fn foo<T: Copy, N: Borrow<T>>(value: N) -> T {
    *value.borrow()
}

Namun ini hanya solusi parsial karena akan memerlukan anotasi jenis dalam beberapa kasus agar berfungsi dengan benar.

Misalnya, ini bekerja di luar kotak dengan usize:

let v = 0usize;
println!("{}", foo(v));

Tidak ada masalah di sini bagi kompilator untuk menebak bahwa itu foo(v)adalah a usize.

Namun, jika Anda mencoba foo(&v), kompilator akan mengeluh bahwa ia tidak dapat menemukan tipe keluaran yang tepat Tkarena &Tdapat mengimplementasikan beberapa Borrowciri untuk tipe yang berbeda. Anda perlu secara eksplisit menentukan mana yang ingin Anda gunakan sebagai keluaran.

let output: usize = foo(&v);