reference - ¿Cuáles son las reglas exactas de auto-referencia de Rust?
dereference formal-semantics (1)
Su pseudocódigo es bastante correcto.
Para este ejemplo, supongamos que tenemos un método llamado
foo.bar()
donde
foo: T
Voy a usar la
sintaxis totalmente calificada
(FQS) para no ser ambiguo sobre el tipo de método con el que se llama, por ejemplo,
A::bar(foo)
o
A::bar(&***foo)
.
Solo voy a escribir un montón de letras mayúsculas al azar, cada una es solo un tipo / rasgo arbitrario, excepto que
T
es siempre el tipo de la variable original
foo
que se llama el método.
El núcleo del algoritmo es:
-
Para cada
"paso de desreferencia"
U
(es decir, establezcaU = T
y luegoU = *T
, ...)-
si hay una
bar
método donde el tipo de receptor (el tipo deself
en el método) coincide exactamente conU
, úselo ( un "método por valor" ) -
de lo contrario, agregue una referencia automática (tomar
&
o&mut
del receptor) y, si el receptor de algún método coincide con&U
, úselo ( un "método de autorreferencia" )
-
si hay una
Notablemente, todo considera el "tipo de receptor" del método,
no
el tipo
Self
del rasgo, es decir,
impl ... for Foo { fn method(&self) {} }
piensa en
&Foo
cuando coincide con el método, y
fn method2(&mut self)
pensaría en
&mut Foo
al hacer coincidir.
Es un error si alguna vez hay varios métodos de rasgo válidos en los pasos internos (es decir, solo puede haber cero o un método de rasgo válido en cada uno de 1. o 2., pero puede haber uno válido para cada uno: el uno de 1 se tomará primero), y los métodos inherentes tienen prioridad sobre los rasgos.
También es un error si llegamos al final del ciclo sin encontrar nada que coincida.
También es un error tener implementaciones recursivas de
Deref
, que hacen que el bucle sea infinito (alcanzarán el "límite de recursividad").
Estas reglas parecen hacer lo que quiero decir en la mayoría de las circunstancias, aunque tener la capacidad de escribir el formulario FQS inequívoco es muy útil en algunos casos extremos y para mensajes de error sensibles para el código generado por macro.
Solo se agrega una referencia automática porque
- si no hubo límite, las cosas se ponen mal / lentas, ya que cada tipo puede tener un número arbitrario de referencias tomadas
-
tomando una referencia
&foo
conserva una fuerte conexión confoo
(es la dirección defoo
sí), pero tomar más comienza a perderla:&&foo
es la dirección de alguna variable temporal en la pila que almacena&foo
.
Ejemplos
Supongamos que tenemos una llamada
foo.refm()
, si
foo
tiene tipo:
-
X
, entonces comenzamos conU = X
,refm
tiene el tipo de receptor&...
, por lo que el paso 1 no coincide, tomar una referencia automática nos da&X
, y esto sí coincide (conSelf = X
), entonces la llamada esRefM::refm(&foo)
-
&X
, comienza conU = &X
, que coincide con&self
en el primer paso (conSelf = X
), por lo que la llamada esRefM::refm(foo)
-
&&&&&X
, esto no coincide con ninguno de los pasos (el rasgo no está implementado para&&&&X
o&&&&&X
), por lo que desreferenciamos una vez para obtenerU = &&&&X
, que coincide con 1 (conSelf = &&&X
) y la llamada esRefM::refm(*foo)
-
Z
, no coincide con ninguno de los pasos, por lo que se desreferencia una vez, para obtenerY
, que tampoco coincide, por lo que se vuelve a desreferenciar, para obtenerX
, que no coincide con 1, pero coincide después de la autorefinación, por lo que la llamada esRefM::refm(&**foo)
. -
&&A
, el 1. no coincide y tampoco 2. ya que el rasgo no se implementa para&A
(para 1) o&&A
(para 2), por lo que se desreferencia a&A
, que coincide con 1., conSelf = A
Supongamos que tenemos
foo.m()
, y que
A
no es
Copy
, si
foo
tiene tipo:
-
A
, entoncesU = A
coincide conself
directamente, por lo que la llamada esM::m(foo)
conSelf = A
-
&A
, entonces 1. no coincide, y tampoco 2. (ni&A
ni&&A
implementan el rasgo), por lo que se desreferencia aA
, que sí coincide, peroM::m(*foo)
requiere tomarA
por valor y, por lo tanto, salir defoo
, de ahí el error. -
&&A
, 1. no coincide, pero el autoajuste proporciona&&&A
, que sí coincide, por lo que la llamada esM::m(&foo)
conSelf = &&&A
(Esta respuesta se basa en el código y está razonablemente cerca del archivo README (ligeramente desactualizado) . Niko Matsakis, el autor principal de esta parte del compilador / lenguaje, también echó un vistazo a esta respuesta.
Estoy aprendiendo / experimentando con Rust, y con toda la elegancia que encuentro en este idioma, hay una peculiaridad que me desconcierta y parece totalmente fuera de lugar.
Rust desreferencia automáticamente los punteros al hacer llamadas a métodos. Hice algunas pruebas para determinar el comportamiento exacto:
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<''a> M for &''a X { fn m(self) { println!("&X::m()"); } }
impl<''a, ''b> M for &''a &''b X { fn m(self) { println!("&&X::m()"); } }
impl<''a, ''b, ''c> M for &''a &''b &''c 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<''a> RefM for &''a X { fn refm(&self) { println!("&X::refm()"); } }
impl<''a, ''b> RefM for &''a &''b X { fn refm(&self) { println!("&&X::refm()"); } }
impl<''a, ''b, ''c> RefM for &''a &''b &''c 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 }
}
struct A;
impl std::marker::Copy for A {}
impl M for A { fn m(self) { println!("A::m()"); } }
impl<''a, ''b, ''c> M for &''a &''b &''c A { fn m(self) { println!("&&&A::m()"); } }
impl RefM for A { fn refm(&self) { println!("A::refm()"); } }
impl<''a, ''b, ''c> RefM for &''a &''b &''c 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::refm() , 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 == **@
(*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 == **@
Y{val:42}.refm(); // i32::refm() , self == *@
Z{val:Y{val:42}}.refm(); // i32::refm() , self == **@
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 == @
}
Entonces, parece que, más o menos:
- El compilador insertará tantos operadores de desreferencia como sea necesario para invocar un método.
-
El compilador, al resolver métodos declarados usando
&self
(llamada por referencia):-
Primero intenta pedir una única desreferencia de
self
-
Luego intenta llamar al tipo exacto de
self
- Luego, intenta insertar tantos operadores de desreferencia como sea necesario para una coincidencia
-
Primero intenta pedir una única desreferencia de
-
Los métodos declarados usando
self
(llamada por valor) para el tipoT
comportan como si se declararan usando&self
(llamada por referencia) para el tipo&T
y se invoque la referencia a lo que esté en el lado izquierdo del operador de puntos. -
Las reglas anteriores se
Deref
primero con la desreferenciación incorporada sin procesar, y si no hay coincidencia, se usa la sobrecarga con el rasgo deDeref
.
¿Cuáles son las reglas exactas de desreferencia automática? ¿Alguien puede dar alguna razón formal para tal decisión de diseño?