Kopya türleri için Ödünç alma özelliği karşısında mı?
Borrow
Hem sahip olunan türü hem de referansı kabul eden işlevleri tanımlamak için kullanılan özelliği gördüm , örneğin T
veya &T
. borrow()
Daha sonra, yöntem elde etmek üzere işlev olarak adlandırılır &T
.
Ters (kabul, yani bir işlev sağlayan bazı özellik var T
ya da &T
ve elde eder T
için) Copy
tip?
Örneğin bu örnek için:
use std::borrow::Borrow;
fn foo<T: Borrow<u32>>(value: T) -> u32 {
*value.borrow()
}
fn main() {
println!("{}", foo(&5));
println!("{}", foo(5));
}
Bu borrow()
, daha sonra derhal referansı kaldırılan bir referans almayı çağırır .
Sadece T
aktarılmışsa değeri kopyalayan ve &T
verilmişse referansları kaldıran başka bir uygulama var mı ? Yoksa bu tür şeyleri yazmanın yukarıdaki deyimsel yolu mu?
Yanıtlar
Bunun tersi bir özellik yoktur Borrow
, çünkü aynı şekilde işlevlere bağlı olarak gerçekten yararlı değildir Borrow
. Nedeni mülkiyetle ilgili.
"Ters Borrow
" neden daha az yararlıdır Borrow
?
Referans gerektiren işlevler
Yalnızca argümanına başvurması gereken bir işlevi düşünün:
fn puts(arg: &str) {
println!("{}", arg);
}
String
Burada kabul etmek aptalca olurdu, çünkü puts
verilerin sahipliğini üstlenmek zorunda değiliz, ancak kabul etmek &str
bazen arayanı verileri gereğinden daha uzun süre tutmaya zorlayabileceğimiz anlamına gelir:
{
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
Sorun output
, gönderildikten sonra ihtiyaç duyulmaması puts
ve arayanın bunu bilmesi, ancak puts
bir referansa ihtiyaç duyması , bu nedenle output
bloğun sonuna kadar hayatta kalması gerektiğidir. Açıkçası bunu arayan kişide daha fazla blok ekleyerek ve bazen a ekleyerek düzeltebilirsiniz let
, ancak puts
aynı zamanda arayanın temizlik sorumluluğunu devretmesine izin vermek için genel yapılabilir output
:
fn puts<T: Borrow<str>>(arg: T) {
println!("{}", arg.borrow());
}
İçin kabul T: Borrow
etmek puts
, arayan kişiye argümanın etrafında mı yoksa işleve mi hareket ettirileceğine karar verme esnekliği verir.
Sahip olunan değerlere ihtiyaç duyan işlevler
Şimdi, gerçekten sahip olması gereken bir işlevi düşünün:
struct Wrapper(String);
fn wrap(arg: String) -> Wrapper {
Wrapper(arg)
}
Bu durumda kabul etmek &str
aptalca wrap
olurdu çünkü onu çağırmak zorunda kalacaktı to_owned()
. Arayanın String
artık kullanmadığı bir telefon varsa, bu işleve yeni taşınmış olabilecek verileri gereksiz yere kopyalar. Bu durumda, kabul String
etmek daha esnek bir seçenektir, çünkü arayanın bir klon yapıp yapmayacağına veya mevcut olanı geçip geçmeyeceğine karar vermesine izin verir String
. "Ters Borrow
" bir özelliğe sahip olmak arg: String
, halihazırda sağlamayan herhangi bir esneklik eklemeyecektir .
Ama String
dize birkaç farklı türü vardır, çünkü her zaman en ergonomik argüman değil: &str
, Cow<str>
, Box<str>
... Biz yapabilirsiniz wrap
o dönüştürülen olabilir şey kabul söyleyerek biraz daha ergonomik into
bir String
.
fn wrap<T: Into<String>>(arg: T) -> Wrapper {
Wrapper(arg.into())
}
Bu , kelimenin tam anlamıyla wrap("hello, world")
aramak zorunda kalmadan onu arayabileceğiniz anlamına gelir .to_owned()
. Bu gerçekten bir esneklik kazanımı değildir - arayan .into()
kişi genelliği kaybetmeden her zaman arayabilir - ancak ergonomik bir kazançtır.
Copy
Tipler ne olacak ?
Şimdi, Copy
türleri sordunuz . Çoğunlukla yukarıdaki argümanlar hala geçerlidir. puts
Yalnızca a'ya ihtiyaç duyan bir işlev yazıyorsanız &A
, kullanmak T: Borrow<A>
arayan için daha esnek olabilir; bunun gibi wrap
bütüne ihtiyaç duyan bir işlev A
için sadece kabul etmek daha esnektir A
. Ancak Copy
tipler için kabul etmenin ergonomik avantajı T: Into<A>
çok daha az kesindir.
- Tamsayı türleri için, jenerikler tür çıkarımıyla uğraştığından, bunları kullanmak genellikle değişmez değerleri kullanmayı daha az ergonomik hale getirir ; türlere açıkça açıklama eklemek zorunda kalabilirsiniz.
- Yana
&u32
uygulamıyorInto<u32>
, o belirli hile zaten buraya işe yaramaz. - Yana
Copy
tipleri olunan değerler olarak kolayca kullanılabilir, ilk etapta referans olarak bunları kullanmak için daha az yaygındır. - Son olarak, a'yı
&A
birA
zamana dönüştürmekA: Copy
, eklemek kadar basittir*
; Bu adımı atlayabilmek, çoğu durumda jenerik kullanmanın getirdiği ek karmaşıklığı dengelemek için muhtemelen yeterince zorlayıcı bir kazanç değildir.
Sonuç olarak, foo
neredeyse kesinlikle kabul etmeli value: u32
ve arayan kişinin bu değeri nasıl elde edeceğine karar vermesine izin vermelidir.
Ayrıca bakınız
- Yöntemin değerin sahipliğine ihtiyacı olduğunda, değere göre geçiş yapmak mı yoksa referansla geçmek mi daha geleneksel?
Sahip olduğunuz işlev ile yalnızca u32
ödünç alınabilecek bir veya bir türü kullanabilirsiniz u32
.
İkinci bir şablon bağımsız değişkeni kullanarak işlevinizi daha genel hale getirebilirsiniz.
fn foo<T: Copy, N: Borrow<T>>(value: N) -> T {
*value.borrow()
}
Ancak bu, bazı durumlarda düzgün çalışması için tür ek açıklamaları gerektireceğinden yalnızca kısmi bir çözümdür.
Örneğin, kutudan çıkar çıkmaz şunlarla çalışır usize
:
let v = 0usize;
println!("{}", foo(v));
Burada derleyicinin foo(v)
bunun a usize
.
Ancak, denerseniz foo(&v)
, derleyici doğru çıktı türünü bulamadığından şikayet edecektir T
çünkü farklı türler için &T
birkaç Borrow
özellik uygulayabilir . Hangisini çıktı olarak kullanmak istediğinizi açıkça belirtmeniz gerekir.
let output: usize = foo(&v);