Verweise auf Merkmale in Strukturen

72

Ich habe eine Eigenschaft Foo

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

und eine Struktur, die auf dieses Merkmal verweist

pub struct Bar {
   foo: Foo,
}

Beim Versuch zu kompilieren bekomme ich

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

Ändern der Struktur in

struct Bar {
   foo: &Foo,
}

Sagt mir error: missing lifetime specifier

Ändern der Definition in

struct Bar {
   foo: Box<Foo>,
}

Kompiliert - yay!

Wenn ich jedoch möchte, dass eine Funktion wieder fooaktiviert wird bar- so etwas wie:

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

Na offensichtlich bar.fooist ein Box<Foo>, so erwartungsgemäß bekomme icherror: reference to trait `Foo` where a type is expected; try `Box<Foo>` or `&Foo`

Ändern der Signatur in

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

Aber jetzt error: cannot move out of dereference of `&`-pointerversuche ich zu dereferenzieren self.

Wechseln zu

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

Ist alles gut.

Damit....

  1. Warum nicht &in derbar Struktur nicht? Ich gehe davon aus, dass ich boxen muss, da Strukturen ein festgelegtes Speicherlayout haben, also müssen wir sagen, dass es ein Zeiger auf ein Merkmal ist (da wir nicht wissen können, wie groß das sein wird), aber warum schlägt der Compiler etwas vor, das nicht kompiliert werden kann ?
  2. Warum kann ich nicht dereferenzieren selfin get_foo()- Alle Beispiele habe ich Gebrauch , den geliehenen gesehen selfSyntax?
  3. Was bedeutet es, das zu entfernen &und nur zu verwenden self?

Rust zu lernen ist faszinierend, aber die Gedächtnissicherheit ist sowohl faszinierend als auch einschüchternd!

Vollständiger Code, der kompiliert:

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() {}
Neil Danson
quelle

Antworten:

126

Dies ist der schwierige Punkt bei Merkmalobjekten. Sie müssen sehr genau angeben, wem das zugrunde liegende Objekt gehört.

Wenn Sie ein Merkmal als Typ verwenden, muss das zugrunde liegende Objekt irgendwo gespeichert werden, da Merkmalsobjekte tatsächlich Verweise auf ein Objekt sind, das das angegebene Merkmal implementiert. Aus diesem Grund können Sie kein Bare MyTraitals Typ haben, es muss entweder eine Referenz &MyTraitoder eine Box sein Box<MyTrait>.

Mit Referenzen

Die erste Methode, die Sie ausprobiert haben, war eine Referenz, und der Compiler hat sich über einen fehlenden Lebensdauer-Spezifizierer beschwert:

struct Bar {
   foo : &Foo,
}

Das Problem ist, dass eine Referenz das zugrunde liegende Objekt nicht besitzt und ein anderes Objekt oder ein anderer Bereich es irgendwo besitzen muss: Sie leihen es nur aus. Daher benötigt der Compiler Informationen darüber, wie lange diese Referenz gültig ist: Wenn das zugrunde liegende Objekt zerstört würde, hätte Ihre Bar-Instanz einen Verweis auf freigegebenen Speicher, was verboten ist!

Die Idee hier ist, Lebensdauern hinzuzufügen:

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

Was Sie hier dem Compiler sagen, ist: "Mein Balkenobjekt kann die darin enthaltene Foo-Referenz nicht überleben". Sie müssen die Lebensdauer zweimal angeben: einmal für die Lebensdauer der Referenz und einmal für das Merkmalobjekt selbst, da Merkmale für Referenzen implementiert werden können. Wenn das zugrunde liegende Objekt eine Referenz ist, müssen Sie auch dessen Lebensdauer angeben.

Auf besonderen Fall würde schreiben:

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

In diesem Fall ist die 'static muss das zugrunde liegende Objekt eine echte Struktur oder eine &'staticReferenz sein, andere Referenzen sind jedoch nicht zulässig.

Um Ihr Objekt zu erstellen, müssen Sie ihm einen Verweis auf ein anderes Objekt geben, das Sie selbst speichern.

Am Ende haben Sie so etwas:

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

Mit Boxen

Eine Box besitzt im Gegensatz dazu ihren Inhalt, sodass Sie Ihrer Bar-Struktur das Eigentum an dem zugrunde liegenden Objekt übertragen können. Da dieses zugrunde liegende Objekt jedoch eine Referenz sein kann, müssen Sie auch eine Lebensdauer angeben:

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

Wenn Sie wissen, dass das zugrunde liegende Objekt keine Referenz sein kann, können Sie auch schreiben:

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

und das Lebensproblem verschwindet vollständig.

Der Aufbau des Objekts ist daher ähnlich, aber einfacher, da Sie das zugrunde liegende Objekt nicht selbst speichern müssen, wird es von der Box behandelt:

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

In diesem Fall 'staticwäre die Version:

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

Mit dem bloßen Wert

So beantworten Sie Ihre letzte Frage:

Was bedeutet es, das & zu entfernen und nur sich selbst zu verwenden?

Wenn eine Methode eine Definition wie diese hat:

fn unwrap(self) {}

Dies bedeutet, dass Ihr Objekt dabei verbraucht wird und Sie nach dem Aufruf bar.unwrap()nicht mehr verwenden barkönnen.

Es ist ein Prozess, der im Allgemeinen verwendet wird, um das Eigentum an den Daten zurückzugeben, die Ihrer Struktur gehören. unwrap()In der Standardbibliothek finden Sie viele Funktionen.

Levans
quelle
BEEINDRUCKEND. Vielen Dank für so viele Details. Ich werde das ein paar Mal durchlesen, bevor ich es akzeptiere, aber es gibt mir so viel zu spielen und zu verstehen.
Neil Danson
Danke für die ausführliche Beschreibung! - Dies ist eine steile Lernkurve vor dem Hintergrund klassenbasierter Sprachen.
Robert Knight
2
Es scheint, dass sich die Lebensdauerbeschränkungen seitdem geändert haben. Ich kann jetzt schreiben foo: &'a Foound foo: Box<Foo>. Was erklärt diese Änderungen?
Nhaarman
18

Zur späteren Bezugnahme zu beachten: Die Syntax hat sich von geändert

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

zu

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

Gemäß RFC 438

Arien Malec
quelle