API tasarımında iç değişkenlik kötüye kullanımı?

Aug 19 2020

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ı MockMessengeryapı beni sistematik tercih ortak bir API tasarım olduğunu düşündürüyor &selfüzerinde &mut selfbile onun oldukça açık ise er ya da geç bir tür değişkenlik zorunlu olacaktır. MessengerBir 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_castistismar edilmesi veya istismar edilmesi gibi geliyor (C ++ öğrenenler için yaygın hata).mutableconst

Öyleyse, aşağıdaki örnek koduma geri dönersem:

  • kullanmak &mut selfden (zorunlu olmasa bile, derleyici şikayet etmiyor) change_e()için change_i()ben saklı tamsayılar değerlerinin değiştirilmesi gerçeği ile tutarlı tutmak için?
  • &selfsaklanan 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

7 trentcl Aug 19 2020 at 15:59

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_castistismar edilmesi veya istismar edilmesi gibi geliyor (C ++ öğrenenler için yaygın hata).mutableconst

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 mutanahtar 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 &selfve &mut selfister gerçekten değil selfmutasyona 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 &selfkullanı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 ).sendsend&mut self&mut selfMessengerMutex

Ö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 mutableC ++ 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_castpaylaşılan bir çevirmek çürük çünkü, fakat sadece çiğ noktalardaki, &özel içine referans &mutreferans. Tersine, C ++ 'nın benzeri hiçbir şey yoktur Cellveya RefCellher değer örtük olarak UnsafeCellzaten bir.

Öyleyse, aşağıdaki örnek koduma geri dönersem, [...]

Gerçekten amaçlanan bağlıdır semantik arasında Thing. ThingBir kanal uç noktası veya dosya gibi paylaşılmanın doğası var mı ? change_ePaylaşı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 Thingesas veriler için bir kap? Bazen paylaşılması ve bazen özel olması mantıklı mı? Öyleyse Thingmuhtemelen 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. restrictC ++ 'da standart olmayan bir uzantıdır ancak C99'un bir parçasıdır. Rust'un paylaşılan ( &) referansları const *restrictişaretçiler gibidir ve özel ( &mut) referanslar const *restrictişaretçi olmayanlar gibidir. Bkz kelime ortalama C ++ ne kısıtlamak geliyor?

C ++ ' restrictda kasıtlı olarak (veya __restrictvb.) Bir işaretçi en son ne zaman kullandınız ? Bunun hakkında düşünmeye zahmet etmeyin; cevap "asla" dır. restrictnormal 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 restrictkullanı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.