web-dev-qa-db-fra.com

Références aux traits dans les structures

J'ai un trait Foo

pub trait Foo {
   fn do_something(&self) -> f64;
}

et une structure qui fait référence à ce trait

pub struct Bar {
   foo: Foo,
}

Essayer de compiler j'obtiens

error: reference to trait `Foo` where a type is expected; try `Box<Foo>` or `&Foo`

Changer la structure en

struct Bar {
   foo: &Foo,
}

Me dit error: missing lifetime specifier

Changer la définition en

struct Bar {
   foo: Box<Foo>,
}

Compile - yay!

Cependant, quand je veux qu'une fonction retourne foo sur bar - quelque chose comme:

impl Bar {
    fn get_foo(&self) -> Foo {
        self.foo
    }
}

Bien évidemment, bar.foo Est un Box<Foo>, Donc j'attends error: reference to trait `Foo` where a type is expected; try `Box<Foo>` or `&Foo`

Changer la signature en

impl Bar {
    fn get_foo(&self) -> Box<Foo> {
        let this = *self;
        this.foo
    }
}

Mais maintenant, je reçois error: cannot move out of dereference of `&`-pointer En essayant de déréférencer self.

Changer pour

impl Bar {
    fn get_foo(self) -> Box<Foo> {
        self.foo
    }
}

Tout va bien.

Donc....

  1. Pourquoi & Dans la structure bar ne fonctionne-t-il pas? Je suppose que je dois encadrer car les structures ont une disposition de mémoire définie, nous devons donc dire que c'est un pointeur vers un trait (car nous ne pouvons pas savoir quelle taille ce sera), mais pourquoi le compilateur suggère-t-il quelque chose qui ne compilera pas ?
  2. Pourquoi ne puis-je pas déréférencer self dans get_foo() - Tous les exemples que j'ai vus utilisent la syntaxe self empruntée?
  3. Quelle est l'implication de supprimer le & Et d'utiliser simplement self?

Apprendre Rust est fascinant, mais la sécurité de la mémoire est à la fois fascinante et intimidante!

Code complet qui compile:

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() {}
54
neil danson

C'est le point délicat des objets de trait, vous devez être très explicite sur le propriétaire de l'objet sous-jacent.

En effet, lorsque vous utilisez un trait comme type, l'objet sous-jacent doit être stocké quelque part, car les objets trait sont en fait des références à un objet implémentant le trait donné. C'est pourquoi vous ne pouvez pas avoir un MyTrait nu comme type, il doit être soit une référence &MyTrait Soit une boîte Box<MyTrait>.

Avec références

La première méthode que vous avez essayée était avec une référence et le compilateur s'est plaint d'un spécificateur de durée de vie manquant:

struct Bar {
   foo : &Foo,
}

Le problème est qu'une référence ne possède pas l'objet sous-jacent et qu'un autre objet ou étendue doit le posséder quelque part: vous l'empruntez seulement. Et donc, le compilateur a besoin d'informations sur la durée de validité de cette référence: si l'objet sous-jacent était détruit, votre instance Bar aurait une référence à la mémoire libérée, ce qui est interdit!

L'idée ici est d'ajouter des durées de vie:

struct Bar<'a> {
   foo : &'a (Foo + 'a),
}

Ce que vous dites ici au compilateur est: "Mon objet Bar ne peut pas survivre à la référence Foo qu'il contient". Vous devez spécifier la durée de vie deux fois: une fois pour la durée de vie de la référence et une fois pour l'objet trait lui-même, car les traits peuvent être implémentés pour les références, et si l'objet sous-jacent est une référence, vous devez également spécifier sa durée de vie.

Dans un cas spécial, il faudrait écrire:

struct Bar<'a> {
   foo : &'a (Foo + 'static),
}

Dans ce cas, le 'static Requiert que l'objet sous-jacent soit une vraie structure ou une référence &'static, Mais les autres références ne seront pas autorisées.

De plus, pour construire votre objet, vous devrez lui donner une référence à un autre objet que vous stockez vous-même.

Vous vous retrouvez avec quelque chose comme ça:

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);
}

Avec des boîtes

Une boîte possède au contraire son contenu, elle vous permet donc de donner la propriété de l'objet sous-jacent à votre structure Bar. Cependant, comme cet objet sous-jacent pourrait être une référence, vous devez également spécifier une durée de vie:

struct Bar<'a> {
    foo: Box<Foo + 'a>
}

Si vous savez que l'objet sous-jacent ne peut pas être une référence, vous pouvez également écrire:

struct Bar {
    foo: Box<Foo + 'static>
}

et le problème de la vie disparaît complètement.

La construction de l'objet est donc similaire, mais plus simple car vous n'avez pas besoin de stocker vous-même l'objet sous-jacent, il est géré par la boite:

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>);
}

Dans ce cas, la version 'static Serait:

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();
}

Avec la valeur nue

Pour répondre à votre dernière question:

Quelle est l'implication de supprimer le & et de simplement utiliser soi-même?

Si une méthode a une définition comme celle-ci:

fn unwrap(self) {}

Cela signifie qu'il consommera votre objet dans le processus, et après avoir appelé bar.unwrap(), vous ne pourrez plus utiliser bar.

Il s'agit d'un processus généralement utilisé pour redonner la propriété des données appartenant à votre structure. Vous rencontrerez de nombreuses fonctions unwrap() dans la bibliothèque standard.

99
Levans

À noter pour référence future: la syntaxe est passée de

struct Bar<'a> {
    foo: &'a Foo + 'a,
}

à

struct Bar<'a> {
    foo: &'a (Foo + 'a), // with parens
}

Par RFC 438

13
Arien Malec