Kebalikan dari sifat Pinjam untuk jenis Salinan?
Saya telah melihat Borrow
sifat yang digunakan untuk mendefinisikan fungsi yang menerima tipe yang dimiliki atau referensi, misalnya T
atau &T
. The borrow()
Metode ini kemudian disebut dalam fungsi untuk mendapatkan &T
.
Adakah sifat yang memungkinkan kebalikannya (yaitu fungsi yang menerima T
atau &T
dan memperoleh T
) untuk Copy
tipe?
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 T
diteruskan, dan referensi jika &T
diberikan? Ataukah cara idiomatis menulis hal semacam ini?
Jawaban
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 String
akan menjadi konyol di sini, karena puts
tidak perlu mengambil alih kepemilikan data, tetapi menerima &str
berarti 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 output
tidak diperlukan setelah diteruskan ke puts
, dan pemanggil mengetahui ini, tetapi puts
memerlukan referensi, jadi output
harus tetap hidup sampai akhir blok. Jelas Anda selalu dapat memperbaiki ini di pemanggil dengan menambahkan lebih banyak blok dan kadang-kadang let
, tetapi puts
juga dapat dibuat umum agar pemanggil mendelegasikan tanggung jawab untuk membersihkan output
:
fn puts<T: Borrow<str>>(arg: T) {
println!("{}", arg.borrow());
}
Menerima T: Borrow
untuk puts
memberi 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 &str
akan menjadi konyol, karena wrap
harus memanggilnya to_owned()
. Jika pemanggil memiliki String
yang tidak lagi digunakan, itu tidak perlu menyalin data yang baru saja dipindahkan ke fungsi tersebut. Dalam hal ini, menerima String
adalah 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: String
belum disediakan.
Tapi String
tidak selalu yang paling argumen ergonomis, karena ada beberapa jenis string: &str
, Cow<str>
, Box<str>
... Kita bisa membuat wrap
sedikit lebih ergonomis dengan mengatakan ia menerima apa pun yang bisa dikonversi into
menjadi 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 Copy
tipe?
Sekarang, Anda bertanya tentang Copy
tipe. 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 wrap
itu membutuhkan keseluruhan A
, lebih fleksibel untuk menerima saja A
. Tetapi untuk Copy
tipe, 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
&u32
tidak diterapkanInto<u32>
, trik khusus itu tidak akan berfungsi di sini. - Karena
Copy
tipe sudah tersedia sebagai nilai yang dimiliki, penggunaannya dengan referensi pada awalnya kurang umum. - Terakhir, mengubah a
&A
menjadiA
kapanA: Copy
semudah menambahkan*
; bisa melewati langkah itu mungkin bukan kemenangan yang cukup menarik untuk mengimbangi kompleksitas tambahan menggunakan obat generik dalam banyak kasus.
Kesimpulannya, foo
hampir pasti harus menerima value: u32
dan membiarkan penelepon memutuskan bagaimana mendapatkan nilai itu.
Lihat juga
- Apakah lebih konvensional untuk melewatkan nilai atau melewati-dengan-referensi ketika metode membutuhkan kepemilikan nilai?
Dengan fungsi yang Anda miliki, Anda hanya dapat menggunakan u32
atau 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 T
karena &T
dapat mengimplementasikan beberapa Borrow
ciri untuk tipe yang berbeda. Anda perlu secara eksplisit menentukan mana yang ingin Anda gunakan sebagai keluaran.
let output: usize = foo(&v);