Rust'un tam otomatik kayıt silme kuralları nelerdir?


183

Rust ile öğreniyorum / deniyorum ve bu dilde bulduğum tüm zarafette, beni şaşırtan ve tamamen yersiz görünen bir tuhaflık var.

Rust, yöntem çağrıları yaparken işaretçileri otomatik olarak dereferences. Tam davranışı belirlemek için bazı testler yaptım:

struct X { val: i32 }
impl std::ops::Deref for X {
    type Target = i32;
    fn deref(&self) -> &i32 { &self.val }
}

trait M { fn m(self); }
impl M for i32   { fn m(self) { println!("i32::m()");  } }
impl M for X     { fn m(self) { println!("X::m()");    } }
impl M for &X    { fn m(self) { println!("&X::m()");   } }
impl M for &&X   { fn m(self) { println!("&&X::m()");  } }
impl M for &&&X  { fn m(self) { println!("&&&X::m()"); } }

trait RefM { fn refm(&self); }
impl RefM for i32  { fn refm(&self) { println!("i32::refm()");  } }
impl RefM for X    { fn refm(&self) { println!("X::refm()");    } }
impl RefM for &X   { fn refm(&self) { println!("&X::refm()");   } }
impl RefM for &&X  { fn refm(&self) { println!("&&X::refm()");  } }
impl RefM for &&&X { fn refm(&self) { println!("&&&X::refm()"); } }


struct Y { val: i32 }
impl std::ops::Deref for Y {
    type Target = i32;
    fn deref(&self) -> &i32 { &self.val }
}

struct Z { val: Y }
impl std::ops::Deref for Z {
    type Target = Y;
    fn deref(&self) -> &Y { &self.val }
}


#[derive(Clone, Copy)]
struct A;

impl M for    A { fn m(self) { println!("A::m()");    } }
impl M for &&&A { fn m(self) { println!("&&&A::m()"); } }

impl RefM for    A { fn refm(&self) { println!("A::refm()");    } }
impl RefM for &&&A { fn refm(&self) { println!("&&&A::refm()"); } }


fn main() {
    // I'll use @ to denote left side of the dot operator
    (*X{val:42}).m();        // i32::m()    , Self == @
    X{val:42}.m();           // X::m()      , Self == @
    (&X{val:42}).m();        // &X::m()     , Self == @
    (&&X{val:42}).m();       // &&X::m()    , Self == @
    (&&&X{val:42}).m();      // &&&X:m()    , Self == @
    (&&&&X{val:42}).m();     // &&&X::m()   , Self == *@
    (&&&&&X{val:42}).m();    // &&&X::m()   , Self == **@
    println!("-------------------------");

    (*X{val:42}).refm();     // i32::refm() , Self == @
    X{val:42}.refm();        // X::refm()   , Self == @
    (&X{val:42}).refm();     // X::refm()   , Self == *@
    (&&X{val:42}).refm();    // &X::refm()  , Self == *@
    (&&&X{val:42}).refm();   // &&X::refm() , Self == *@
    (&&&&X{val:42}).refm();  // &&&X::refm(), Self == *@
    (&&&&&X{val:42}).refm(); // &&&X::refm(), Self == **@
    println!("-------------------------");

    Y{val:42}.refm();        // i32::refm() , Self == *@
    Z{val:Y{val:42}}.refm(); // i32::refm() , Self == **@
    println!("-------------------------");

    A.m();                   // A::m()      , Self == @
    // without the Copy trait, (&A).m() would be a compilation error:
    // cannot move out of borrowed content
    (&A).m();                // A::m()      , Self == *@
    (&&A).m();               // &&&A::m()   , Self == &@
    (&&&A).m();              // &&&A::m()   , Self == @
    A.refm();                // A::refm()   , Self == @
    (&A).refm();             // A::refm()   , Self == *@
    (&&A).refm();            // A::refm()   , Self == **@
    (&&&A).refm();           // &&&A::refm(), Self == @
}

( Oyun Parkı )

Yani, öyle görünüyor ki, az çok:

  • Derleyici, bir yöntemi çağırmak için gereken sayıda dereference operatörü ekleyecektir.
  • Derleyici, &self(çağrı yoluyla) kullanılarak bildirilen yöntemleri çözerken :
    • İlk denemek için tek bir başvuru self
    • Ardından, tam olarak self
    • Ardından, bir eşleşme için gereken sayıda dereference operatörü eklemeyi dener
  • selfTür için (değere göre çağrı) kullanılarak bildirilen yöntemler , tür için (başvuruya göre çağrı) Tkullanılarak bildirilmiş gibi davranır ve nokta operatörünün sol tarafında ne varsa &selfbaşvuruyu çağırır &T.
  • Yukarıdaki kurallar ilk olarak ham yerleşik kayıt silme ile denenir ve eşleşme yoksa, özellikli aşırı yük Derefkullanılır.

Tam otomatik kayıt silme kuralları nelerdir? Böyle bir tasarım kararı için herhangi bir resmi gerekçe verilebilir mi?


1
Bazı iyi cevaplar alma umuduyla bunu Rust altdizenine çapraz gönderdim !
Shepmaster

Ekstra eğlence için deneyi jenerik olarak tekrarlamaya çalışın ve sonuçları karşılaştırın.
user2665887

Yanıtlar:


138

Sahte kodunuz hemen hemen doğrudur. Bu örnekte, bir yöntem çağrısı vardı varsayalım foo.bar()nerede foo: T. Yöntemin hangi türle çağrıldığı konusunda net olmamak için tam nitelikli sözdizimini (FQS) kullanacağım, örneğin A::bar(foo)veya A::bar(&***foo). Ben sadece rasgele büyük harfler yığını yazacağım, her zaman sadece rastgele bir tür / özellik, dışında Ther zaman fooyöntemin çağrıldığı özgün değişkenin türü .

Algoritmanın özü:

Özellikle, her şey yönteminin "alıcı türünü", gördüğü değilSelf yani özelliğin türünü impl ... for Foo { fn method(&self) {} }düşünen &Fooyöntemi eşleşen ne zaman ve fn method2(&mut self)hakkında düşünürdüm &mut Foozaman eşleştirme.

İç adımlarda geçerli olan birden fazla özellik yöntemi varsa bir hatadır (yani, 1 veya 2'nin her birinde yalnızca sıfır veya bir özellik yöntemi olabilir, ancak her biri için geçerli bir tane olabilir: bir 1'den önce alınacaktır) ve doğal yöntemler özelliklerden önceliklidir. Eşleşen bir şey bulamadan döngünün sonuna ulaşırsak da bir hatadır. DerefDöngüyü sonsuz yapan yinelemeli uygulamalara sahip olmak da bir hatadır ("özyineleme sınırına" ulaşacaklardır).

Belirsiz FQS formunu yazma yeteneğine sahip olmak bazı kenar durumlarda ve makro tarafından üretilen kod için mantıklı hata mesajları için bu kurallar çoğu durumda ne demek istediğimi gösteriyor gibi görünüyor.

Yalnızca bir otomatik referans eklendi çünkü

  • sınır yoksa, işler kötüleşir / yavaşlar, çünkü her türün keyfi sayıda referansı olabilir
  • bir referans &fooalmak foo( fookendisinin adresidir ) ile güçlü bir bağlantıyı korur , ancak daha fazla almak onu kaybetmeye başlar: &&foodepolayan yığın üzerindeki bazı geçici değişkenlerin adresidir &foo.

Örnekler

Bizim için bir çağrı olduğunu varsayalım foo.refm()eğer foobir türde:

  • XO zaman başlayalım U = X, refmalıcı türü olan &...1. adım eşleşmiyor böylece bir oto-ref bize verdiği alarak, &Xve bu maçı (ile yaptığı Self = Xçağrıdır bu yüzden,)RefM::refm(&foo)
  • &X, ile başlar , ilk adımda (ile ) U = &Xeşleşir ve böylece çağrı&selfSelf = XRefM::refm(foo)
  • &&&&&X, bu her iki adımla da eşleşmez (özellik &&&&Xveya için uygulanmaz &&&&&X), bu nedenle almak için bir kez dereference yapıyoruz U = &&&&X, bu da 1 (ile Self = &&&X) eşleşiyor ve çağrıRefM::refm(*foo)
  • Z, her iki adımı Yda eşleştirmez, bu yüzden bir kez Xkayıttan çıkarılır, aynı zamanda eşleşmez, bu yüzden tekrar kayıttan çıkarılır, elde etmek , 1 ile eşleşmez, ancak otomatik kaydetmeden sonra eşleşir, bu yüzden çağrı RefM::refm(&**foo).
  • &&A, 1. eşleşmez ve 2 ile eşleşmez. Çünkü özellik &A(1 için) veya &&A(2 için ) için uygulanmadığından , 1 &Aile eşleşen kayıttan çıkarılır.Self = A

Varsayalım , eğer foo.m()böyle bir Aşey Copyvarsa foo:

  • AArdından U = Aeşleşen selfçağrıdır bunu doğrudan M::m(foo)ileSelf = A
  • &A, o zaman 1. eşleşmez ve 2. (ne özelliği ne &Ade &&Auygular) Aeşleşmez, bu nedenle eşleşmez, eşleşir, ancak değer M::m(*foo)almayı Ave bu foonedenle hatadan uzaklaşmayı gerektirir .
  • &&A, 1. eşleşmiyor, ancak eşleşmeyi sağlayan otomatik kaydetme veriyor &&&A, bu nedenle çağrı M::m(&foo)ile Self = &&&A.

(Bu cevap koda dayanmaktadır ve makul olarak (biraz eskimiş) README'ye yakındır . Derleyicinin / dilin bu bölümünün ana yazarı Niko Matsakis de bu cevaba göz attı.)


15
Bu cevap ayrıntılı ve ayrıntılı görünüyor ama bence kuralların kısa ve erişilebilir bir yaz mevsimi yok. Böyle bir yazlık bu yorumda Shepmaster tarafından verilir : "Bu [deref algoritması] mümkün olduğunca çok kez silinecektir ( &&String-> &String-> String-> str) ve sonra en fazla bir kez referans verecek ( str-> &str)".
Lii

(Bu açıklamanın kendim ne kadar doğru ve eksiksiz olduğunu bilmiyorum.)
Lii

1
Hangi durumlarda otomatik kayıt silme gerçekleşir? Yalnızca yöntem çağrısı için alıcı ifadesi için mi kullanılır? Saha erişimi için de? Atama sağ taraf mı? Sol taraf? Fonksiyon parametreleri? Dönüş değeri ifadeleri?
Lii

1
Not: Şu anda, nomicon'un bu cevaptan bilgi çalmak ve static.rust-lang.org/doc/master/nomicon/dot-operator.html adresinde
SamB

1
Zorlama (A) bundan önce denenmiş mi veya (B) bundan sonra denenmiş mi veya (C) bu algoritmanın her adımında mı denenmiş veya (D) başka bir şey mi?
haslersn

8

Rust başvurusunun yöntem çağrısı ifadesi hakkında bir bölümü vardır . Aşağıdaki en önemli kısmı kopyaladım. Hatırlatma: Biz bir ifadenin söz ediyoruz recv.m(), recvaşağıdaki "alıcı ifadesi" denir.

İlk adım, aday alıcı türlerinin bir listesini oluşturmaktır. Bunları, alıcı ifadesinin türünü tekrar tekrar kaydırarak, karşılaşılan her türü listeye ekleyerek, sonunda sonunda boyutsuz bir zorlamayı deneyerek ve başarılı olursa sonuç türünü ekleyerek elde edin. Daha sonra her aday için T, ekleme &Tve &mut Themen sonra listeye T.

Alıcı türüne sahip Örneğin, Box<[i32;2]>daha sonra, aday tipi olacaktır Box<[i32;2]>, &Box<[i32;2]>, &mut Box<[i32;2]>, [i32; 2], (kaldırma tarafından) &[i32; 2], &mut [i32; 2], [i32](haşıllanmamış zorlama), &[i32]ve son olarak &mut [i32].

Ardından, her aday türü Tiçin, aşağıdaki yerlerde bu tür bir alıcıyla görünür bir yöntem arayın:

  1. Tdoğasında var olan yöntemler (doğrudan T[¹] üzerinde uygulanan yöntemler ).
  2. Tarafından uygulanan görünür bir özellik tarafından sağlanan yöntemlerden herhangi biri T. [...]

( [¹] hakkında not : Aslında bu ifadenin yanlış olduğunu düşünüyorum. Bir sorun açtım . Parantez içindeki cümleyi görmezden gelelim.)


Kodunuzdaki birkaç örneği ayrıntılı olarak inceleyelim! Örnekleriniz için, "boyutsuz baskı" ve "doğal yöntemler" hakkındaki kısmı göz ardı edebiliriz.

(*X{val:42}).m(): alıcı ifadesinin türü i32. Şu adımları gerçekleştiriyoruz:

  • Aday alıcı türleri listesi oluşturma:
    • i32 kayıttan çıkartılamaz, bu nedenle zaten 1. adımla işimiz bitti. Liste: [i32]
    • Sonra &i32ve ekliyoruz &mut i32. Liste:[i32, &i32, &mut i32]
  • Her aday alıcı türü için yöntemlerin aranması:
    • <i32 as M>::mHangisinin alıcı tipine sahip olduğunu buluyoruz i32. Yani zaten bitti.


Şimdiye kadar çok kolay. Şimdi daha zor bir örnek seçelim: (&&A).m(). Alıcı ifadesinin türü &&A. Şu adımları gerçekleştiriyoruz:

  • Aday alıcı türleri listesi oluşturma:
    • &&Akayıttan çıkartılabilir &A, bu yüzden bunu listeye ekliyoruz. &Atekrar kayıttan çıkartılabilir, bu yüzden Alisteye de ekliyoruz . Akayıttan çıkartılamaz, bu yüzden dururuz. Liste:[&&A, &A, A]
    • Daha Tsonra, listedeki her tür için hemen &Tve &mut Themen ekliyoruz T. Liste:[&&A, &&&A, &mut &&A, &A, &&A, &mut &A, A, &A, &mut A]
  • Her aday alıcı türü için yöntemlerin aranması:
    • Alıcı türünde bir yöntem yoktur &&A, bu yüzden listedeki bir sonraki türe gideriz.
    • <&&&A as M>::mGerçekten de alıcı tipine sahip olan yöntemi buluyoruz &&&A. Yani işimiz bitti.

İşte tüm örnekleriniz için aday alıcı listeleri. İçine alınır tipi ⟪x⟫olduğunu "kazandı", yani uydurma yöntem bulunamadı kendisi için birinci tip biridir. Ayrıca listedeki ilk türün her zaman alıcı ifadesinin türü olduğunu unutmayın. Son olarak, listeyi üç satır halinde biçimlendirdim, ancak bu sadece biçimlendiriyor: bu liste düz bir listedir.

  • (*X{val:42}).m()<i32 as M>::m
    [i32, &i32, &mut i32]
  • X{val:42}.m()<X as M>::m
    [⟪X⟫, &X, &mut X, 
     i32, &i32, &mut i32]
  • (&X{val:42}).m()<&X as M>::m
    [&X⟫, &&X, &mut &X, 
     X, &X, &mut X, 
     i32, &i32, &mut i32]
  • (&&X{val:42}).m()<&&X as M>::m
    [&&X⟫, &&&X, &mut &&X, 
     &X, &&X, &mut &X, 
     X, &X, &mut X, 
     i32, &i32, &mut i32]
  • (&&&X{val:42}).m()<&&&X as M>::m
    [&&&X⟫, &&&&X, &mut &&&X, 
     &&X, &&&X, &mut &&X, 
     &X, &&X, &mut &X, 
     X, &X, &mut X, 
     i32, &i32, &mut i32]
  • (&&&&X{val:42}).m()<&&&X as M>::m
    [&&&&X, &&&&&X, &mut &&&&X,&&&X⟫, &&&&X, &mut &&&X, 
     &&X, &&&X, &mut &&X, 
     &X, &&X, &mut &X, 
     X, &X, &mut X, 
     i32, &i32, &mut i32]
  • (&&&&&X{val:42}).m()<&&&X as M>::m
    [&&&&&X, &&&&&&X, &mut &&&&&X, 
     &&&&X, &&&&&X, &mut &&&&X,&&&X⟫, &&&&X, &mut &&&X, 
     &&X, &&&X, &mut &&X, 
     &X, &&X, &mut &X, 
     X, &X, &mut X, 
     i32, &i32, &mut i32]


  • (*X{val:42}).refm()<i32 as RefM>::refm
    [i32,&i32, &mut i32]
  • X{val:42}.refm()<X as RefM>::refm
    [X,&X⟫, &mut X, 
     i32, &i32, &mut i32]
  • (&X{val:42}).refm()<X as RefM>::refm
    [&X⟫, &&X, &mut &X, 
     X, &X, &mut X, 
     i32, &i32, &mut i32]
  • (&&X{val:42}).refm()<&X as RefM>::refm
    [&&X⟫, &&&X, &mut &&X, 
     &X, &&X, &mut &X, 
     X, &X, &mut X, 
     i32, &i32, &mut i32]
  • (&&&X{val:42}).refm()<&&X as RefM>::refm
    [&&&X⟫, &&&&X, &mut &&&X, 
     &&X, &&&X, &mut &&X, 
     &X, &&X, &mut &X, 
     X, &X, &mut X, 
     i32, &i32, &mut i32]
  • (&&&&X{val:42}).refm()<&&&X as RefM>::refm
    [&&&&X⟫, &&&&&X, &mut &&&&X, 
     &&&X, &&&&X, &mut &&&X, 
     &&X, &&&X, &mut &&X, 
     &X, &&X, &mut &X, 
     X, &X, &mut X, 
     i32, &i32, &mut i32]
  • (&&&&&X{val:42}).refm()<&&&X as RefM>::refm
    [&&&&&X, &&&&&&X, &mut &&&&&X,&&&&X⟫, &&&&&X, &mut &&&&X, 
     &&&X, &&&&X, &mut &&&X, 
     &&X, &&&X, &mut &&X, 
     &X, &&X, &mut &X, 
     X, &X, &mut X, 
     i32, &i32, &mut i32]


  • Y{val:42}.refm()<i32 as RefM>::refm
    [Y, &Y, &mut Y,
     i32,&i32, &mut i32]
  • Z{val:Y{val:42}}.refm()<i32 as RefM>::refm
    [Z, &Z, &mut Z,
     Y, &Y, &mut Y,
     i32,&i32, &mut i32]


  • A.m()<A as M>::m
    [⟪A⟫, &A, &mut A]
  • (&A).m()<A as M>::m
    [&A, &&A, &mut &A,
     ⟪A⟫, &A, &mut A]
  • (&&A).m()<&&&A as M>::m
    [&&A,&&&A⟫, &mut &&A,
     &A, &&A, &mut &A,
     A, &A, &mut A]
  • (&&&A).m()<&&&A as M>::m
    [&&&A⟫, &&&&A, &mut &&&A,
     &&A, &&&A, &mut &&A,
     &A, &&A, &mut &A,
     A, &A, &mut A]
  • A.refm()<A as RefM>::refm
    [A,&A⟫, &mut A]
  • (&A).refm()<A as RefM>::refm
    [&A⟫, &&A, &mut &A,
     A, &A, &mut A]
  • (&&A).refm()<A as RefM>::refm
    [&&A, &&&A, &mut &&A,&A⟫, &&A, &mut &A,
     A, &A, &mut A]
  • (&&&A).refm()<&&&A as RefM>::refm
    [&&&A,&&&&A⟫, &mut &&&A,
     &&A, &&&A, &mut &&A,
     &A, &&A, &mut &A,
     A, &A, &mut A]
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.