rust - Referencias a rasgos en estructuras.
traits (2)
A tener en cuenta para futuras referencias: la sintaxis ha cambiado desde
struct Bar<''a> {
foo: &''a Foo + ''a,
}
a
struct Bar<''a> {
foo: &''a (Foo + ''a), // with parens
}
Por RFC 438
Tengo un rasgo Foo
pub trait Foo {
fn do_something(&self) -> f64;
}
y una estructura que hace referencia a ese rasgo.
pub struct Bar {
foo: Foo,
}
Tratando de compilar lo consigo
error: reference to trait `Foo` where a type is expected; try `Box<Foo>` or `&Foo`
Cambiando la estructura a
struct Bar {
foo: &Foo,
}
Me dice error: missing lifetime specifier
Cambiando la definición a
struct Bar {
foo: Box<Foo>,
}
Compila - yay!
Sin embargo, cuando quiero una función para devolver foo
en la bar
, algo como:
impl Bar {
fn get_foo(&self) -> Foo {
self.foo
}
}
Bueno, obviamente, bar.foo
es un Box<Foo>
, así que, como es de esperar, recibo un error: reference to trait `Foo` where a type is expected; try `Box<Foo>` or `&Foo`
error: reference to trait `Foo` where a type is expected; try `Box<Foo>` or `&Foo`
Cambiando la firma a
impl Bar {
fn get_foo(&self) -> Box<Foo> {
let this = *self;
this.foo
}
}
Pero ahora me sale un error: cannot move out of dereference of `&`-pointer
en intentar desreferirme a mí self
.
Cambiando a
impl Bar {
fn get_foo(self) -> Box<Foo> {
self.foo
}
}
Todo está bien.
Asi que....
- ¿Por qué no funciona la estructura de
bar
? Supongo que tengo que encajonar ya que las estructuras tienen un diseño de memoria establecido, así que tenemos que decir que es un puntero a un rasgo (ya que no podemos saber qué tan grande será), pero ¿por qué el compilador sugiere algo que no compila? ? - ¿Por qué no puedo hacer referencia a
self
enget_foo()
- Todos los ejemplos que he visto utilizan la sintaxis deself
prestada? - ¿Cuál es la implicación de eliminar el
&
y solo el uso deself
?
Aprender Rust es fascinante, pero la seguridad de la memoria es fascinante e intimidante.
Código completo que compila:
trait Foo {
fn do_something(&self) -> f64;
}
struct Bar {
foo: Box<Foo>,
}
impl Bar {
fn get_foo(self) -> Box<Foo> {
let foo = self.foo;
foo.do_something();
foo
}
}
fn main() {}
Este es el punto difícil de los objetos de rasgos, debe ser muy explícito acerca de quién es el propietario del objeto subyacente.
De hecho, cuando utiliza un rasgo como un tipo, el objeto subyacente debe almacenarse en algún lugar, ya que los objetos del rasgo son de hecho referencias a un objeto que implementa el rasgo dado. Esta es la razón por la que no puede tener un MyTrait
como tipo, debe ser una referencia &MyTrait
o una casilla Box<MyTrait>
.
Con referencias
El primer método que probó fue con una referencia y el compilador se quejó de que faltaba un especificador de vida útil:
struct Bar {
foo : &Foo,
}
El problema es que una referencia no es propietaria del objeto subyacente y otro objeto o ámbito debe poseerlo en alguna parte: solo lo está tomando prestado. Y por lo tanto, el compilador necesita información acerca de cuánto tiempo será válida esta referencia: si el objeto subyacente fue destruido, su instancia de Bar tendría una referencia a la memoria liberada, ¡lo cual está prohibido!
La idea aquí es agregar tiempos de vida:
struct Bar<''a> {
foo : &''a (Foo + ''a),
}
Lo que le está diciendo aquí al compilador es: "El objeto de mi barra no puede sobrevivir a la referencia de Foo dentro de él". Debe especificar el tiempo de vida dos veces: una vez para el tiempo de vida de la referencia, y una vez para el objeto de rasgo en sí mismo, porque los rasgos se pueden implementar para referencias, y si el objeto subyacente es una referencia, también debe especificar su tiempo de vida.
En caso especial estaría escribiendo:
struct Bar<''a> {
foo : &''a (Foo + ''static),
}
En este caso, el ''static
requiere que el objeto subyacente sea una estructura real o una referencia &''static
, pero no se permitirán otras referencias.
Además, para construir su objeto, tendrá que darle una referencia a otro objeto que almacene usted mismo.
Terminas con algo como esto:
trait Foo {}
struct MyFoo;
impl Foo for MyFoo {}
struct Bar<''a> {
foo: &''a (Foo + ''a),
}
impl<''a> Bar<''a> {
fn new(the_foo: &''a Foo) -> Bar<''a> {
Bar { foo: the_foo }
}
fn get_foo(&''a self) -> &''a Foo {
self.foo
}
}
fn main() {
let myfoo = MyFoo;
let mybar = Bar::new(&myfoo as &Foo);
}
Con cajas
Una Caja contrariamente posee su contenido, por lo que le permite otorgar la propiedad del objeto subyacente a su estructura de Bar. Sin embargo, como este objeto subyacente podría ser una referencia, también debe especificar una duración:
struct Bar<''a> {
foo: Box<Foo + ''a>
}
Si sabe que el objeto subyacente no puede ser una referencia, también puede escribir:
struct Bar {
foo: Box<Foo + ''static>
}
y el problema de la vida desaparece por completo.
La construcción del objeto es, por lo tanto, similar, pero más simple, ya que no es necesario que usted mismo almacene el objeto subyacente, el cuadro lo maneja:
trait Foo {}
struct MyFoo;
impl Foo for MyFoo {}
struct Bar<''a> {
foo: Box<Foo + ''a>,
}
impl<''a> Bar<''a> {
fn new(the_foo: Box<Foo + ''a>) -> Bar<''a> {
Bar { foo: the_foo }
}
fn get_foo(&''a self) -> &''a Foo {
&*self.foo
}
}
fn main() {
let mybar = Bar::new(box MyFoo as Box<Foo>);
}
En este caso, la ''static
versión ''static
sería:
trait Foo {}
struct MyFoo;
impl Foo for MyFoo {}
struct Bar {
foo: Box<Foo + ''static>,
}
impl Bar {
fn new(the_foo: Box<Foo + ''static>) -> Bar {
Bar { foo: the_foo }
}
fn get_foo<''a>(&''a self) -> &''a Foo {
&*self.foo
}
}
fn main() {
let mybar = Bar::new(box MyFoo as Box<Foo>);
let x = mybar.get_foo();
}
Con el valor desnudo
Para contestar su última pregunta:
¿Cuál es la implicación de eliminar el & y solo el uso de self?
Si un método tiene una definición como esta:
fn unwrap(self) {}
Significa que consumirá su objeto en el proceso, y después de llamar a bar.unwrap()
, ya no podrá usar la bar
.
Es un proceso utilizado generalmente para devolver la propiedad de los datos que posee su estructura. Conocerás muchas funciones de unwrap()
en la biblioteca estándar.