2015-08-18 13 views
8

Wenn Sie den folgenden Code kompilieren:Trait 'X' nicht für den Typ implementiert ist 'X'

trait RenderTarget {} 

struct RenderWindow; 
impl RenderTarget for RenderWindow {} 

trait Drawable { 
    fn draw<RT: RenderTarget>(&self, target: &mut RT); 
} 

fn main() { 
    let mut win = RenderWindow; 
    let mut vec: Vec<Box<Drawable>> = Vec::new(); 

    for e in &vec { 
     e.draw(&mut win); 
    } 
} 

ich den Fehler:

error: the trait `Drawable` is not implemented for the type `Drawable` [E0277] 
src/main.rs:15   e.draw(&mut win); 
         ^~~~~~~~~~~~~~ 

Was wird die Fehlermeldung zu sagen versucht? Auch, wie man es repariert?

Es gibt ein related question aber die Lösung war das Merkmal zu ändern A (was in meinem Fall Drawable entspricht), aber das ist hier nicht möglich, da Drawable von einer externen Bibliothek.

Antwort

6

Update: festen Objekt Sicherheitsregeln auf die Version 1.0 von ihnen. Nämlich, By-Wert self macht Methode Objekt-unsicher nicht mehr.

Dieser Fehler tritt wegen object safety auf.

Um ein Merkmalsobjekt außerhalb eines Merkmals erstellen zu können, muss das Merkmal objektsicher sein. Ein Merkmal ist objekt sicher, wenn diese beiden Aussagen:

  1. es hat nicht Sized Anforderung, wie in trait Whatever: Sized {};
  2. alle ihre Methoden sind objekt sicher.

Verfahren ist objekt sicher, wenn beide Aussagen zutreffen:

  1. es where Self: Sized Bedarf hat, wie in fn method() where Self: Sized;
  2. keine der folgenden Aussagen gilt:

    1. Diese Methode erwähnt Self in ihrer Signatur in jeder Form, selbst unter einer Referenz, mit Ausnahme zugehörigen Typen;
    2. Diese Methode ist statisch;
    3. diese Methode ist generisch.

Diese Einschränkungen sind in der Tat recht natürlich, wenn Sie mehr von ihnen denken.

Denken Sie daran, dass bei der Erstellung von Werten in Merkmalsobjekten tatsächliche Informationen ihres Typs einschließlich ihrer Größe gelöscht werden. Daher können Merkmalsobjekte nur über eine Referenz verwendet werden. Verweise (oder andere intelligente Zeiger, wie Box oder Rc) werden, wenn sie auf Merkmalsobjekte angewendet werden, "Fat Pointer" - zusammen mit dem Zeiger auf den Wert enthalten sie auch einen Zeiger auf die virtuelle Tabelle für diesen Wert.

Da Merkmalsobjekte nur über einen Zeiger verwendet werden können, können die Methoden nicht aufgerufen werden - Sie benötigen den tatsächlichen Wert, um solche Methoden aufzurufen. Dies war eine Verletzung von Objektsicherheit an einem Punkt, was dazu führte, dass Züge mit solchen Methoden nicht Charakterzug Objekte gemacht werden können, jedoch noch vor 1,0 die Regeln gezwickt worden durch Wert self Methoden auf Charakterzug Objekte zu ermöglichen. Diese Methoden können jedoch aus dem oben beschriebenen Grund immer noch nicht aufgerufen werden.Es gibt Gründe zu erwarten, dass diese Einschränkung in Zukunft aufgehoben wird, weil sie derzeit zu einigen Eigenheiten in der Sprache führt, zum Beispiel die Unfähigkeit, Box<FnOnce()> Schließungen zu nennen.

Self kann nicht in Methoden verwendet werden, die auf Merkmalsobjekte genau aufgerufen werden sollten, weil Merkmalsobjekte ihren tatsächlichen Typ gelöscht haben, aber um solche Methoden aufzurufen, müsste der Compiler diesen gelöschten Typ kennen.

Warum statische Methoden nicht auf Merkmalsobjekte aufgerufen werden können, ist offensichtlich - statische Methoden gehören definitionsgemäß zu dem Merkmal selbst, nicht zum Wert, also müssen Sie den konkreten Typ kennen, der das Merkmal implementiert um sie anzurufen. Konkreter gesagt, werden reguläre Methoden über eine virtuelle Tabelle gesendet, die in einem Merkmalsobjekt gespeichert ist. Statische Methoden haben jedoch keinen Empfänger, so dass sie nicht weitergeleitet werden müssen. Aus diesem Grund können sie nicht in einer virtuellen Tabelle gespeichert werden. So sind sie unkündbar, ohne den konkreten Typ zu kennen.

Generische Trait-Methoden können nicht aus einem anderen Grund aufgerufen werden, technischer als logischer, denke ich. In Rust werden generische Funktionen und Methoden durch Monomorphisierung implementiert - dh für jede Instanzierung einer generischen Funktion mit einer bestimmten Menge von Typparametern generiert der Compiler eine separate Funktion. Für den Sprachbenutzer sieht es so aus, als würden sie eine generische Funktion aufrufen; aber auf der untersten Ebene für jede Menge von Typparametern gibt es eine separate Kopie der Funktion, die darauf spezialisiert ist, für die instanziierten Typen zu arbeiten.

Bei diesem Ansatz, um generische Methoden auf einem Merkmalsobjekt aufrufen zu können, müsste seine virtuelle Tabelle Zeiger auf virtuell jede mögliche Instanz der generischen Methode für alle möglichen Typen enthalten, was natürlich unmöglich, weil es eine unendliche Anzahl von Instanziierungen erfordern würde. Und so ist das Aufrufen generischer Methoden für Merkmalsobjekte nicht erlaubt.

Wenn Drawable ein externes Merkmal ist, dann stecken Sie fest - es ist unmöglich zu tun, was Sie wollen, nämlich draw() für jedes Element in einer heterogenen Sammlung aufzurufen. Wenn Ihre Zeichensatzgruppe statisch bekannt ist, können Sie für jeden Zeichensatz eine eigene Sammlung erstellen oder alternativ Ihre eigene enum erstellen, die eine Variante für jeden ziehbaren Typ enthalten würde. Dann können Sie Drawable für das Enum selbst implementieren, was ziemlich einfach wäre.

+1

"Sie benötigen ihre virtuelle Tabelle, um Zeiger auf praktisch jede mögliche Instanziierung der generischen Methode für alle möglichen Typen zu enthalten, was [...] eine unendliche Anzahl von Instanziierungen erfordert." Nein, würde es nicht - es gibt nur eine endliche Anzahl von Typen und nur eine kleine Teilmenge wird tatsächlich im Code verwendet. Es scheint möglich zu sein, diese statisch zu erkennen, wenn es die Kosten nicht wert ist. – Veedrac

+3

"gibt es nur eine endliche Anzahl von Typen" wirklich? Es gibt unendlich viele Arten, und hier ist ein Beweis. Wenn Sie von diesen Aussagen ausgehen: 'i32 in Typen',' für alle T.Option in Typen' (die offensichtlich wahr sind), können Sie leicht alle 'i32',' Option ',' Option > ableiten 'und so weiter sind Typen. Daher ist die Menge der Typen unendlich, weil wir gerade ihre unendliche Teilmenge gefunden haben. Natürlich ist die Menge der tatsächlich in einem Programm verwendeten Typen * endlich, aber es wäre sehr schwierig, sie zu finden und Methoden für jedes generische Merkmal zu erzeugen und dann unbenutzte zu entfernen. –

+1

Ich würde auch hinzufügen, dass es aussieht, als ob es Probleme mit der Interoperabilität gibt. Angenommen, eine Eigenschaft mit einer generischen Methode ist in einer Kiste definiert und ihre Merkmalsobjekte werden in derselben Kiste verwendet. Es müsste eine Art virtueller Tisch für diese Kiste erzeugt werden. Jetzt wird diese Kiste von einer anderen Kiste benutzt, die auch Merkmalsobjekte für das gleiche Merkmal benutzt, aber mit unterschiedlichen Typparametern für ihre Methode. Jetzt haben wir zwei inkompatible virtuelle Tabellen, und so sind Merkmalsobjekte, die in der ersten und der zweiten Kiste erzeugt werden, nicht kompatibel. Es wird sogar noch lustiger, wenn wir anfangen, gemeinsame Bibliotheken zu benutzen. –

2

Wenn Sie mit dem, was Sie gegeben haben, stecken bleiben, gibt es zwei Möglichkeiten, die Sie ausprobieren könnten.

In diesem Fall kann man nicht, aber wenn man ein unsized RenderTarget

trait Drawable { 
    fn draw<RT: RenderTarget + ?Sized>(&self, target: &mut RT); 
} 

gegeben wurden, konnten Sie

trait DrawableDynamic { 
    fn draw(&self, target: &mut RenderTarget); 
} 

impl<T: Drawable> DrawableDynamic for T { 
    fn draw(&self, target: &mut RenderTarget) { 
     Drawable::draw(self, target) 
    } 
} 

implementieren, um die Arten zu leiten Sie auf eine objekt- gegeben sind sicher dynamisch versandte Alternative. Es sieht aus wie eine solche Änderung könnte stromaufwärts gemacht werden, da Sie nicht wirklich die Tatsache verwenden können, dass Größe ist.

Die andere erlaubt Ihnen nicht, beliebig Drawable s in Ihrem Vec setzen, sollte aber funktionieren, ohne unsize Typen stromaufwärts zu ermöglichen.Dies ist eine ENUM nutzen, um die möglichen Werte des Vektors zu wickeln:

enum AllDrawable { 
    Square(Square), 
    Triangle(Triangle) 
} 

impl Drawable for AllDrawable { 
    fn draw<RT: RenderTarget>(&self, target: &mut RT) { 
     match *self { 
      AllDrawable::Square(ref x) => x.draw(target), 
      AllDrawable::Triangle(ref x) => x.draw(target), 
     } 
    } 
} 

Man könnte From Implementierungen und solche hinzufügen möchten; Sie könnten es einfacher finden, wenn Sie wrapped_enum! verwenden, die diese automatisch für Sie implementieren wird.

3

Ich beziehe mich auf Vladimirs ausgezeichnete Antwort, die die Sicherheit von Object erklärt, aber ich fürchte, als mitten in der Diskussion wurde das konkrete Problem vergessen.

Wie Vladimir erwähnt, ist das Problem, dass eine Methode generische über Typen (generische über Lebenszeiten ist in Ordnung) macht die Eigenschaft, die es gehört für Laufzeit-Polymorphismus unbrauchbar; Dies wird in Rust Objektsicherheit genannt.

Die einfachste Lösung ist daher, den generischen Parameter der Methode zu entfernen!

trait RenderTarget {} 

struct RenderWindow; 
impl RenderTarget for RenderWindow {} 

trait Drawable { 
    fn draw(&self, target: &mut RenderTarget); 
} 

fn main() { 
    let mut win = RenderWindow; 
    let mut vec: Vec<Box<Drawable>> = Vec::new(); 

    for e in &vec { 
     e.draw(&mut win); 
    } 
} 

Der Hauptunterschied zwischen:

fn draw<RT: RenderTarget>(&self, target: &mut RT) 

und

fn draw(&self, target: &mut RenderTarget) 

ist, dass letztere RenderTarget sicher zu sein erfordert auch Objekt, wie es jetzt in einer Laufzeit Polymorphismus Situation verwendet wird, (also keine statische Methode, keine generische Methode, keine Self, ...). Ein weiterer (technischerer) Unterschied besteht darin, dass der erstere zur Kompilierungszeit "monophilisiert" wird (das heißt, wird durch den realen Typ ersetzt und alle relevanten Optimierungen angewendet), während letzteres nicht ist (und so, keine solchen Optimierungen) auftreten).

Verwandte Themen