API tasarımında iç değişkenlik kötüye kullanımı?
C ++ 'daki geçmişim, iç değişkenlik konusunda beni rahatsız ediyor . Aşağıdaki kod, bu konuyla ilgili araştırmamdır.
Ödünç alma denetleyicisi bakış açısından, iç durumun er ya da geç değiştirilebileceği her bir yapı üzerinde birçok referansla uğraşmanın imkansız olduğuna katılıyorum; bu açıkça iç değişkenliğin yardımcı olabileceği yerdir.
Üstelik bölümde 15.5 "RefCell ve İç Mutability Desen" arasında Dili Programlama Rust , yaklaşık örneği Messenger
özelliği ve üzerindeki uygulanması MockMessenger
yapı beni sistematik tercih ortak bir API tasarım olduğunu düşündürüyor &self
üzerinde &mut self
bile onun oldukça açık ise er ya da geç bir tür değişkenlik zorunlu olacaktır. Messenger
Bir mesaj gönderirken kendi iç durumunu değiştirmeyen bir uygulama nasıl olur? İstisna, sadece mesajın yazdırılmasıdır, ki bu da tutarlıdır &self
, ancak genel durum muhtemelen bir tür iç akışa yazmaktan ibarettir; bu, arabelleğe alma, hata bayraklarının güncellenmesi anlamına gelebilir ... Tüm bunlar &mut self
, örneğin, kesinlikle gerektiririmpl Write for File.
Bu problemi çözmek için içsel değişkenliğe güvenmek bana, C ++ 'da, sadece uygulamanın başka bir yerinde ness konusunda tutarlı olmadığımız için üyelerin const_cast
istismar edilmesi veya istismar edilmesi gibi geliyor (C ++ öğrenenler için yaygın hata).mutable
const
Öyleyse, aşağıdaki örnek koduma geri dönersem:
- kullanmak
&mut self
den (zorunlu olmasa bile, derleyici şikayet etmiyor)change_e()
içinchange_i()
ben saklı tamsayılar değerlerinin değiştirilmesi gerçeği ile tutarlı tutmak için? &self
saklanan tamsayıların değerlerini gerçekten değiştirsem bile dahili değişkenlik buna izin verdiği için kullanmaya devam et?
Bu karar sadece yapının kendisi için yerel değildir, aynı zamanda bu yapıyı kullanarak uygulamada neyin ifade edilebileceği üzerinde büyük bir etkiye sahip olacaktır. İkinci çözüm kesinlikle çok yardımcı olacaktır, çünkü yalnızca paylaşılan referanslar söz konusudur, ancak Rust'ta beklenenle tutarlıdır.
Rust API Yönergelerinde bu soruya bir yanıt bulamıyorum . C ++ CoreGuidelines'a benzer başka Rust belgeleri var mı ?
/*
$ rustc int_mut.rs && ./int_mut
initial: 1 2 3 4 5 6 7 8 9
change_a: 11 2 3 4 5 6 7 8 9
change_b: 11 22 3 4 5 6 7 8 9
change_c: 11 22 33 4 5 6 7 8 9
change_d: 11 22 33 44 5 6 7 8 9
change_e: 11 22 33 44 55 6 7 8 9
change_f: 11 22 33 44 55 66 7 8 9
change_g: 11 22 33 44 55 66 77 8 9
change_h: 11 22 33 44 55 66 77 88 9
change_i: 11 22 33 44 55 66 77 88 99
*/
struct Thing {
a: i32,
b: std::boxed::Box<i32>,
c: std::rc::Rc<i32>,
d: std::sync::Arc<i32>,
e: std::sync::Mutex<i32>,
f: std::sync::RwLock<i32>,
g: std::cell::UnsafeCell<i32>,
h: std::cell::Cell<i32>,
i: std::cell::RefCell<i32>,
}
impl Thing {
fn new() -> Self {
Self {
a: 1,
b: std::boxed::Box::new(2),
c: std::rc::Rc::new(3),
d: std::sync::Arc::new(4),
e: std::sync::Mutex::new(5),
f: std::sync::RwLock::new(6),
g: std::cell::UnsafeCell::new(7),
h: std::cell::Cell::new(8),
i: std::cell::RefCell::new(9),
}
}
fn show(&self) -> String // & is enough (read-only)
{
format!(
"{:3} {:3} {:3} {:3} {:3} {:3} {:3} {:3} {:3}",
self.a,
self.b,
self.c,
self.d,
self.e.lock().unwrap(),
self.f.read().unwrap(),
unsafe { *self.g.get() },
self.h.get(),
self.i.borrow(),
)
}
fn change_a(&mut self) // &mut is mandatory
{
let target = &mut self.a;
*target += 10;
}
fn change_b(&mut self) // &mut is mandatory
{
let target = self.b.as_mut();
*target += 20;
}
fn change_c(&mut self) // &mut is mandatory
{
let target = std::rc::Rc::get_mut(&mut self.c).unwrap();
*target += 30;
}
fn change_d(&mut self) // &mut is mandatory
{
let target = std::sync::Arc::get_mut(&mut self.d).unwrap();
*target += 40;
}
fn change_e(&self) // !!! no &mut here !!!
{
// With C++, a std::mutex protecting a separate integer (e)
// would have been used as two data members of the structure.
// As our intent is to alter the integer (e), and because
// std::mutex::lock() is _NOT_ const (but it's an internal
// that could have been hidden behind the mutable keyword),
// this member function would _NOT_ be const in C++.
// But here, &self (equivalent of a const member function)
// is accepted although we actually change the internal
// state of the structure (the protected integer).
let mut target = self.e.lock().unwrap();
*target += 50;
}
fn change_f(&self) // !!! no &mut here !!!
{
// actually alters the integer (as with e)
let mut target = self.f.write().unwrap();
*target += 60;
}
fn change_g(&self) // !!! no &mut here !!!
{
// actually alters the integer (as with e, f)
let target = self.g.get();
unsafe { *target += 70 };
}
fn change_h(&self) // !!! no &mut here !!!
{
// actually alters the integer (as with e, f, g)
self.h.set(self.h.get() + 80);
}
fn change_i(&self) // !!! no &mut here !!!
{
// actually alters the integer (as with e, f, g, h)
let mut target = self.i.borrow_mut();
*target += 90;
}
}
fn main() {
let mut t = Thing::new();
println!(" initial: {}", t.show());
t.change_a();
println!("change_a: {}", t.show());
t.change_b();
println!("change_b: {}", t.show());
t.change_c();
println!("change_c: {}", t.show());
t.change_d();
println!("change_d: {}", t.show());
t.change_e();
println!("change_e: {}", t.show());
t.change_f();
println!("change_f: {}", t.show());
t.change_g();
println!("change_g: {}", t.show());
t.change_h();
println!("change_h: {}", t.show());
t.change_i();
println!("change_i: {}", t.show());
}
Yanıtlar
Bu problemi çözmek için içsel değişkenliğe güvenmek bana, C ++ 'da, sadece uygulamanın başka bir yerinde ness konusunda tutarlı olmadığımız için üyelerin
const_cast
istismar edilmesi veya istismar edilmesi gibi geliyor (C ++ öğrenenler için yaygın hata).mutable
const
Bu, C ++ bağlamında tamamen anlaşılabilir bir düşüncedir. Doğru olmamasının nedeni, C ++ ve Rust'un farklı değişkenlik kavramlarına sahip olmasıdır.
Bir bakıma, Rust'un mut
anahtar kelimesinin aslında iki anlamı vardır. Bir modelde "değiştirilebilir" anlamına gelir ve bir referans türünde "özel" anlamına gelir. Arasındaki fark &self
ve &mut self
ister gerçekten değil self
mutasyona uğramış ya da değil, ama olup olmadığını edilebilir Aliased .
In Messenger
Örneğin, iyi, ilk en çok ciddiye almayalım; sistem tasarımını değil, dil özelliklerini göstermeyi amaçlamaktadır. Ancak neden &self
kullanılabileceğini hayal edebiliriz : paylaşılanMessenger
yapılar tarafından uygulanması amaçlanmıştır , bu nedenle farklı kod parçaları aynı nesneye referanslar tutabilir ve birbirleriyle koordinasyon olmadan bunu uyarılar için kullanabilir . Eğer alacaklardı sadece bir tane olabilir, çünkü bu amaç için yararsız olacaktır bir anda varlığına referansı. Paylaşılana mesaj göndermek imkansız olurdu (harici bir dahili değişkenlik katmanı veya başka bir şey eklemeden ).send
send
&mut self
&mut self
Messenger
Mutex
Öte yandan, her C ++ başvurusu ve gösterici takma adla adlandırılabilir. Yani Rust terimleriyle, C ++ 'daki tüm değişkenlik "iç" değişkenliktir! Rust'un mutable
C ++ ile bir eşdeğeri yoktur çünkü Rust'un hiçbir const
üyesi yoktur (buradaki anahtar kelime "değişebilirlik türünün değil, bağlamanın bir özelliğidir"). Pas yapar için bir eş const_cast
paylaşılan bir çevirmek çürük çünkü, fakat sadece çiğ noktalardaki, &
özel içine referans &mut
referans. Tersine, C ++ 'nın benzeri hiçbir şey yoktur Cell
veya RefCell
her değer örtük olarak UnsafeCell
zaten bir.
Öyleyse, aşağıdaki örnek koduma geri dönersem, [...]
Gerçekten amaçlanan bağlıdır semantik arasında Thing
. Thing
Bir kanal uç noktası veya dosya gibi paylaşılmanın doğası var mı ? change_e
Paylaşılan (takma ad) bir referansta çağrılmak mantıklı mı ? Eğer öyleyse, bir yöntemi ortaya çıkarmak için iç değişkenliği kullanın &self
. Mi Thing
esas veriler için bir kap? Bazen paylaşılması ve bazen özel olması mantıklı mı? Öyleyse Thing
muhtemelen dahili değişkenliği kullanmamalı ve gerekli olması halinde, kütüphane kullanıcısının paylaşılan mutasyonla nasıl başa çıkılacağına karar vermesine izin vermelidir.
Ayrıca bakınız
- Bir değişken adının önüne "mut" ve ":" karakterinden sonra "mut" yerleştirmek arasındaki fark nedir?
- Neden bir değişkenin değişkenliği Rust'taki tip imzasına yansımıyor?
- Rust'ın hücresi ve referans sayılan türler hakkında bütüncül bir açıklamaya ihtiyacınız var
¹ Aslında, C ++ , işaretçilerin Rust'taki referanslara benzer şekilde çalışmasını sağlayan bir özelliğe sahiptir. Biraz. restrict
C ++ 'da standart olmayan bir uzantıdır ancak C99'un bir parçasıdır. Rust'un paylaşılan ( &
) referansları const *restrict
işaretçiler gibidir ve özel ( &mut
) referanslar const
*restrict
işaretçi olmayanlar gibidir. Bkz kelime ortalama C ++ ne kısıtlamak geliyor?
C ++ ' restrict
da kasıtlı olarak (veya __restrict
vb.) Bir işaretçi en son ne zaman kullandınız ? Bunun hakkında düşünmeye zahmet etmeyin; cevap "asla" dır. restrict
normal işaretçilerden daha agresif optimizasyonları mümkün kılar, ancak bunu doğru kullanmak çok zordur çünkü örtüşme konusunda son derece dikkatli olmanız gerekir ve derleyici hiçbir yardım sunmaz. Temelde devasa bir tabanca ve neredeyse hiç kimse kullanmıyor. C ++ 'da restrict
kullanım şeklinizi yaygın bir şekilde kullanmaya değer kılmak için const
, işaretçilerin hangi zamanlarda diğerlerini değiştirmesine izin verilen işlevlere açıklama ekleyebilmeli, işaretçilerin ne zaman geçerli olduğuna dair bazı kurallar koyabilmelisiniz. ve her işlevde kurallara uyulup uyulmadığını kontrol eden bir derleyici geçişine sahip olun. Bir çeşit ... pul gibi.