Jakie są dokładne zasady auto-dereferencji Rusta?

Uczę się / eksperymentuję z Rustem i w całej elegancji, którą znajduję w tym języku, jest jedna osobliwość, która mnie zaskakuje i wydaje się zupełnie nie na miejscu.

Rust automatycznie dereferuje wskaźniki podczas wywoływania metod. Zrobiłem kilka testów, aby określić dokładne zachowanie:

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 == @
}

(plac zabaw )

Wygląda na to, że mniej więcej:

  • kompilator wstawi tyle operatorów dereferencyjnych ile będzie to konieczne aby wywołać metodę.
  • kompilator, gdy rozwiązuje metody zadeklarowane za pomocą &self (call-by-reference):
    • pierwsza próba wywołania jednej dereferencji self
    • następnie próbuje wywołać dokładny typ self
    • następnie próbuje wstawić tyle operatorów dereferencji, ile potrzeba do dopasowania
  • metody zadeklarowane za pomocą self (call-by-value) dla typu T zachowują się tak, jakby zostały zadeklarowane za pomocą &self (call-by-reference) dla typu &T i wywołany odwołaniem do tego, co znajduje się po lewej stronie operatora kropki.
  • powyższe reguły są najpierw wypróbowane z wbudowanym dereferencjowaniem raw, a jeśli nie ma dopasowania, używane jest przeciążenie z Deref.

Jakie są dokładne zasady auto-dereferencji? Czy ktoś może podać jakieś formalne uzasadnienie takiej decyzji projektowej?

Author: Lukas Kalbertodt, 2015-02-14

2 answers

Twój pseudo-kod jest prawie poprawny. W tym przykładzie załóżmy, że mamy wywołanie metody foo.bar() gdzie foo: T. Użyję w pełni kwalifikowanej składni (FQS), aby być jednoznacznym, z jakim typem jest wywoływana metoda, np. A::bar(foo) lub A::bar(&***foo). Po prostu napiszę stos losowych dużych liter, każda z nich to jakiś dowolny typ / cecha, z wyjątkiem {[4] } jest zawsze typem oryginalnej zmiennej foo, na której jest wywoływana metoda.

Rdzeń algorytmu jest:

  • for each "dereference step" U (czyli ustawić U = T a następnie U = *T,...)
    1. jeśli istnieje metoda bar, w której Typ odbiornika (typ self w metodzie) pasuje dokładnie U, użyj jej (a "według metody wartości")
    2. w przeciwnym razie dodaj jeden auto-ref (weź & lub &mut odbiornika), a jeśli odbiornik jakiejś metody pasuje &U, użyj go ( "metoda autorefd")

Szczególnie, wszystko rozważa "Typ odbiornika" metody, Nie Typ Self cechy, tzn. impl ... for Foo { fn method(&self) {} } myśli o &Foo podczas dopasowywania metody, a fn method2(&mut self) myśli o &mut Foo podczas dopasowywania.

Jest to błąd, jeśli kiedykolwiek istnieje wiele metod cech ważnych w wewnętrznych krokach (to znaczy, może być tylko zero lub jedna metoda cech ważna w każdej z 1. lub 2., ale może być jeden ważny dla każdego: Ten z 1 będzie brany pierwszy), a metody wrodzone mają pierwszeństwo przed cechami jeden. Jest to również błąd, jeśli dojdziemy do końca pętli bez znalezienia niczego, co pasuje. Błędem jest również posiadanie rekurencyjnych implementacji Deref, które sprawiają, że pętla jest nieskończona (osiągną "limit rekurencji").

Reguły te wydają się robić-to-co-mam-na myśli w większości sytuacji, chociaż posiadanie zdolności do zapisu jednoznacznej formy FQS jest bardzo przydatne w niektórych przypadkach krawędzi, a dla sensownych komunikatów o błędach dla makro-wygenerowanego kodu.

Dodano tylko jedno auto-odniesienie ponieważ

  • jeśli nie było wiązania, rzeczy stają się złe / powolne, ponieważ każdy typ może mieć dowolną liczbę odwołań pobranych
  • biorąc jedno odniesienie &foo zachowuje silne połączenie z foo (jest to adres samej foo), ale biorąc więcej zaczyna go tracić: &&foo jest adresem jakiejś tymczasowej zmiennej na stosie, który przechowuje &foo.

Przykłady

Załóżmy, że mamy wywołanie foo.refm(), Jeśli foo mA typ:

  • X, wtedy my zacznij od U = X, refm mA typ odbiornika &..., więc krok 1 nie pasuje, biorąc auto-ref daje nam &X, A to pasuje (z Self = X), więc wywołanie jest RefM::refm(&foo)
  • W pierwszym kroku (z &X, zaczyna się od U = &X, które pasuje do &self w pierwszym kroku (z Self = X), a więc wywołanie to RefM::refm(foo)
  • &&&&&X, to nie pasuje do żadnego kroku (cecha nie jest zaimplementowana dla &&&&X lub &&&&&X), więc dereferujemy raz, aby uzyskać U = &&&&X, który pasuje do 1 (z Self = &&&X) i wywołanie jest RefM::refm(*foo)
  • Z, nie pasuje do żadnego kroku, więc jest dereferenced raz, aby uzyskać Y, który również nie pasuje, więc jest dereferenced ponownie, aby uzyskać X, który nie pasuje do 1, ale pasuje po autorefingu, więc wywołanie jest RefM::refm(&**foo).
  • &&A, the 1. nie pasuje do 2. ponieważ cecha nie jest zaimplementowana dla &A (dla 1) lub &&A (dla 2), więc jest dereferowana do &A, co odpowiada 1., z Self = A

Załóżmy, że mamy foo.m() i że A isn ' t Copy, if foo has type:

  • A, następnie U = A dopasowuje self bezpośrednio więc wywołanie jest M::m(foo) Z Self = A
  • &A, następnie 1. nie pasuje, podobnie jak 2. (ani &A, ani &&A nie implementują cechy), więc jest ona dereferowana do A, co pasuje, ale M::m(*foo) wymaga wzięcia A według wartości i stąd wyprowadzenie z foo, stąd błąd.
  • &&A, 1. nie pasuje, ale autorefing daje &&&A, który pasuje, więc wywołanie jest M::m(&foo) z Self = &&&A.

(ta odpowiedź opiera się na kod , i jest dość blisko (nieco przestarzały) README . Niko Matsakis, główny autor tej części kompilatora / języka, również spojrzał na tę odpowiedź.)

 161
Author: huon,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-06-20 10:04:48

Odniesienie do Rusta zawiera rozdział o wyrażeniu wywołania metody. Skopiowałem najważniejszą część poniżej. Przypomnienie: mówimy o wyrażeniu recv.m(), gdzie recv nazywa się poniżej "wyrażeniem odbiorcy".

Pierwszym krokiem jest zbudowanie listy kandydujących typów odbiorników. Uzyskaj je przez wielokrotne odwoływanie się do typu wyrażenia odbiornika, dodawanie każdego napotkanego typu do listy, a następnie na końcu próby przymusu bez rozmiaru i dodanie typu wyniku, jeśli się powiedzie. Następnie, dla każdego kandydata T, dodaj &T i &mut T do listy bezpośrednio po T.

Na przykład, jeśli odbiorca ma typ Box<[i32;2]>, to typy kandydatów będą Box<[i32;2]>, &Box<[i32;2]>, &mut Box<[i32;2]>, [i32; 2] (autor: dereferencing), &[i32; 2], &mut [i32; 2], [i32] (przez przymus nieograniczony), &[i32], i wreszcie &mut [i32].

Następnie, dla każdego typu kandydata T, wyszukaj widoczną metodę z odbiornikiem tego typu w następujący sposób miejsca:

  1. T's inherent methods (metody zaimplementowane bezpośrednio na T [1]).
  2. każda z metod dostarczanych przez widoczną cechę zaimplementowaną przez T. [...]

(uwaga na temat [1] : uważam, że to sformułowanie jest błędne. otworzyłem problem. Zignorujmy to zdanie w nawiasie.)


Przejrzyjmy kilka przykładów z twojego kodu w szczegółach! Na twoje przykłady, my można zignorować część o "przymusie bezwymiarowym" i "metodach wrodzonych".

(*X{val:42}).m(): typ wyrażenia odbiorczego to i32. Wykonujemy następujące czynności:

  • Tworzenie listy typów kandydatów:
    • i32 nie można dereferować, więc już skończyliśmy z krokiem 1. Lista: [i32]
    • następnie dodajemy &i32 i &mut i32. Lista: [i32, &i32, &mut i32]
  • poszukiwanie metod dla każdego kandydata Typ odbiornika:
    • znajdujemy <i32 as M>::m, który ma typ odbiornika i32. Więc już skończyliśmy.


Na razie tak łatwo. Teraz wybierzmy trudniejszy przykład: (&&A).m(). Typ wyrażenia odbiorczego to &&A. Wykonujemy następujące czynności:

  • Tworzenie listy typów kandydatów:
    • &&A można zmienić na &A, więc dodajemy to do listy. &A może być ponownie odwołany, więc dodajemy również A do listy. A nie może być odwołany, więc przestajemy. Lista: [&&A, &A, A]
    • następnie dla każdego typu T na liście dodajemy &T i &mut T bezpośrednio po T. Lista: [&&A, &&&A, &mut &&A, &A, &&A, &mut &A, A, &A, &mut A]
  • Wyszukiwanie metod dla każdego typu odbiornika kandydata:
    • nie ma metody z typem odbiornika &&A, więc przechodzimy do następnego typu na liście.
    • znajdujemy metodę {[67] } która rzeczywiście ma typ odbiornika &&&A. Więc skończyliśmy.

Oto lista kandydatów na odbiorców dla wszystkich Twoich przykładów. Typ, który jest zamknięty w ⟪x⟫ to ten, który "wygrał", tzn. pierwszy typ, dla którego można znaleźć metodę dopasowania. Pamiętaj również, że pierwszym typem na liście jest zawsze typ wyrażenia odbiorcy. Na koniec sformatowałem listę w trzech wierszach, ale to tylko formatowanie: ta lista jest płaską listą.

  • (*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]
    
 12
Author: Lukas Kalbertodt,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2019-11-16 01:44:36