2015-02-20 7 views
26

diesen Code Gegeben:Warum unterstützt Rust nicht das Trait-Objekt-Upcasting?

trait Base { 
    fn a(&self); 
    fn b(&self); 
    fn c(&self); 
    fn d(&self); 
} 

trait Derived : Base { 
    fn e(&self); 
    fn f(&self); 
    fn g(&self); 
} 

struct S; 

impl Derived for S { 
    fn e(&self) {} 
    fn f(&self) {} 
    fn g(&self) {} 
} 

impl Base for S { 
    fn a(&self) {} 
    fn b(&self) {} 
    fn c(&self) {} 
    fn d(&self) {} 
} 

Leider habe ich nicht &Derived-&Base werfen können. Ich fragte mich, warum war das, weil die Derived vtable auf die eine oder andere Weise auf Base Methoden verweisen muss. die folgende

Nun, enthüllt die LLVM IR Inspektion:

@vtable4 = internal unnamed_addr constant { 
    void (i8*)*, 
    i64, 
    i64, 
    void (%struct.S*)*, 
    void (%struct.S*)*, 
    void (%struct.S*)*, 
    void (%struct.S*)* 
} { 
    void (i8*)* @_ZN2i813glue_drop.98717h857b3af62872ffacE, 
    i64 0, 
    i64 1, 
    void (%struct.S*)* @_ZN6S.Base1a20h57ba36716de00921jbaE, 
    void (%struct.S*)* @_ZN6S.Base1b20h3d50ba92e362d050pbaE, 
    void (%struct.S*)* @_ZN6S.Base1c20h794e6e72e0a45cc2vbaE, 
    void (%struct.S*)* @_ZN6S.Base1d20hda31e564669a8cdaBbaE 
} 

@vtable26 = internal unnamed_addr constant { 
    void (i8*)*, 
    i64, 
    i64, 
    void (%struct.S*)*, 
    void (%struct.S*)*, 
    void (%struct.S*)*, 
    void (%struct.S*)*, 
    void (%struct.S*)*, 
    void (%struct.S*)*, 
    void (%struct.S*)* 
} { 
    void (i8*)* @_ZN2i813glue_drop.98717h857b3af62872ffacE, 
    i64 0, 
    i64 1, 
    void (%struct.S*)* @_ZN9S.Derived1e20h9992ddd0854253d1WaaE, 
    void (%struct.S*)* @_ZN9S.Derived1f20h849d0c78b0615f092aaE, 
    void (%struct.S*)* @_ZN9S.Derived1g20hae95d0f1a38ed23b8aaE, 
    void (%struct.S*)* @_ZN6S.Base1a20h57ba36716de00921jbaE, 
    void (%struct.S*)* @_ZN6S.Base1b20h3d50ba92e362d050pbaE, 
    void (%struct.S*)* @_ZN6S.Base1c20h794e6e72e0a45cc2vbaE, 
    void (%struct.S*)* @_ZN6S.Base1d20hda31e564669a8cdaBbaE 
} 

Alle Rust vtables enthalten Zeiger auf destructor, Größe und Ausrichtung in den ersten Feldern und subtrait vtables sie nicht duplizieren, wenn supertrait Methoden verweisen, Verwenden Sie auch keinen indirekten Verweis auf Supertrait-Vtables. Sie haben nur Kopien der Methodenzeiger wörtlich und nichts anderes.

dass Design Gegeben, es ist leicht zu verstehen, warum dies nicht funktioniert. Eine neue vtable müsste in Laufzeit erstellt werden, die wahrscheinlich auf dem Stapel liegen würde, und das ist nicht gerade eine elegante (oder optimale) Lösung.

Es gibt natürlich einige Problemumgehungen wie das Hinzufügen expliziter Upcast-Methoden zur Schnittstelle, aber das erfordert ziemlich viel Boilerplate (oder Makro-Raserei), um richtig zu funktionieren.

Nun, die Frage ist - warum es nicht in irgendeiner Weise implementiert, das Merkmal Objekt Upcasting ermöglichen würde? Wie z. B. Hinzufügen eines Zeigers zur V-Tabelle des Supertraits in der Vtable des Subtrahts. Fürs Erste scheint Rust's dynamischer Versand die LSP, die ein sehr grundlegendes Prinzip für objektorientiertes Design ist, nicht zu erfüllen.

Natürlich können Sie statische Versand verwenden, die in Rust in der Tat sehr elegant ist, aber es führt leicht zu Code Bloat, die manchmal wichtiger ist als Rechenleistung - wie auf eingebetteten Systemen, und Rust-Entwickler behaupten, solche zu unterstützen Anwendungsfälle der Sprache. In vielen Fällen können Sie auch erfolgreich ein Modell verwenden, das kein reines OO ist, das durch das funktionale Design von Rust unterstützt zu werden scheint. Trotzdem unterstützt Rust viele der nützlichen OO-Muster ... also warum nicht der LSP?

Kennt jemand die Gründe für eine solche Konstruktion?

+9

Als Randnotiz: Rust ist keine objektorientierte Sprache. Eigenschaften sind keine Schnittstellen, sie sind mehr wie Typklassen von Haskell. Rust hat auch keine Subtypisierung, daher ist LSP etwas unbrauchbar, weil seine Definition an die Subtyping-Beziehung gebunden ist. –

+5

Wie ich bereits sagte, unterstützt Rust viele OO-artige Abstraktionen, und Eigenschaften dürfen erben und bilden so etwas wie eine Typhierarchie. Für mich wäre es natürlich, LSP für Merkmalsobjekte zu unterstützen, auch wenn OO nicht das Hauptparadigma der Sprache ist. – kFYatek

+0

Bitte stellen Sie sicher, nützliche Antworten zu verbessern und markieren Sie eine Antwort als akzeptiert, wenn es Ihr Problem gelöst hat! Wenn keine Antwort akzeptabel ist, ziehen Sie in Erwägung, Kommentare zu hinterlassen, die den Grund erklären, oder bearbeiten Sie Ihre Frage, um das Problem anders zu formulieren. – Shepmaster

Antwort

15

Ich lief in die gleiche Wand, wenn ich mit Rust gestartet. Wenn ich nun über Eigenschaften nachdenke, habe ich ein anderes Bild im Kopf als wenn ich an Klassen denke.

trait X : Y {} bedeutet, wenn Sie Eigenschaft implementieren X für struct S Sie auch brauchen Merkmal zu implementieren Y für S.

Natürlich bedeutet dies, dass ein &X weiß, es ist auch ein &Y, und bietet daher die entsprechenden Funktionen. Es würde einige Laufzeit-Aufwand (mehr Pointer-Dereferenzierung) erfordern, wenn Sie Zeiger auf Y Vtable zuerst durchlaufen mussten.

Dann wieder, das aktuelle Design + zusätzliche Zeiger auf andere vtables schaden würde wahrscheinlich nicht viel, und würde leicht Gießen ermöglicht umgesetzt werden. Vielleicht brauchen wir beide? Das ist etwas, auf internal.rust-lang.org

26

Eigentlich diskutiert werden, ich glaube, ich den Grund bekam.Ich fand eine elegante Möglichkeit, jedem Merkmal, das es wünscht, Upcasting-Unterstützung hinzuzufügen, und auf diese Weise kann der Programmierer wählen, ob er den zusätzlichen vtable-Eintrag zu dem Merkmal hinzufügt oder lieber nicht, was ein ähnlicher Kompromiss wie in ist Die virtuellen und nicht virtuellen Methoden von C++: Eleganz und Modellkorrektheit vs. Leistung.

Der Code kann wie folgt implementiert werden:

trait Base : AsBase { 
    ... 
} 

trait AsBase { 
    fn as_base(&self) -> &Base; 
} 

impl<T: Base> AsBase for T { 
    fn as_base(&self) -> &Base { self } 
} 

Natürlich ist eine weitere Methoden hinzufügen kann einen &mut Zeiger oder eine Box zum Gießen (das fügt eine Anforderung, dass T ein 'static Typ sein muss), aber diese ist eine allgemeine Idee. Dies ermöglicht eine sichere und einfache (wenn auch nicht implizite) Upcasting aller abgeleiteten Typen ohne Vorsatz für jeden abgeleiteten Typ.

+0

Whoa! Und das sieht makrofähig aus. –

+0

Leider ist der Einsatzbereich dieses 'impl Trait'-Ansatzes etwas eng, wenn eine komplexe Merkmalsarchitektur verwendet wird. http://play.integer32.com/?gist=bbe93906ddab1beaa34eb33e11eda41a&version=nightly Ohne die Möglichkeit der Beseitigung der schneidenden Implementierungen (dh 'impl ') ist es erforderlich, die 'as_ zu installieren *() 'Methoden direkt in die Verschachtelung, um Konflikte zu vermeiden. – snuk182

9

Als Jun 2017 der Status dieses "Unter trait Zwang" (oder "super-trait Zwang") ist wie folgt:

  • Ein RFC akzeptiert #0401 dies als Teil des Zwanges erwähnt. Diese Konvertierung sollte also implizit erfolgen.

    coerce_inner (T) = U wo T ist ein Teilmerkmal der U;

  • Dies ist jedoch noch nicht implementiert. Es gibt ein entsprechendes Problem #18600.

Es gibt auch eine doppelte Ausgabe #5665. Kommentare erklären, was verhindert, dass dies umgesetzt wird.

  • Grundsätzlich besteht das Problem darin, Vtables für Super-Traits abzuleiten. Aktuelle Layout vtables ist wie (in x86-64 Fall) folgt:
    +-----+-------------------------------+ 
    | 0- 7|pointer to "drop glue" function| 
    +-----+-------------------------------+ 
    | 8-15|size of the data    | 
    +-----+-------------------------------+ 
    |16-23|alignment of the data   | 
    +-----+-------------------------------+ 
    |24- |methods of Self and supertraits| 
    +-----+-------------------------------+ 
    
    Sie stellen keine VTable für eine super-Eigenschaft als Teilfolge enthalten. Wir müssen zumindest einige Verbesserungen mit Vtables vornehmen.
  • Natürlich gibt es Möglichkeiten, dieses Problem zu mildern, aber viele mit unterschiedlichen Vor-/Nachteilen! Bei einer Diamantvererbung hat man einen Vorteil für die Größe der V-Tabelle. Ein anderer soll schneller sein.

Es @typelist sagt sie a draft RFC hergestellt, die gut organisierte aussieht, aber sie sehen aus wie danach verschwunden (Nov 2016).

Verwandte Themen