Rust: Tracking-Problem für TryFrom/TryInto-Merkmale

Erstellt am 5. Mai 2016  ·  240Kommentare  ·  Quelle: rust-lang/rust

Tracking-Problem für https://github.com/rust-lang/rfcs/pull/1542


Machen:

B-unstable C-tracking-issue T-libs

Hilfreichster Kommentar

Ich möchte, dass ! zu einem ausreichenden Vokabulartyp wird, sodass Result<_, !> intuitiv als „unfehlbares Ergebnis“ oder „Ergebnis, das (buchstäblich) niemals Irrt“ gelesen wird. Die Verwendung eines Alias ​​(ganz zu schweigen von einem separaten Typ!) erscheint mir etwas überflüssig, und ich kann mir vorstellen, dass dies zumindest für mich selbst eine vorübergehende Pause beim Lesen von Typsignaturen verursacht – „Warte, wie war das noch mal anders als ! ?" YMMV natürlich.

Alle 240 Kommentare

Gibt es eine Möglichkeit, allgemein einen Fehler mit dem ursprünglichen Wert auszugeben, wenn die Konvertierung fehlschlägt, ohne dass Clone erforderlich ist, sodass eine verwandte Methode, die bei einem Fehler in Panik gerät, nette Fehlermeldungen haben könnte?

Eine Diskussion darüber, ob dies in das Vorspiel aufgenommen werden sollte, steht noch aus, wenn dies stabil wird .

Entschuldigung, wenn dies woanders behandelt wird, aber was würden wir gerne sehen, bevor wir dies als stabil markieren? Ich bin mir ziemlich sicher, dass ich diese Funktionalität ein paar Mal in verschiedenen Projekten neu implementiert habe, also würde mich eine gemeinsame wiederverwendbare Eigenschaft zu 😄 machen

Wir können es für den nächsten Zyklus zur Diskussion stellen.

🔔 Dieses Heft geht nun in eine zykluslange Schlusskommentarfrist zur Stabilisierung 🔔

Als Stabilisierungspunkt möchte das Libs-Team diese Eigenschaften auch im Rahmen ihrer Stabilisierung zum Auftakt hinzufügen. Dies erfordert einen Kraterlauf, der _mindestens_ 100 % sauber ist, aber wir sind relativ zuversichtlich, dass der aktuelle Stand der Lösung es abwärtskompatibel macht, dem Vorspiel Eigenschaften hinzuzufügen.

Ich habe ein paar Fragen zum Zweck dieser Eigenschaften.

  1. Für welche Typen in std werden diese implementiert?
  2. Welche Typen sollten Implementierungen wie impl TryFrom<T> for T erhalten?
  3. Welche Typen sollten Implementierungen wie impl TryFrom<U> for T erhalten, wenn sie bereits impl From<U> for T haben?

cc @sfackler , könnten Sie den aktuellen Satz von Impls und auch einige der Gründe erläutern?

Ich denke, im Allgemeinen sollte die Intuition für From / Into genau gleich sein, außer wenn die Konvertierung fehlschlagen kann. So wie wir nach und nach Implementierungen von From und Into zu Standardbibliothekstypen hinzufügen, gehe ich davon aus, dass wir dasselbe für TryFrom und TryInto tun werden. Die wichtigste Richtlinie hier ist, dass die Konvertierung die "kanonisch offensichtliche" sein sollte - wenn es mehr als eine vernünftige Möglichkeit gibt, einen Typ in einen anderen zu konvertieren, sind TryFrom oder From möglicherweise nicht die richtige Dinge zu verwenden.

_Im Allgemeinen denke ich nicht, dass erwartet werden sollte, dass alle Typen impl TryFrom<T> for T oder manuell ein impl From<U> for T heben sollten. Insbesondere ist oft nicht klar, welchen Fehlertyp man wählen soll. Diese Arten von Implementierungen können und sollten jedoch so definiert werden, dass eine bestimmte API funktioniert. Zum Beispiel haben wir diese beiden Arten von Implementierungen für verschiedene Kombinationen primitiver Integer-Typen aus den im RFC beschriebenen Gründen. Als weiteres Beispiel ist ein Ad-hoc-Merkmal, das TryFrom / TryInto ersetzen wird, postgres::IntoConnectParams , das eine reflexive Implementierung hat, um ein ConnectParams in sich selbst umzuwandeln.

Im Allgemeinen denke ich nicht, dass erwartet werden sollte, dass alle Typen TryFrom<T> for T implizieren oder ein impl From<U> for T manuell aufheben sollten.

Wenn eine TryFrom -Konvertierung unfehlbar ist, sollte der Fehlertyp enum Void {} sein, richtig? (Oder ein ähnliches Enum mit einem anderen Namen.) Was für mich übrigens nach einem guten Grund klingt, einen allgemeinen Zweck Void std .

@SimonSapin , das eine API im IntoConnectParams -Stil sowie den im RFC beschriebenen ganzzahligen Konvertierungsanwendungsfall beschädigen würde, da die Fehlertypen der unfehlbaren und fehlbaren Konvertierungen nicht übereinstimmen würden.

@sfackler Nicht, wenn Sie für die Fehlertypen einfach From verwenden (z. B. try! )! impl<T> From<!> for T kann und sollte für diesen Zweck existieren.

So wie wir schrittweise Implementierungen von From und Into zu Standardbibliothekstypen hinzufügen, gehe ich davon aus, dass wir dasselbe für TryFrom und TryInto tun werden.

Das scheint noch nicht passiert zu sein, also weiß ich nicht, ob es daran liegt, dass sich niemand darum gekümmert hat oder es keine anwendbaren Typen in std gibt. Wenn es anwendbare Typen in std gibt, wäre es schön, einige Implementierungen dafür zu sehen. Sind zum Beispiel einige der folgenden guten Implementierungen?

impl TryFrom<u32> for char
impl TryFrom<char> for u8
impl TryFrom<Vec<u8>> for String
impl TryFrom<&[u8]> for &str

_Im Allgemeinen denke ich nicht, dass erwartet werden sollte, dass alle Typen impl TryFrom<T> for T oder manuell ein impl From<U> for T heben sollten.

Das Problem ist, dass Sie, wenn Sie TryInto<T> verwenden möchten und T nicht in Ihrer Kiste enthalten sind, einfach hoffen müssen, dass impl TryFrom<T> for T implementiert wurde. Das ist eine unglückliche Einschränkung, die es für From / Into nicht gibt.

@SimonSapin , das eine API im IntoConnectParams -Stil sowie den im RFC beschriebenen ganzzahligen Konvertierungsanwendungsfall beschädigen würde, da die Fehlertypen der unfehlbaren und fehlbaren Konvertierungen nicht übereinstimmen würden.

Bedeutet dies, dass der Fehlertyp basierend auf dem Typ, in den Sie konvertieren, irgendwie behoben ist?

Warum ist TryFrom::Err und TryInto::Err nicht durch std::error::Error begrenzt?

nicht durch std::error::Error begrenzt?

Das würde verhindern, dass Err () wird, was ein praktikabler Fehlertyp für unfehlbare Konvertierungen ist.

Wenn () der Fehlertyp ist, könnte die Funktion Err(()) zurückgeben. Das macht die Funktion nicht unfehlbar. Es liefert nur keinen nützlichen Fehler, wenn es fehlschlägt. Beim Schreiben von Code, der Konvertierungen verwendet, die möglicherweise fehlbar sind oder nicht, ist es meiner Meinung nach am besten, durch Into gebunden zu werden und eine Spezialisierung zu schreiben, die TryInto verwendet.

Sobald RFC 1216 implementiert ist , ist ! der geeignete Fehlertyp für unfehlbare Konvertierungen. Ich _denke_, das würde bedeuten, dass ein Error gebunden an TryFrom::Err / TryInto::Err in Ordnung wäre.

Ich würde es auf jeden Fall begrüßen, wenn die Implementierung From<T> zu einer Implementierung von TryFrom<T, Err = !> führen würde (und natürlich auch für Into ).

Ich _denke_, das würde bedeuten, dass eine Error -Bindung an TryFrom::Err / TryInto::Err in Ordnung wäre.

Ja, genau für solche Fälle haben wir auf jeden Fall impl Error for ! { ... } .

Ich bin etwas besorgt über die Typunterschiede im Anwendungsfall der Integer-Konvertierung, aber es scheint, als sollten wir wahrscheinlich versuchen, dies zu stabilisieren, bis ! -as-a-type landet, um die Möglichkeit zum Experimentieren zu haben.

In meinem POV sollten alle zugeordneten Typen, die Fehler darstellen, durch Error begrenzt werden. Leider haben wir bereits einen Typ ohne ihn gelassen : FromStr::Err (Die einzigen anderen öffentlichen Typen sind TryInto und TryFrom , überprüft mit ack 'type Err;' und ack 'type Error;' ). Lass das nicht noch einmal machen.

Kürzlich diskutiert, beschloss das Libs-Team, diese Eigenschaften zu stabilisieren, bis der ! -Typ ausgearbeitet ist.

Ich vermute, dass dies jetzt nicht mehr blockiert ist, oder?

Das Entfernen der Nominierung als @sfackler wird ! -Typen und diese Eigenschaften untersuchen.

Wird sich dies in absehbarer Zeit stabilisieren?

Warum sind TryFrom::Err und TryInto::Err nicht durch std::error::Error begrenzt?

std::err::Error existiert nicht in #![no_std] Builds. Außerdem bringt es in vielen Fällen keinen Vorteil, std::err::Error für einen Fehlertyp zu implementieren, selbst wenn es verfügbar ist. Daher würde ich es vorziehen, keine solche Bindung zu haben.

Ich habe damit experimentiert, dies selbst in meiner eigenen Bibliothek zu implementieren. Ich möchte die von @SimonSapin in https://github.com/rust-lang/rfcs/pull/1542#issuecomment -206804137 geäußerten Bedenken wiederholen: try!(x.try_into()); ist verwirrend, weil das Wort „versuchen“ ist verwendet zwei verschiedene Arten in der gleichen Anweisung.

Ich verstehe, dass viele Leute der Meinung sind, dass solche Dinge x.try_into()?; geschrieben werden sollten, aber ich gehöre zu einer beträchtlichen Anzahl von Leuten (basierend auf all den Debatten), die es stark vorziehen, die ? -Syntax nicht zu verwenden wegen ... all der Gründe, die in allen Debatten genannt wurden.

Ich persönlich denke, wir sollten trotzdem versuchen, ein Muster zu finden, das das Präfix try_ im Namen nicht erfordert.

Die Namensgebung liegt mir nicht besonders am Herzen, aber mir persönlich fällt nichts Besseres ein.

Es ist bereits zum Halbstandard geworden, try_ für Varianten von Funktionen zu verwenden, die ein Result zurückgeben.

Ich gehöre jedoch zu einer beträchtlichen Anzahl von Personen (basierend auf all den Debatten), die es vorziehen, die ? -Syntax nicht zu verwenden, aus ... all den Gründen, die in allen Debatten erwähnt wurden.

Diese Debatte ist jetzt vorbei, oder? Ich meine, ich sympathisiere mit dieser Seite der Debatte: Ich weiß nicht, warum die Leute sagten, sie finden try!() so nervig, ? ist weniger sichtbar, fördert faulen Umgang mit Fehlern und es scheint so eine Verschwendung von Syntax für etwas, für das wir bereits ein perfektes Makro hatten (es sei denn, es wird in Zukunft zu etwas viel Allgemeinerem erweitert).

Aber das ist jetzt Vergangenheit. ? ist stabil und verschwindet nicht. Wir könnten also alle zu ihm wechseln, damit wir alle dasselbe verwenden und uns keine Gedanken mehr über Namenskonflikte mit try! machen müssen.

Ich möchte die Bedenken von @SimonSapin in rust-lang/rfcs#1542 (Kommentar) wiederholen.

Da ich namentlich genannt werde, möchte ich sagen, dass ich diese Sorge eigentlich nicht mehr habe. Als ich diesen Kommentar machte, war der ? -Operator ein Vorschlag, dessen Zukunft ungewiss war, aber jetzt ist er da, um zu bleiben.

Außerdem denke ich, dass es wichtiger ist, sich früher als später zu stabilisieren, als eine weitere Namensrunde für Bikeshedding. Es ist Monate her, dass der RFC angenommen und implementiert wurde #[unstable] .

Es ist bereits zum Halbstandard geworden, try_ für Varianten von Funktionen zu verwenden, die ein Ergebnis zurückgeben.

Das ist das, was ich an dieser Funktion am seltsamsten finde. Die meisten Funktionen, die ich schreibe, geben ein Result zurück, aber ich habe noch nie eine dieser Funktionen mit einem try_ -Präfix benannt, außer wenn ich versuche, mit dieser Eigenschaft zu experimentieren.

Außerdem habe ich keinen praktischen Vorteil gefunden, dies zu schreiben:

impl TryInto<X> for Y {
    type Err = MyErrorType;

   fn try_into(self) -> Result<X, Self::Err> { ... }
}

Stattdessen kann ich immer einfach Folgendes schreiben, viel weniger syntaktischen Overhead:

    fn into_x(self) -> Result<X, MyErrorType> { ... }

Ich musste noch nie generischen Code schreiben, der trotz vieler Konvertierungen durch TryInto oder TryFrom parametrisiert wurde, daher ist die letztere Form für alle meine Verwendungen in den von mir definierten Typen ausreichend. Ich denke, TryInto<...> oder TryFrom<...> Parameter zu haben scheint eine fragwürdige Form zu sein.

Außerdem stellte ich fest, dass die Benennung des zugehörigen Fehlertyps Err anstelle von Error fehleranfällig war, da ich immer Error . Mir ist aufgefallen, dass dieser Fehler schon beim Entwurf des RFC selbst gemacht wurde: https://github.com/rust-lang/rfcs/pull/1542#r60139383. Außerdem verwendet Code, der Result verwendet, bereits ausführlich den Namen Err , da es sich um einen Result -Konstruktor handelt.

Es gab einen alternativen Vorschlag, der sich speziell auf Integer-Typen konzentrierte und Terminologie wie „widen“ und „narrow“ verwendete, z. B. x = try!(x.narrow()); , den ich ebenfalls implementiert hatte. Ich fand, dass dieser Vorschlag für meine Verwendung der hier vorgeschlagenen Funktionalität in meiner tatsächlichen Verwendung ausreichend war, da ich solche Konvertierungen nur für integrierte Integer-Typen durchführte. Es ist auch ergonomischer und übersichtlicher (IMO) für die Anwendungsfälle, für die es ausreichend ist.

Außerdem habe ich keinen praktischen Vorteil darin gefunden, dies zu schreiben ...

Ich stimme irgendwie zu. Diese Eigenschaft ist dafür da, wo ein Ding verwendet werden kann, um ein anderes Ding zu erstellen, aber manchmal kann dieser Prozess fehlschlagen – aber das klingt wie so ziemlich jede Funktion. Ich meine, sollten wir diese Impls haben?:

impl TryInto<TcpStream> for SocketAddr {
    type Err = io::Error;
    fn try_into(self) -> Result<TcpStream, io::Error> {
        TcpStream::connect(self)
    }
}

impl<T> TryInto<MutexGuard<T>> for Mutex<T> {
    type Err = TryLockError<MutexGuard<T>>;
    fn try_into(self) -> Result<Mutex<T>, Self::Err> {
        self.try_lock()
    }
}

impl TryInto<process::Output> for process::Child {
    type Err = io::Error;
    fn try_into(self) -> Result<process::Output, io::Error> {
        self.wait_with_output()
    }
}

impl TryInto<String> for Vec<u8> {
    type Err = FromUtf8Error;
    fn try_into(self) -> Result<String, FromUtf8Error> {
        String::from_utf8(self)
    }
}

Diese Eigenschaft scheint fast zu allgemein zu sein. In allen obigen Beispielen wäre es viel besser, explizit zu sagen, was Sie tatsächlich tun, anstatt try_into zu nennen.

Ich denke, TryInto<...> oder TryFrom<...> Parameter zu haben scheint eine fragwürdige Form zu sein.

Stimme auch zu. Warum würden Sie nicht einfach die Konvertierung durchführen und den Fehler behandeln, bevor Sie den Wert an die Funktion übergeben?

std::err::Error existiert nicht in #![no_std] Builds. Außerdem bringt es in vielen Fällen keinen Vorteil, std::err::Error für einen Fehlertyp zu implementieren, selbst wenn er verfügbar ist. Daher würde ich es vorziehen, keine solche Bindung zu haben.

Der einzige Vorteil der Begrenzung durch std::error::Error ist, dass es der Rückgabewert von cause() eines anderen Fehlers sein kann. Ich weiß wirklich nicht, warum es kein core::error::Error gibt, aber ich habe das nicht untersucht.

Außerdem stellte ich fest, dass die Benennung des zugehörigen Fehlertyps Err anstelle von Error fehleranfällig war, da ich immer Error eintippte. Mir ist aufgefallen, dass dieser Fehler schon beim Entwurf des RFC selbst gemacht wurde: rust-lang/rfcs#1542 (Kommentar). Außerdem verwendet Code, der Result verwendet, bereits ausführlich den Namen Err, da es sich um einen Result-Konstruktor handelt.

FromStr , das stabil ist, verwendet auch Err für seinen zugeordneten Typ. Ob es nun der beste Name ist oder nicht, ich denke, es ist wichtig, ihn in der gesamten Standardbibliothek konsistent zu halten.

Unabhängig davon, ob TryFrom und TryInto zu allgemein sind oder nicht, würde ich wirklich gerne fehlbare Konvertierungen, zumindest zwischen Integer-Typen, in der Standardbibliothek sehen. Ich habe eine Kiste dafür, aber ich denke, die Anwendungsfälle gehen weit genug, um es zum Standard zu machen. Damals, als Rust Alpha oder Beta war, erinnere ich mich, FromPrimitive und ToPrimitive für diesen Zweck verwendet zu haben, aber diese Eigenschaften hatten noch größere Probleme.

Error kann aufgrund von Kohärenzproblemen nicht nach core verschoben werden.

Auch aufgrund von Kohärenzproblemen implementiert $ Box<Error> nicht Error , ein weiterer Grund, warum wir den Typ Err nicht binden.

Es ist sowieso nicht nötig, es bei der Merkmalsdefinition zu binden - Sie können diese Grenze später jederzeit selbst hinzufügen:

where T: TryInto<Foo>, T::Err: Error

Normalerweise kommentiere ich diese Threads nicht, aber ich habe eine Weile darauf gewartet, und wie von mehreren oben zum Ausdruck gebracht, bin ich mir nicht sicher, was die Verzögerung ist. Ich möchte immer ein from-Merkmal, das fehlschlagen kann. Ich habe überall Code, der try_from heißt ... Diese Eigenschaft kapselt diese Idee _perfekt_ ein. Bitte lassen Sie mich es auf stabilem Rost verwenden.

Ich habe auch eine ganze Reihe anderer Dinge geschrieben, aber ich habe es seitdem gelöscht, da die Kohärenz der Eigenschaften leider verhindert, dass diese Eigenschaft so nützlich ist, wie sie für mich sein könnte. Zum Beispiel habe ich im Wesentlichen eine spezialisierte Version dieses genau gleichen Merkmals für die primitiven Typen für einen hochgradig generischen Parser dieser Typen neu implementiert. Keine Sorge, ich werde ein andermal darüber schimpfen.

Abgesehen davon glaube ich, dass str::parse davon stark profitieren wird, da es auch ein FromStr -Merkmal als Grenze spezialisiert - was genau ( TryFrom<str> ) handspezialisiert ist.

Korrigieren Sie mich also, wenn ich falsch liege, aber ich glaube, wenn Sie dies stabilisieren und für str::parse verwenden, wird dies:

  1. Entfernen Sie die Boilerplate-Neuimplementierung und entfernen Sie somit ein weniger bekanntes und spezialisiertes Merkmal
  2. fügen Sie TryFrom<str> in die Signatur ein, die sich eigentlich im Konvertierungsmodul befindet und deutlicher macht, was sie tut
  3. ermöglicht Upstream-Clients, TryFrom<str> für ihre Datentypen zu implementieren und str.parse::<YourSweetDataType>() kostenlos zu erhalten, zusammen mit anderen try_from , die sie implementieren möchten, was zu einer besseren logischen Codeorganisation führt.

Abgesehen von Abstraktionen, Wiederverwendung von Code, bla bla, denke ich, dass einer der untertriebenen Vorteile solcher Merkmale der semantische Kauf ist, den sie Anfängern und Veteranen gleichermaßen bieten. Im Wesentlichen liefern sie (oder beginnen zu liefern, je mehr wir uns stabilisieren) eine einheitliche Landschaft mit vertrautem, kanonisiertem Verhalten. Default , From , Clone sind wirklich großartige Beispiele dafür. Sie bieten eine einprägsame Funktionslandschaft, auf die Benutzer bei bestimmten Operationen zugreifen können und deren Verhalten und Semantik sie bereits gut kennen (einmal lernen, überall anwenden). Z.B:

  1. Ich möchte eine Standardversion; oh, lass mich nach SomeType::default() greifen
  2. Ich möchte dies konvertieren, ich frage mich, ob SomeType::from(other) implementiert ist (Sie können es einfach eingeben und sehen, ob es kompiliert wird, ohne nach Dokumentation zu greifen)
  3. Ich möchte das klonen, clone()
  4. Ich möchte versuchen, dies daraus zu bekommen, und da Fehler ein wesentlicher Bestandteil von Rost sind, kann es in der Signatur fehlschlagen, also lassen Sie mich try_from - oh warte: P

Alle diese Methoden werden alltäglich und werden Teil des Rust-Benutzer-Toolkits, das meiner Meinung nach den logischen Aufwand reduziert, indem es uns ermöglicht, nach vertrauten Konzepten (und das ist nur ein anderer Name für eine Eigenschaft!) Zu greifen, deren Dokumentation und semantisches Verhalten wir bereits verinnerlicht haben.

Natürlich stimmen sie nicht immer überein, in diesem Fall spezialisieren wir unsere Datenstrukturen, aber Eigenschaften wie diese reduzieren die API-Belastung von Benutzern und Programmierern gleichermaßen, indem sie ihnen Zugang zu Konzepten geben, die sie bereits studiert haben, anstatt Dokumentation zu lesen/Ihre zu finden from_some_thing usw. Ohne Ihre Dokumentation lesen zu müssen, können Benutzer durch die Verwendung von std-Traits auf logische, robuste und effiziente Weise durch Ihre API und Datenstrukturen navigieren.

In gewissem Sinne formalisiert es die Vereinbarung eines Gentlemans untereinander und macht es für uns einfacher und vertrauter, bestimmte vertraute Operationen durchzuführen.

Und das ist alles, was ich dazu zu sagen habe ;)

Dies wurde zuvor aufgrund einer Untersuchung der Möglichkeit blockiert, ! als Fehlertyp für unfehlbare Integer-Konvertierungen zu verwenden. Da das Feature derzeit implementiert ist, scheitert dies in den einfachsten Fällen: https://is.gd/Ws3K7V.

Denken wir immer noch darüber nach, die Methodennamen zu ändern, oder sollten wir diese Funktion in FCP integrieren?

@sfackler Dieser Playground-Link funktioniert für mich, wenn ich den Rückgabetyp in Zeile 29 von Result<u32, ()> in Result<u32, !> ändere: https://is.gd/A9pWbU Er erkennt das let Ok(x) = val; nicht

@Ixrec eine Hauptmotivation für diese Merkmale waren Konvertierungen in und von C-Integer-Typedefs. Wenn ich eine Funktion habe

fn foo(x: i64) -> Result<c_long, TryFromIntError> {
    x.try_into()
}

Dies wird auf i686-Zielen kompiliert, aber nicht auf x86_64-Zielen.

Angenommen, ich möchte den Typ IntoConnectParams ersetzen: https://docs.rs/postgres/0.13.4/postgres/params/trait.IntoConnectParams.html. Wie kann ich das machen, wenn da eine Decke impl<T> TryFrom<T> for T { type Error = ! } ist? Ich brauche die reflexive Implementierung für ConnectParams , aber mit einem anderen konkreten Fehlertyp als ! .

Es erkennt nicht, dass let Ok(x) = val; ein unwiderlegbares Muster ist, wenn val vom Typ Err ist!

Beachten Sie, dass dafür eine PR offen ist .

Wenn ich eine Funktion habe ...

Dies sollte aber funktionieren

fn foo(x: i64) -> Result<c_long, TryFromIntError> {
    let val = x.try_into()?;
    Ok(val)
}

Auf die Gefahr hin, ein lästiger +1-Kommentar zu sein, möchte ich nur erwähnen, dass nach der Ankunft von Macros 1.1 in Rust 1.15 try_from das letzte Feature sein wird, das Ruma auf nächtlichem Rust hält. Stable try_from wird mit Spannung erwartet!

Etwas substanzieller...

Das ist das, was ich an dieser Funktion am seltsamsten finde. Die meisten Funktionen, die ich schreibe, geben ein Ergebnis zurück, aber ich habe noch nie eine dieser Funktionen mit einem try_-Präfix benannt, außer wenn ich versuche, mit dieser Eigenschaft zu experimentieren.

Das ist eine gute Beobachtung, aber ich denke, der Grund für das Präfix try_ ist nicht, dass es notwendig ist, den Rückgabetyp als Result zu identifizieren, sondern um ihn von dem nicht fehlbaren Äquivalent zu unterscheiden.

Außerdem stellte ich fest, dass die Benennung des zugehörigen Fehlertyps Err anstelle von Error fehleranfällig war, da ich immer Error eintippte. Mir ist aufgefallen, dass dieser Fehler schon beim Entwurf des RFC selbst gemacht wurde: rust-lang/rfcs#1542 (Kommentar). Außerdem verwendet Code, der Result verwendet, bereits ausführlich den Namen Err, da es sich um einen Result-Konstruktor handelt.

Ich stimme diesem zu. Die meisten anderen Fehlertypen, denen ich in Bibliotheken begegnet bin, heißen "Error" und mir gefällt, dass "Err" bisher immer nur Result::Err bedeutet hat. Es scheint, als würde die Stabilisierung als "Err" dazu führen (kein Wortspiel beabsichtigt), dass die Leute ständig den Namen falsch verstehen.

@canndrew Natürlich ist es möglich , Arbeit zu leisten, aber der ganze Sinn dieser Motivation für diese Funktion besteht darin, den korrekten Umgang mit dieser Art von Plattformunterschieden zu vereinfachen - wir möchten den gesamten Raum von "kompiliert auf x86, aber nicht ARM" vermeiden. .

Ich glaube, ich habe Err wegen der Konsistenz mit FromStr gewählt, aber ich würde sehr gerne zu Error wechseln, bevor ich mich stabilisiere!

das letzte Feature, das Ruma auf dem nächtlichen Rust hält

Ebenso benötigt das alternative Spielplatz-Backend nur jede Nacht für den Zugriff auf TryFrom ; Das nächtliche Serde-Zeug war für meinen Geschmack zu instabil, also wechselte ich zum Build-Skript-Setup. Mit 1.15 gehe ich zurück zu #[derive] . Ich warte gespannt darauf, dass dieses Feature stabil wird!

@sfackler Tut mir leid, dass ich nicht gefolgt bin. Im Fall von Integer-Konvertierungen klingt es so, als müssten wir c_ulong nicht je nach Plattform entweder in u32 oder u64 typisieren, sondern es irgendwie zu einem neuen Typ machen. Auf diese Weise kann ein TryFrom<c_ulong> -Impl nicht mit einem TryFrom<u{32,64}> -Impl interferieren.
Schließlich werden wir immer "kompiliert eine Plattform, aber nicht die andere" Probleme haben, wenn wir Typen auf verschiedenen Plattformen unterschiedlich definieren. Es ist eine Schande, das ansonsten völlig logische TryFrom<T> for U where U: From<T> impl opfern zu müssen, nur damit wir eine scheinbar fragwürdige Praxis unterstützen können.

Ich schlage nicht ernsthaft vor, dass wir diesen RFC blockieren, bis wir einen Integer-RFC vom neuen Typ geschrieben, zusammengeführt und stabilisiert haben. Aber wir sollten es für die Zukunft im Hinterkopf behalten.

Angenommen, ich möchte den Typ IntoConnectParams ersetzen:

Was ist hier aber das Problem? Warum würden Sie nicht einen Fehlertyp für TryFrom<ConnectParams> und einen anderen für TryFrom<&'a str> verwenden?

Ich würde nicht befürworten, dass wir buchstäblich alle FFI-Codes der Welt brechen. Nachdem wir versucht haben, ähnliche ganzzahlige Newtype-Wrapper wie Wrapping aufzugreifen, sind massive ergonomische Kosten entstanden.

Gibt es in der Standardbibliothek ein impl<T> From<!> for T ? Ich sehe es nicht in den Dokumenten, aber das könnte nur ein Rustdoc-Fehler sein. Wenn es nicht da ist, gibt es keine Möglichkeit, die $ ! Error TryFrom<ConnectParams> in die zu konvertieren, die ich tatsächlich brauche.

Nachdem wir versucht haben, ähnliche ganzzahlige Newtype-Wrapper wie Wrapping zu finden, und es nicht geschafft haben, gibt es massive ergonomische Kosten.

Ich dachte eher an die Möglichkeit, eigene Integer-Typen zu definieren a. la. C++:

trait IntLiteral: Integer {
    const SUFFIX: &'static str;
    const fn from_bytes(is_negative: bool, bytes: &[u8]) -> Option<Self>; // or whatever
}

impl IntLiteral for c_ulong {
    const SUFFIX: &'static str = "c_ulong";
    ...
}

extern fn foo(x: c_ulong);

foo(123c_ulong); // use a c_ulong literal
foo(123); // infer the type of the integer

Würde das die meisten ergonomischen Probleme lösen? Ich mag eigentlich keine benutzerdefinierten C++-Literale - oder Funktionen im Allgemeinen, die den Leuten die Möglichkeit geben, die Sprache auf verwirrende Weise zu ändern -, aber ich denke, es könnte am Ende das kleinere von zwei Übeln sein.

Gibt es in der Standardbibliothek ein impl<T> From<!> for T ?

Derzeit nicht, da es mit der From<T> for T -Implementierung in Konflikt steht. Mein Verständnis ist, dass die impl-Spezialisierung dies jedoch letztendlich bewältigen kann.

Das klingt nach etwas, zu dem wir in ein paar Jahren zurückkehren sollten.

Wie sieht der Zeitplan für die Stabilisierung der Spezialisierung und ! aus?

Für die Spezialisierung weiß ich es nicht. Für ! selbst geht es hauptsächlich darum, wann ich meine Patches zusammenführen kann.

@canndrew Ich stimme definitiv zu, dass es nicht für alles implementiert werden sollte. In den Dokumenten steht _Versuch, Self über eine Konvertierung zu erstellen_, aber was zählt als Konvertierung? Wie wäre es mit … _dasselbe von einer Darstellung in eine andere zu ändern oder einen Wrapper hinzuzufügen oder zu entfernen_? Dies umfasst Ihre Vec<u8> -> String und Mutex<T> -> MutexGuard<T> sowie Dinge wie u32 -> char und &str -> i64 ; wobei SocketAddr -> TcpStream und process::Child -> process::Output ausgeschlossen werden.

Ich habe das Gefühl, dass impl From<T> for U wahrscheinlich TryFrom<T, Err=!> for U implizieren sollte. Ohne das können Funktionen, die TryFrom<T> s nehmen, nicht auch From<T> s nehmen. (Die Verwendung try_from() | try_into() für eine konkrete, unfehlbare Konvertierung wäre wahrscheinlich nur ein Anti-Pattern. Sie _würden_ dazu in der Lage sein, aber ... es wäre dumm, also lassen Sie es einfach .)

Ich fühle mich wie implfür U sollte wahrscheinlich TryFrom bedeutenfür dich.

Einverstanden.

Du könntest es tun, aber ... es wäre albern, also lass es einfach.

Klingt nach möglichen klebrigen Fusseln.

@BlacklightShining Ich denke, es sollte für Typen implementiert werden, bei denen die "Konvertierung" angesichts des Ausgabetyps offensichtlich ist. Sobald Mehrfachkonvertierungen möglich sind (utf8/16/32? serialisieren vs. casten? etc...), sollte darauf verzichtet werden.

MEINER BESCHEIDENEN MEINUNG NACH:

Im Moment könnten wir einfach eine pauschale Implementierung von TryFrom<&str> für alles bereitstellen, was FromStr implementiert:

impl<'a, T: FromStr> TryFrom<&'a str> for T {
    type Err = <T as FromStr>::Err;
    fn try_from(s: &'a s) -> Result<T, Self::Err> {
        T::from_str(s)
    }
}

Ich würde gerne sehen, dass so etwas hinzugefügt wird, bevor try_from stabilisiert wird, oder zumindest ein Hinweis darauf in den Dokumenten. Andernfalls kann es verwirrend sein, unterschiedliche Implementierungen von TryFrom<&'a str> und FromStr zu haben.

Ich stimme zu, dass dies eine gute Aufnahme wäre, aber es könnte mit dem vorgeschlagenen Try -> TryFrom impl?

@sfackler möglicherweise. Es wäre vollkommen in Ordnung, wenn TryFrom , TryInto und FromStr alle in den Dokumenten aufeinander verweisen würden, aber meine Hauptsorge ist, dass es keinen bestehenden Standard gibt, der besagt, ob str::parse und str::try_into geben denselben Wert zurück.

Ich wäre mit einer sanften Anfrage in den Dokumenten einverstanden, dass die Leute sie implementieren sollten , um das gleiche Verhalten zu haben, obwohl ich definitiv Fälle sehen kann, in denen jemand denken könnte, dass sie anders sein könnten.

Nehmen wir beispielsweise an, dass jemand eine Password -Struktur für eine Website erstellt. Sie könnten davon ausgehen, dass "password".parse() das Passwort auf Gültigkeit prüfen und es dann in einen Hash umwandeln würde, während Password::try_from("1234abcd") davon ausgehen könnte, dass "1234abcd" bereits ein in der Datenbank gespeicherter Hash ist, und es versuchen würde um es direkt in einen Hash zu parsen, der verglichen werden kann.

Dies ist sinnvoll, wenn man bedenkt, dass der Wortlaut von parse impliziert, dass ein gewisses Maß an Parsing durchgeführt wird, während try_from nur eine Typkonvertierung ist. In Wirklichkeit möchten wir jedoch vielleicht klarstellen, dass beide Funktionen dasselbe ausführen sollen.

Obwohl das Sprachteam vorgeschlagen hat, den RFC wegen veralteter anonymer Parameter zu schließen, scheinen sich alle darin einig zu sein, dass wir idealerweise aufhören würden, neuen Code zu erstellen, der anonyme Parameter verwendet. Könnten wir vor diesem Hintergrund die Signatur von try_from / try_into aktualisieren, um den Parametern Namen zu geben? Oder wäre es wichtiger, die Symmetrie mit from / into aufrechtzuerhalten?

Wäre es außerdem sinnvoll, eine Zusammenfassung der wichtigsten noch offenen Fragen zu schreiben? Ich hoffe wirklich, dass wir uns entscheiden können, dies für den nächsten Veröffentlichungszyklus zu stabilisieren. Wie ich bereits erwähnt habe, ist es die einzige verbleibende nächtliche Funktion, die ich häufig verwende. :)

@jimmycuadra auf jeden Fall ja! Möchten Sie eine PR mit einigen Parameternamen senden?

Zum jetzigen Stand der Dinge ist meines Erachtens nur die Frage offen, ob es eine geben sollte

impl<T, U> TryFrom<U> for T
    where T: From<U>
{
    type Error = !;

    fn try_from(u: U) -> Result<T, !> {
        Ok(T::from(u))
    }
}

Dies fügt eine nette Menge an Symmetrie hinzu, macht die Dinge aber in vielen Fällen etwas lästiger, da Sie nicht länger einen einzigen Fehlertyp für die Dinge haben, die Sie konvertieren möchten.

Typ Fehler = !;

Ich schlage vor, dies in einem separaten RFC zu tun, der das Ergebnis dessen berücksichtigt, was über all die unentschiedenen Dinge in Bezug auf ! entschieden wird.

@sfackler Ich denke, es wäre wichtig, auch die Dinge zu berücksichtigen, die ich über FromStr erwähnt habe. Sollten wir ein ähnliches Impl für FromStr -Implementierer haben, oder sollten sie unterschiedlich sein dürfen, oder sollten wir nur dokumentieren, dass sie gleich sein sollten , aber nicht müssen?

Ich bin der Meinung, dass TryFrom<str> und FromStr funktional identisch sein sollten, und die Dokumentation sollte deutlich machen, dass die Implementierungen der beiden identisch sein sollen. Die Implementierung des einen sollte Ihnen auch das andere geben, zumindest in Bezug auf die Verwendung str::parse . Hätte Rust von Anfang an TryFrom gehabt, wären FromStr nie nötig gewesen. Aus diesem Grund würde ich auch TryFrom<str> als bevorzugte Form für neuen Code dokumentieren.

@jimmycuadra In diesem Fall sollten wir parse ändern, um $#$ TryFrom $#$ zu verwenden, und dann ein Blanko-Impl für FromStr -> TryFrom setzen.

Wenn wir str::parse so ändern, dass es in Form von TryFrom implementiert wird, sollten wir auch andere Implementierungen von FromStr für konkrete Typen ähnlich ändern (dh alle Implementierer in dieser Liste : https://doc.rust-lang.org/stable/std/str/trait.FromStr.html)? Sollten die Dokumente für FromStr aktualisiert werden, um stattdessen die Verwendung TryFrom vorzuschlagen? Sollten die Dokumente der aktuellen konkreten Implementierungen von FromStr in die Version TryFrom verschoben werden?

Ich denke, aus Gründen der Abwärtskompatibilität können wir FromStr nicht ändern, oder?

Das Entfernen FromStr zugunsten von TryFrom<&str> ist definitiv etwas, das man für Rust 2.0 im Hinterkopf behalten sollte.

Ja, sobald sich diese Funktion stabilisiert hat, melden wir ein Problem und markieren es mit 2.0-breakage-wishlist.

Eine Sache, die Sie auch berücksichtigen sollten, ist das Hinzufügen einer Methode parse_into zu String , die TryFrom<String> verwendet. Ich finde mich häufig dabei, TryFrom für beide zu implementieren, wenn ein Typ intern ein String speichert, aber dennoch eine Validierung erfordert.

Wenn wir impl verlassenTryFrom für T, wobei T: From Ist diese Funktion für einen zukünftigen RFC dann bereit, sich jetzt zu stabilisieren? Ich möchte wirklich keinen weiteren Veröffentlichungszyklus verpassen, also hoffe ich, dass einige Leute im Rust-Team die nötige Bandbreite haben, um zu diskutieren und eine Entscheidung zu treffen.

Ich denke, das Problem ist, dass es schwierig wäre, dieses Feature zu stabilisieren, nachdem es stabilisiert wurde und die Leute Impls für beide bereitgestellt haben.

Ich würde erwarten, dass bereits T : From<U> U : TryFrom<T> in die Änderungskategorie „ offensichtliches API-Loch “ einordnen würde, wenn die Implementierung vernünftig ist.

Das bedeutet, dass es mindestens ein T : TryFrom<T> mit Error = ! geben sollte, aber die Version für alle unfehlbaren From ist eindeutig besser als das.

IMO gibt es keine wirklich klare Unterscheidung, ob From eine TryFrom -Implementierung bereitstellen sollte oder ob TryFrom eine From -Implementierung bereitstellen sollte.

Denn einerseits könnten Sie T::from(val) nur als T::try_from(val).unwrap() betrachten, und andererseits könnten Sie T::try_from(val) nur als Ok(T::from(val)) betrachten. Welches ist besser? Ich weiß nicht.

Sie könnten T::from(val) einfach als T::try_from(val).unwrap() betrachten

Dem stimme ich nicht zu. Es wird nicht erwartet, dass From Implementierungen jemals in Panik geraten. Sinnvoll ist nur der umgekehrte Weg.

@clarcharr Da From nicht in Panik geraten sollte, sind die Optionen From in Bezug auf TryFrom<Error=!> oder umgekehrt. Aber ich würde es hassen, wenn der übliche Rat lautet: "Sie sollten TryFrom mit type Error = ! implementieren" statt "Sie sollten From implementieren".

Irgendeine Möglichkeit, etwas Bewegung zu bekommen, um dies zu stabilisieren? Uns läuft die Zeit davon, bevor 1.18 in die Beta geht. @fackler?

@rfcbot fcp zusammenführen

Teammitglied @sfackler hat vorgeschlagen, dies zusammenzuführen. Der nächste Schritt ist die Überprüfung durch den Rest der markierten Teams:

  • [x] @BurntSushi
  • [x] @Kimundi
  • [x] @alexcrichton
  • [x] @aturon
  • [x] @brson
  • [x] @sfackler

Derzeit sind keine Bedenken aufgeführt.

Sobald diese Gutachter einen Konsens erzielen, tritt die letzte Kommentierungsfrist ein. Wenn Sie ein wichtiges Problem entdecken, das zu keinem Zeitpunkt in diesem Prozess angesprochen wurde, melden Sie sich bitte!

In diesem Dokument finden Sie Informationen darüber, welche Befehle markierte Teammitglieder mir geben können.

@sfackler : Nur um nachzusehen, sind wir gut in Bezug auf verschiedene Bedenken in Bezug auf ! und pauschale Impls? Ich weiß, dass wir darüber im Bibliothekstreffen gesprochen haben, aber es wäre hilfreich, hier eine Zusammenfassung zu bekommen.

@aturon Die letzte Diskussion darüber war @sfackler , der sich fragte, ob impl From<T> for U impl TryFrom<T> for U bereitstellen sollte, wo TryFrom::Error = ! .

@briansmith schlug vor , dass eine Entscheidung darüber ein separater RFC sein sollte, sobald ungelöste Fragen rund um den nie-Typ ausgearbeitet sind.

Ist das Hauptproblem bei der Stabilisierung jetzt nicht, dass eine solche Änderung nicht vorgenommen werden kann, ohne die Abwärtskompatibilität zu brechen? Oder besteht die Lösung darin, diese Änderung einfach nicht voranzutreiben?

Ich denke, der aktuelle Satz von Impls ist unhaltbar. Ich könnte jede dieser Positionen verstehen:

  1. TryFrom ist _nur_ für fehlbare Konvertierungen gedacht, hat also keine Dinge wie u8 -> u128 oder usize -> usize .
  2. TryFrom ist für _alle_ Konvertierungen gedacht, von denen einige unfehlbar sind und daher einen unbewohnten TryFrom::Error -Typ haben.

Aber im Moment befinden sich die Dinge in einem seltsamen hybriden Zustand, in dem der Compiler Prüfcode für eine i32 -> i32 -Konvertierung einfügt und Sie dennoch keine String -> String -Konvertierung durchführen können.

Was sind die Einwände gegen ! als Fehlertyp? Das einzige, was mir bei einem schnellen Überfliegen aufgefallen ist, war "aber macht die Dinge in vielen Fällen etwas ärgerlicher, da Sie keinen einzigen Fehlertyp mehr für die Dinge haben, die Sie konvertieren möchten", aber ich bin nicht überzeugt, dass ich dem zustimme damit müssen Sie davon ausgehen, dass Sie etwas Benutzerdefiniertes mit einem benutzerdefinierten Fehlertyp im generischen Kontext übergeben haben, egal was passiert.

Ist das Hauptproblem bei der Stabilisierung jetzt nicht, dass eine solche Änderung nicht vorgenommen werden kann, ohne die Abwärtskompatibilität zu brechen? Oder besteht die Lösung darin, diese Änderung einfach nicht voranzutreiben?

Ich bin der Meinung, dass es übereifrig ist, eine generische Implementierung von TryFrom hinzuzufügen, wenn From implementiert ist. Obwohl es semantisch richtig ist, dass, wenn es eine From -Implementierung gibt, es eine TryFrom -Implementierung gibt, die keinen Fehler erzeugen kann, sehe ich diese bereitgestellte Implementierung überhaupt nicht als praktisch nützlich an, geschweige denn a häufig genug erforderlich, dass es standardmäßig bereitgestellt werden sollte. Wenn jemand dieses Verhalten aus irgendeinem Grund wirklich für seinen Typ braucht, ist es nur eine einfache Implementierung entfernt.

Wenn es ein Beispiel für einen Fall gibt, in dem Sie jemals try_from anstelle von from für eine unfehlbare Konvertierung verwendet haben, könnte ich sicherlich meine Meinung ändern.

Was sind die Einwände gegen ! als Fehlertyp?

! ist Monate von einer Stabilisierung entfernt. Wählen Sie entweder die Stabilisierung von TryFrom in naher Zukunft oder die impl<T, U> TryFrom<U> for T where T: From<U> .

Haben wir in Betracht gezogen, hier Eigenschaftsaliase (rust-lang/rfcs#1733) zu verwenden? Wenn das landet, können wir From<T> zu TryFrom<T, Error=!> , wodurch die beiden Merkmale ein und dasselbe werden.

@lfairy Das würde den Benutzer impl s von From brechen, leider.

@glaebhoerl Ja, du hast recht 😥 Der Motivationsabschnitt dieses RFC erwähnt Aliasnamen von impl s, aber der eigentliche Vorschlag verbietet sie.

(Auch wenn dies nicht der Fall war, haben die Methoden unterschiedliche Namen usw.)

Das könnte unter eine 2.0-Wunschliste gehen, aber trotzdem wird es nicht passieren, ohne etwas zu beschädigen.

Zunächst einmal vielen Dank an @sfackler für das großartige Gespräch darüber im IRC. Nachdem ich die Dinge eine Weile in meinem Kopf sitzen gelassen hatte, bin ich hier gelandet.

Wählen Sie entweder die Stabilisierung von TryFrom in naher Zukunft oder die impl<T, U> TryFrom<U> for T where T: From<U> .

Ich denke, die Kernfrage hier ist, ob unfehlbare Konversionen in das Merkmal gehören. Ich denke, dass sie es tun, für Dinge wie die unfehlbaren Konvertierungen im RFC und für die generische Verwendung (analog zu dem scheinbar nutzlosen T:From<T> ). Angesichts dessen möchte ich am meisten eine Welt vermeiden, in der von jedem Typimplementierer erwartet wird, dass er impl TryFrom<MyType> for MyType und jede From -Implementierung auch zu einer TryFrom -Implementierung führt. (Oder lassen Sie Fehler später einreichen, weil Sie sie nicht bereitgestellt haben.)

Könnten wir also die Decke implizieren, ohne ! zu stabilisieren? Ich denke, es gibt einen Weg, da wir bereits ! -ähnliche Typen in der Bibliothek haben, wie zum Beispiel std::string::ParseError . (" Diese Aufzählung ist etwas umständlich: Sie wird nie wirklich existieren. ")

Eine Skizze, wie das funktionieren könnte:

  • Ein neuer Typ, core::convert::Infallible , wird genau wie std::string::ParseError implementiert. (Vielleicht ändern Sie letzteres sogar in einen Typ-Alias ​​für ersteres.)
  • impl<T> From<Infallible> for T damit es in ? mit jedem Fehlertyp kompatibel ist (siehe die c_foo Sachen später)
  • Verwenden Sie Infallible als Error -Typ im Blanko-Implement
  • Betrachten Sie später type Infallible = !; als Teil der Stabilisierung des Never-Typs

Ich werde freiwillig eine PR machen, die das tut, wenn es hilfreich wäre, es zu konkretisieren.

Wie für c_foo : Das Obige würde weiterhin Code wie diesen zulassen:

fn foo(x: c_int) -> Result<i32, TryFromIntError> { Ok(x.try_into()?) }

Aber es würde Code wie diesen aufgrund der unterschiedlichen Fehlertypen zu einer "Fußkanone" für die Portabilität machen

fn foo(x: c_int) -> Result<i32, TryFromIntError> { x.try_into() }

Persönlich macht mir dieser Unterschied keine Sorgen, denn solange c_int ein Typ-Alias ​​ist, gibt es eine "vollautomatische" Fußwaffe:

fn foo(x: c_int) -> i32 { x }

Und im Allgemeinen fühlt sich Code, der erwartet, dass der zugeordnete Typ eines Merkmals für verschiedene Impls gleich ist, für mich wie ein Code-Geruch an. Ich lese TryFrom als „generalize From “; Wenn das Ziel "bessere Konvertierungen zwischen ganzzahligen Teilmengen" wäre - was sich auch nützlich anfühlt -, dann ist der "immer derselbe Fehlertyp" logisch, aber ich würde stattdessen etwas erwarten, das in std::num abzielt, wahrscheinlich wie num::cast::NumCast (oder boost::numeric_cast ).

(Außerdem: mit #[repr(transparent)] im FCP-Merge können die c_foo -Typen möglicherweise zu neuen Typen werden, an welcher Stelle diese Konvertierungen konsistenter sein könnten. Die From&TryFrom-Implemente könnten das C "char <= short < codieren = int <= long" Regeln sowie die standardmäßig erforderlichen Mindestgrößen für sie als Dinge wie c_int:From<i16> oder c_long:TryFrom<i64> . Dann wäre die obige Umrechnung i32:TryFrom<c_int> Plattformen, mit immer demselben Error -Typ, und das Problem verschwindet.)

In Bezug auf "Diese Aufzählung ist etwas umständlich: Sie wird niemals wirklich existieren."

Gibt es einen Grund, warum der Fehlertyp nicht einfach Einheit sein kann? Warum sich mit der einzigartigen ParseError-Struktur herumschlagen, wenn die Konversation niemals fehlschlagen kann?

@sunjay () ist die Typsystemdarstellung von "das kann passieren, aber es gibt nichts Interessantes zu sagen, wenn es passiert". Unbewohnte Typen (wie ! und std::string::ParseError ) sind das Gegenteil, die Art und Weise, wie das Typensystem sagt: "Diese Situation kann niemals passieren, also müssen Sie sich nicht damit befassen."

@jimmycuadra

Wenn es ein Beispiel für einen Fall gibt, in dem Sie try_from anstelle von from für eine unfehlbare Konvertierung verwenden würden, könnte ich meine Meinung sicherlich ändern.

@scottmcm

Ich denke, die Kernfrage hier ist, ob unfehlbare Konversionen in das Merkmal gehören.

Hier ist mein Anwendungsfall: Ich habe ein Konfigurationsdateiformat, in dem Werte bool, numerisch oder Zeichenfolgen sein können, und ein Makro zum Schreiben von wörtlichen Konfigurationswerten, wobei die Schlüssel entweder Enum-Varianten oder Zeichenfolgen sein können. Beispielsweise:

let cfg = config![
    BoolOpt::SomeCfgKey => true,
    "SomeOtherCfgKey" => 77,
];

Um es kurz zu machen, das Makro wird schließlich zu einer Liste von ($k, $v).into() -Aufrufen erweitert. Ich möchte die Konvertierung für Zeichenfolgenschlüssel überprüfen, um sicherzustellen, dass sie eine gültige Konfigurationsoption benennen, dh die Implementierung TryFrom<(String, ???)> und die Änderung des Makros zur Verwendung ($k, $v).try_into() . Es würde es schwieriger machen, all dies zu tun, wenn es keinen einzigen Methodennamen gäbe, den das Makro für alle Konvertierungen verwenden könnte.

:bell: Dies tritt jetzt in seine letzte Kommentarperiode ein , wie in der obigen Überprüfung angegeben . :Glocke:

Die Idee gefällt mir eigentlich ganz gut:

impl<U: TryFrom<T, Error=!>> From<T> for U {
    fn from(val: T) -> U {
        val.unwrap()
    }
}

Weil jeder, der TryFrom<Error=!> will, es implementieren kann, aber die Leute können immer noch From implementieren, wenn sie möchten. Vielleicht könnten wir letztendlich From verwerfen, aber das müssen wir nicht.

Der Plan von @ scottmcm zur Verwendung einer leeren Aufzählung klingt für mich großartig.

@Ericson2314 du hast geschrieben :

Nicht, wenn Sie für die Fehlertypen einfach From verwenden (zB try! )! impl<T> From<!> for T kann und sollte für diesen Zweck existieren.

Wie würde das in der Praxis funktionieren? Angenommen, ich versuche, eine Funktion wie diese zu schreiben:

fn myfn<P: TryInto<MyType>>(p: P) -> Result<(), MyError>

Abgesehen davon, dass dies natürlich nicht funktioniert, muss ich Error= auf TryInto angeben. Aber welchen Typ soll ich da schreiben? MyError scheint offensichtlich, aber dann kann ich MyType nicht mit der Decke TryFrom implizieren.

Schlägst du folgendes vor?

fn myfn<E: Into<MyError>, P: TryInto<MyType, Error=E>>(p: P) -> Result<(), MyError>

Das scheint ziemlich ausführlich zu sein.

Es gibt eine allgemeinere Frage, wie das funktionieren soll, wenn ich mehrere „unfehlbare“ Konvertierungen für denselben Typ, aber mit unterschiedlichen Fehlertypen möchte.

Vielleicht sollte die Definition TryFrom wie folgt geändert werden:

pub trait TryFrom<T, E>: Sized {
    type Error: Into<E>;

    fn try_from(t: T) -> Result<Self, E>;
}

Sie können den Fehler so einschränken, dass er in MyError konvertierbar ist, ohne ihm einen expliziten Namen geben zu müssen, wie z

fn myfn<P: TryInto<MyType>>(p: P) -> Result<(), MyError> where MyError: From<P::Error>

Es ist immer noch ein wenig ausführlich, gibt aber wirklich die Einschränkungen für den Aufruf der Funktion gut an ( Spielplatz )

BEARBEITEN: Und der Versuch mit einer Variante wie P::Error: Into<MyError> funktioniert nicht wirklich mit ? , da es keine pauschale Implementierung von From für Into gibt. Wenn ich TryFrom so ändere, wie Sie es gezeigt haben, würde ich erwarten, auf dasselbe Problem zu stoßen.

Die letzte Kommentierungsfrist ist nun abgeschlossen.

@jethrogb heh, du hast mich vor einem Jahr zitiert, also musste ich ein bisschen nachdenken. @ Nemo157 ist absolut richtig und das scheint vernünftig.

Im speziellen Fall von ! sollten wir ein pauschales Impl haben, aber ich erinnere mich, dass es ein anderes überlappt. Es ist eine ärgerliche Überschneidung, da sich beide Implementierungen auf die Implementierung einigen – undefiniertes Verhalten / toter Code.

Ein Kommentar dazu aus einem anderen Issue: https://github.com/rust-lang/rust/pull/41904#issuecomment -300908910

Hat jemand vom libs-Team Gedanken zu Scottmcms Idee ? Für mich klingt das nach einem großartigen Ansatz, und ich würde diese Funktion gerne weiter vorantreiben, nachdem ich einen weiteren Veröffentlichungszyklus verpasst habe.

Das Libs-Team hat vor ein paar Wochen noch einmal darüber gesprochen (Entschuldigung für die Verzögerung beim Schreiben)! Wir sind zu dem Schluss gekommen, dass die Ursache der Probleme mit dieser Funktion in erster Linie darin bestand, dass der FFI-Fall nicht wirklich zu anderen Anwendungsfällen dieser Merkmale passt – es ist insofern einzigartig, als Sie es auf konkrete Typen durch unterschiedliche Aliase aufrufen basierend auf Ziel.

Der grundlegende Aktionsplan besteht also darin, impl<T, U> TryFrom<T> for U where U: From<T> hinzuzufügen und die expliziten Impls für Integer-Konvertierungen zu entfernen, die unfehlbar sind. Um den FFI-Anwendungsfall zu handhaben, erstellen wir eine separate API.

Es ist interessant, einen Typ-Alias ​​zu verwenden, um das Blockieren von ! zu vermeiden. Meine einzige Sorge ist, ob ! "spezieller" ist als normale unbewohnte Typen, die zu Brüchen führen würden, wenn wir den Alias ​​von einer unbewohnten Aufzählung auf ! tauschen.

Ich habe eine PR für die "separate API" für ganzzahlige Typen geöffnet: https://github.com/rust-lang/rust/pull/42456

Ich musste noch nie generischen Code schreiben, der durch TryInto oder TryFrom parametrisiert wurde, obwohl ich viele Konvertierungen hatte, daher ist die letztere Form für alle meine Verwendungen in den von mir definierten Typen ausreichend. Ich denke, TryInto<...> oder TryFrom<...> Parameter zu haben scheint eine fragwürdige Form zu sein.

Ich beabsichtige, TryFrom zu verwenden, sobald es als Teil eines abgeleiteten Merkmals stabil ist, und es wäre wirklich seltsam, intrinsische Ad-hoc-Methoden für einige Typen als Teil eines derive -Makros aufzurufen.

Bitte entfernen Sie diese nicht.

Ich musste noch nie generischen Code schreiben, der von TryInto oder TryFrom parametrisiert wurde

Selbst wenn das der Fall ist, denke ich nicht, dass das TryInto und TryFrom viel weniger nützlich macht. Ich verwende Into und From überall in nicht generischen Kontexten. Das Hinzufügen impl s von Standardbibliotheksmerkmalen fühlt sich viel "normaler" und "erwarteter" an als eine Reihe von Ad-hoc-Konvertierungsmethoden.

Ich musste noch nie generischen Code schreiben, der von TryInto oder TryFrom parametrisiert wurde

Aus einem meiner Projekte:

pub fn put_str_lossy<C, S> (&self, s: S)
    where C: TryInto<ascii::Char>,
          S: IntoIterator<Item = C>
{
    for c in s.into_iter() {
        self.put_char(match c.try_into() {
            Ok(c) => c,
            Err(_) => ascii::QUESTION_MARK,
        });
    }
}

Wird erwartet, dass die Implementierung dieser Merkmale bestimmten Gesetzen folgt? Wenn wir zum Beispiel A in B und B in A konvertieren können, ist es dann erforderlich, dass die Konvertierung umkehrbar ist, wenn sie erfolgreich ist?:

#![feature(try_from)]

use std::convert::{TryFrom, TryInto};

fn invertible<'a, A, B, E>(a: &'a A) -> Result<(), E>
    where A: 'a + TryFrom<&'a B>,
          A: PartialEq,
          B: 'a + TryFrom<&'a A>,
          E: From<<A as TryFrom<&'a B>>::Error>,
          E: From<<B as TryFrom<&'a A>>::Error>,
{
    let b = B::try_from(a)?;
    let a2 = A::try_from(&b)?;
    assert!(a == &a2);
    Ok(())
}

edit: s/reflexive/invertierbar/

@briansmith Angesichts der Funktionsweise From würde ich sagen, dass es auf diese Weise nicht umkehrbar ist.

use std::collections::BinaryHeap;

fn main() {
    let a = vec![1, 2];
    let b = BinaryHeap::from(a.clone());
    let c = Vec::from(b);
    assert_ne!(a, c);
}

Ich wundere mich also über die aktuellen Implementierungen dieser Eigenschaft. Wie in #43127 aufgetaucht ist (siehe auch #43064), weiß ich nicht, ob es nur daran liegt, dass die Implementierung i/u128 verwendet, aber es sieht so aus, als wären Aufrufe von TryInto nicht inline. Das ist nicht sehr optimal und nicht wirklich im Sinne von Zero-Cost-Abstraktionen. Beispielsweise sollte die Verwendung <u32>::try_into<u64>() in der endgültigen Assemblierung auf eine einfache Vorzeichenerweiterung optimiert werden (vorausgesetzt, die Plattform ist 64-Bit), aber stattdessen führt dies zu einem Funktionsaufruf.

Gibt es eine Anforderung, dass die Implementierungen des Merkmals für alle Integer-Typen gleich sein müssen?

Laut #42456 werden wir impl TryFrom wahrscheinlich nicht direkt auf den Integer-Typen haben, aber wie dieses "NumCast"-Merkmal (zu dem #43127 wechseln sollte) aussieht, wird noch entworfen.

Unabhängig davon, ob sie zu einem anderen Trait wechseln, sind diese Konvertierungen heute in Libcore implementiert und können in Libcore verwendet werden. Ich denke, dass die Verwendung u128 / i128 für alle Integer-Konvertierungen aus Gründen der Einfachheit des Quellcodes erfolgt ist. Wir sollten wahrscheinlich stattdessen unterschiedlichen Code haben, je nachdem, ob der Quelltyp breiter oder schmaler als das Ziel ist. (Möglicherweise mit unterschiedlichen Makroaufrufen basierend auf #[cfg(target_pointer_width = "64")] vs. #[cfg(target_pointer_width = "32")] vs. #[cfg(target_pointer_width = "16")] .)

Erinnerung: Es ist nicht viel nötig, um diese zu entsperren! Wenn Sie dazu bereit sind, werfen Sie einen Blick auf die Zusammenfassung von @sfackler und zögern Sie nicht, mich oder andere Mitglieder des Libs-Teams anzupingen, um sich beraten zu lassen.

Mir war nicht klar, dass das libs-Team die Zustimmung gab, dass wir die Idee von @scottmcm als Problemumgehung dafür verwenden könnten, dass der Bang-Typ instabil ist. Ich kann an einem PR arbeiten, um die von @sfackler erwähnten Änderungen mithilfe der Problemumgehung vorzunehmen.

Super, danke @jimmycuadra!

Der größte Teil dieser Diskussion scheint sich um die Implementierung TryFrom für Integer-Typen zu drehen. Ob sich TryFrom stabilisiert oder nicht, sollte meiner Meinung nach nicht nur an diesen Typen liegen.

Es gibt andere nette Konvertierungen, die von diesen Merkmalen profitieren würden, wie z. B. TryFrom<&[T]> für &[T; N] . Ich habe kürzlich eine PR eingereicht, um genau dies zu implementieren: https://github.com/rust-lang/rust/pull/44764.

Konvertierungen wie diese sind mir wichtig genug, um TryFrom zu stabilisieren.

Mit # 44174 zusammengeführt, glaube ich, dass dies jetzt entsperrt ist.

Dieser PR entfernte die automatische Implementierung von FromStr für jeden Typ, der TryFrom<&str> implementiert, da das Typsystem dies derzeit nicht unterstützen kann, selbst mit Spezialisierung. Die Absicht ist, dass FromStr und parse zugunsten von TryFrom<&str> und try_into werden, sobald diese Funktion stabilisiert ist. Es ist bedauerlich, dass die vorläufige Kompatibilität zwischen den beiden verloren geht – wenn jemand Ideen für eine Notlösung hat, melden Sie sich bitte.

Wenn keine weiteren Änderungen vorgenommen werden müssen und jemand im Libs-Team grünes Licht für die Stabilisierung gibt, kann ich die Stabilisierungs-PR und eine PR durchführen, um FromStr / parse zu verwerfen.

und ein PR, um FromStr/parse zu verwerfen.

Deprecation-Warnungen sollten nicht zu Nightly hinzugefügt werden, bis der Ersatz auf Stable verfügbar ist (oder bis https://github.com/rust-lang/rust/issues/30785 implementiert ist), damit es jederzeit möglich ist, a Crate-Build ohne Warnungen auf allen drei Veröffentlichungskanälen.

Ich habe die andere PR verpasst, da Referenzen nicht zu E-Mail-Benachrichtigungen führen. Mir ist aufgefallen, dass es ein bestimmtes impl From<Infallible> for TryFromIntError gibt. Sollte das nicht wie besprochen impl<T> From<Infallible> for T sein?

@jethrogb Leider kollidiert das mit impl<T> From<T> for T und kann daher nicht durchgeführt werden (bis wir Kreuzungsimpls erhalten? - und die Verwendung ! funktioniert dort auch nicht).

@scottmcm ah natürlich.

Ich glaube nicht, dass Sie Schnittpunkte brauchen? Ist das nicht eine reine Spezialisierung?

Ich habe die anderen Kommentare nicht gelesen, aber TryFrom ist für mich gerade kaputt (es hat vorher gut funktioniert).

Rustc-Version:

rustc 1.22.0-nightly (d6d711dd8 2017-10-10)
binary: rustc
commit-hash: d6d711dd8f7ad5885294b8e1f0009a23dc1f8b1f
commit-date: 2017-10-10
host: x86_64-unknown-linux-gnu
release: 1.22.0-nightly
LLVM version: 4.0

Der relevante Codeabschnitt, den Rust bemängelt, ist hier: https://github.com/fschutt/printpdf/blob/master/src/types/plugins/graphics/two_dimensional/image.rs#L29 -L39 und https://github .com/fschutt/printpdf/blob/master/src/types/plugins/graphics/xobject.rs#L170 -L200 - es wurde vor ein paar Wochen gut kompiliert, weshalb die Bibliothek immer noch das Abzeichen "Build Passing" trägt.

Beim neuesten nächtlichen Build scheint TryFrom jedoch zu brechen:

error[E0119]: conflicting implementations of trait `std::convert::TryFrom<_>` for type `types::plugins::graphics::two_dimensional::image::Image`:
  --> src/types/plugins/graphics/two_dimensional/image.rs:29:1
   |
29 | / impl<T: ImageDecoder> TryFrom<T> for Image {
30 | |     type Error = image::ImageError;
31 | |     fn try_from(image: T)
32 | |     -> std::result::Result<Self, Self::Error>
...  |
38 | |     }
39 | | }
   | |_^
   |
   = note: conflicting implementation in crate `core`

error[E0119]: conflicting implementations of trait `std::convert::TryFrom<_>` for type `types::plugins::graphics::xobject::ImageXObject`:
   --> src/types/plugins/graphics/xobject.rs:170:1
    |
170 | / impl<T: image::ImageDecoder> TryFrom<T> for ImageXObject {
171 | |     type Error = image::ImageError;
172 | |     fn try_from(mut image: T)
173 | |     -> std::result::Result<Self, Self::Error>
...   |
199 | |     }
200 | | }
    | |_^
    |
    = note: conflicting implementation in crate `core`

error: aborting due to 2 previous errors

error: Could not compile `printpdf`.

Angeblich hat es also eine duplizierte Implementierung in Crate core . Wenn sich das jemand anschauen könnte, wäre das super, danke.

@fschutt Das widersprüchliche Impl ist wahrscheinlich impl<T, U> TryFrom<T> for U where U: From<T> , hinzugefügt in https://github.com/rust-lang/rust/pull/44174. Es könnte ein T wie sowohl T: ImageDecoder als auch Image: From<T> .

Gibt es irgendetwas, das noch benötigt wird, um das Feature-Gate zu verlassen?

Wenn https://github.com/rust-lang/rust/issues/35121 zuerst stabilisiert wird, könnten wir den in https://github.com/rust-lang/rust/pull/ eingeführten Typ Infallible entfernen. 44174 und verwenden Sie stattdessen ! . Ich weiß nicht, ob dies als Voraussetzung gilt.

Ich denke, der Hauptblocker hier sind immer noch die Integer-Typen. Entweder verwenden wir ein separates Cast -Merkmal für Integer-Typen https://github.com/rust-lang/rust/pull/42456#issuecomment -326159595, oder lassen zuerst den Portabilitätslint #41619 passieren.

Also hatte ich früher eine Aufzählung, die TryFrom für AsRef<str> implementierte, aber das ist vor ein paar Monaten kaputt gegangen. Ich dachte, es wäre ein Fehler, der in Nightly eingeführt wurde und mit der Zeit verschwinden würde, aber es scheint, dass dies nicht der Fall ist. Ist dies kein unterstütztes Muster mehr für TryFrom ?

impl<S: AsRef<str>> TryFrom<S> for MyEnum {
    type Error = &'static str;

    fn try_from(string: S) -> Result<Self, Self::Error> {
        // Impl here
    }
}
...

Welche anderen Optionen gibt es, um sowohl &str als auch String umzuwandeln?

@kybishop implementiert MyEnum FromStr ? Das kann die Ursache für Ihren Bruch sein.

@nvzqz nicht, obwohl mir empfohlen wurde, TryFrom über das Rust IRC zu verwenden, da es sich um eine allgemeinere Lösung handelt. TryFrom funktionierte anfangs großartig, bis vor ein paar Monaten eine bahnbrechende Änderung in Nightly auftrat.

BEARBEITEN: Meinen Sie damit, dass ich zur Implementierung FromStr wechseln sollte, oder dass, wenn es _did_ FromStr implementiert, dies zu einem Bruch führen könnte?

EDIT 2: Hier ist das vollständige Impl, ziemlich kurz und einfach für Neugierige: https://gist.github.com/kybishop/2fa9e9d32728167bed5b1bc0b9becd97

@kybishop gibt es einen bestimmten Grund, warum Sie eine Implementierung für AsRef<str> statt für $#$ &str $#$ wünschen?

@sfackler Ich hatte den Eindruck, dass es die Konvertierung sowohl von &str als auch String erlaubt, obwohl ich immer noch ein bisschen ein Rust-Neuling bin, also verstehe ich vielleicht genau, wie AsRef<str> wird verwendet. Ich werde versuchen, &str auszuschalten und zu sehen, ob AsRef etwas erlaubt hat, was &str nicht erlaubt.

@kybishop Dasselbe wie https://github.com/rust-lang/rust/issues/33417#issuecomment -335815206, und wie die Compiler-Fehlermeldung sagt, ist dies ein echter Konflikt mit dem impl<T, U> std::convert::TryFrom<U> for T where T: std::convert::From<U> impl, das war zu libcore hinzugefügt.

Es könnte einen Typ T (möglicherweise in einer Downstream-Crate), der beides implementiertFrom<MyEnum> und AsRef<str>und hat MyEnum: From<T> . In diesem Fall würden beide Impls gelten, also dürfen beide Impls nicht zusammen existieren.

@SimonSapin verstanden. Was sind also die Optionen für Leute, die sowohl von &str als auch &String konvertieren möchten? Müssen Sie nur TryFrom für beide implementieren?

BEARBEITEN: Ich denke, ich kann die zusätzliche Arbeit einfach essen und .as_ref() auf String s anrufen. Ich kann dann nur ein einzelnes TryFrom impl für str haben.

Ja, zwei Impls (möglicherweise mit einem auf dem anderen basierenden, wie Sie darauf hingewiesen haben) sollten funktionieren, solange MyEnum nicht From<&str> oder From<String> implementiert.

@kybishop String implementiert Deref<str> , daher sollte die Typinferenz &String ermöglichen, in &str $ zu zwingen, wenn es an die TryFrom -Implementierung übergeben wird. Es ist nicht immer erforderlich, as_ref anzurufen.

Da https://github.com/rust-lang/rust/pull/47630 dabei ist, ! zu stabilisieren, besteht hier Appetit auf eine PR, um Infallible durch ! zu ersetzen ?

Ein besserer Weg wäre, einen Alias ​​zu erstellen. Es bewahrt die Ausdruckskraft und verwendet die angepasste Sprachfunktion.

type Infallible = !;

Springe einfach rein. Da bin ich mit @scottmcm .

Dies fügt jedoch den zusätzlichen Aufwand hinzu, dass diese Funktion ( TryInto / TryFrom ) jetzt von einer anderen instabilen Funktion abhängt – never_type .

Außerdem hat Infallible den Vorteil, dass es mehr Informationen / Semantik darüber gibt, warum der Typ nicht konstruiert werden kann. Ich bin im Moment rechthaberisch mit mir.

Ich möchte, dass ! zu einem ausreichenden Vokabulartyp wird, sodass Result<_, !> intuitiv als „unfehlbares Ergebnis“ oder „Ergebnis, das (buchstäblich) niemals Irrt“ gelesen wird. Die Verwendung eines Alias ​​(ganz zu schweigen von einem separaten Typ!) erscheint mir etwas überflüssig, und ich kann mir vorstellen, dass dies zumindest für mich selbst eine vorübergehende Pause beim Lesen von Typsignaturen verursacht – „Warte, wie war das noch mal anders als ! ?" YMMV natürlich.

@jdahlstrom Ich stimme voll und ganz zu. Wir müssten das in das Rust-Buch oder das Nomicon einführen, damit es „Grundwahrheit“ und freundlich ist.

Es ist jetzt zwei Jahre her, seit der RFC für diese API eingereicht wurde.

~ @briansmith : Es ist eine Stabilisierungs-PR im Gange.~

EDIT : Vielleicht bin ich auch zu müde...

Sie haben die PR der Stabilisierung vom Typ ! verlinkt.

Da die ! -Stabilisierungs-PR gerade zusammengeführt wurde, habe ich eine PR eingereicht, um convert::Infallible durch ! zu ersetzen: #49038

https://github.com/rust-lang/rust/pull/49038 wird zusammengeführt. Ich glaube, dies war der letzte Blocker für die Stabilisierung. Bitte lassen Sie es mich wissen, wenn ich ein ungelöstes Problem übersehen habe.

@rfcbot fcp zusammenführen

rfcbot antwortet nicht, da unter https://github.com/rust-lang/rust/issues/33417#issuecomment -302817297 bereits ein anderes FCP abgeschlossen wurde.

Uhmm, es gibt ein paar Impls, die noch einmal überprüft werden sollten. Jetzt, da wir impl<T, U> TryFrom<U> for T where T: From<U> haben, sollten verbleibende Impls von TryFrom , die type Error = ! haben, entweder durch Impls von From ersetzt, entfernt oder fehlbar gemacht werden ( Ändern des Fehlertyps in einen nicht unbewohnten Typ).

Diejenigen, die ich in diesem Fall finden kann, beinhalten usize oder isize . Ich nehme an, dass die entsprechenden From Impls nicht existieren, da ihre Fehlbarkeit von der Größe des Zielzeigers abhängt. Tatsächlich werden die TryFrom Impls für verschiedene Ziele unterschiedlich generiert: https://github.com/rust-lang/rust/blob/1.24.1/src/libcore/num/mod.rs#L3103 -L3179 ​​This ist wahrscheinlich ein Portabilitätsrisiko.

für unterschiedliche Ziele unterschiedlich generiert

Zur Verdeutlichung: Unterschiedliche Methodenkörper für verschiedene target_pointer_width im gleichen impl sind in Ordnung (und wahrscheinlich notwendig), unterschiedliche APIs (Fehlertypen) sind es nicht.

Stabilisierungs-PR: #49305. Nach einigen Diskussionen dort entfernt dieser PR auch einige TryFrom Impls, die usize oder isize beinhalten, weil wir uns nicht zwischen zwei verschiedenen Möglichkeiten entschieden haben, sie zu implementieren. (Und wir können natürlich nur einen haben.)

Spezielles Tracking-Problem für diese: https://github.com/rust-lang/rust/issues/49415

TryFrom funktionierte perfekt auf rustc 1.27.0-nightly (ac3c2288f 2018-04-18) ohne Feature-Gating, brach aber zusammen, als es mit rustc 1.27.0-nightly (66363b288 2018-04-28) kompiliert wurde.

Gab es in den letzten 10 Tagen Regressionen zur Stabilisierung dieser Funktion?

@kjetilkjeka Diese Funktion wurde kürzlich destabilisiert: #50121.

(Wiedereröffnung seit Wiederherstellung der Stabilisierung)

@kjetilkjeka Die Stabilisierung von TryFrom wurde in https://github.com/rust-lang/rust/pull/50121 zusammen mit der Stabilisierung des Typs ! wegen der TryFrom<U, Error=!> for T where T: From<U> impl. Der Typ ! wurde aufgrund von https://github.com/rust-lang/rust/issues/49593 destabilisiert.

Danke fürs Erklären. Bedeutet dies, dass diese Funktion bei einigen Änderungen am Compiler-Typzwang im Wesentlichen blockiert ist? Ich kann kein Problem finden, das erklärt, welche Änderungen erforderlich sind. Ist das Ausmaß der Änderungen zu diesem Zeitpunkt bekannt?

Gibt es einen grundlegenden Grund, warum wir nicht weitermachen und die Eigenschaft TryFrom selbst und alle Impls, die nicht ! beinhalten, stabilisieren und die Stabilisierung von Impls, die ! betreffen, einfach aufschieben konnten? bis nach der möglichen Stabilisierung von ! ?

https://github.com/rust-lang/rust/pull/49305#issuecomment -376293243 klassifiziert die verschiedenen möglichen Trait-Implementierungen.

@joshtriplett Soweit ich weiß, ist insbesondere impl<T, U> TryFrom<T> for U where U: From<T> { type Err = !; } nicht abwärtskompatibel hinzuzufügen, wenn TryFrom bereits stabil war.

@SimonSapin
Warum ist das nicht abwärtskompatibel hinzuzufügen?

(Und brauchen wir wirklich die Decke impl?)

Warum ist das nicht abwärtskompatibel hinzuzufügen?

Ich denke, es ist nicht abwärtskompatibel, da TryFrom zwischen der Stabilisierung von TryFrom und der Stabilisierung ! manuell hätte implementiert werden können. Wenn die pauschale Implementierung hinzugefügt wurde, würde dies mit der manuellen Implementierung in Konflikt geraten.

(Und brauchen wir wirklich die Decke impl?)

Ich denke, das pauschale Impl ist wirklich sinnvoll, wenn generischer Code über TryFrom geschrieben wird. Wenn Sie sich auf alle Typen beziehen, die eine Möglichkeit zur Umwandlung in T haben, möchten Sie meistens auch alle Typen einbeziehen, die auf jeden Fall in T umgewandelt werden können. Ich nehme an, eine Alternative könnte darin bestehen, von jedem zu verlangen, auch TryFrom für alle Typen zu implementieren, die From implementieren, und auf die Spezialisierung zu warten, bevor die pauschale Implementierung vorgenommen wird. Sie hätten immer noch das Problem, was Err Result generisch zu machen wäre. ! scheint ein netter Weg zu sein, um die Unfehlbarkeit der Konvertierung sowohl auszudrücken als auch programmatisch durchzusetzen, und das Warten lohnt sich hoffentlich.

Wir könnten immer warten, bis die Spezialisierung verfügbar ist, um das pauschale Impl bereitzustellen und die verschiedenen numerischen Konvertierungen in der Zwischenzeit zu stabilisieren.

Ich habe verschiedene Anfragen dazu im Kontext von Leuten gesehen, die Hilfe für die idiomatische Durchführung von Integer-Konvertierungen in Rust suchten, und bin erst heute auf jemanden gestoßen, der sich speziell gefragt hat, wie er u64 in u32 mit Fehlererkennung konvertieren sollte.

Ich bin nicht davon überzeugt, dass die Spezialisierung es auf magische Weise erlaubt, das Blanko-Impl im Nachhinein hinzuzufügen. Mein Verständnis der aktuellen Vorschläge ist, dass sich ein Merkmal dafür entscheiden muss, spezialisierbar zu sein, was möglicherweise nicht rückwärtskompatibel mit bestehenden Merkmalen ist.

Denn wann immer dies stabilisiert ist: _bitte_ fügen Sie TryFrom und TryInto zum Präludium hinzu.

@SergioBenitez Wir machen das schon seit einiger Zeit bei Nightly, und leider war es eine bahnbrechende Änderung für Kisten, die ihre eigene Eigenschaft TryFrom oder TryInto definieren (zur Verwendung, während die std Ones sind instabil), genug, dass wir es rückgängig gemacht haben.

Ich denke, die Lektion, die das std libs-Team hier lernen muss, ist, dass wir, wenn es eine Eigenschaft gibt, die wir dem Vorspiel hinzufügen möchten, dies tun sollten, sobald sie implementiert ist, und nicht auf die Stabilisierung warten sollten.

Für TryFrom und TryInto wäre eine Lösung jedoch, einen anderen Auftakt für die Ausgabe 2018 zu haben. Dies wurde zuvor informell diskutiert, aber ich habe es nicht abgelegt gefunden, also habe ich https://github.com/rust-lang/rust/issues/51418 geöffnet.

Eine generische Lösung für ein ähnliches Problem wurde zur Erweiterung implementiert
Methoden in https://github.com/rust-lang/rust/issues/48919 , wobei unstable
Erweiterungsmethoden geben Warnungen aus, wenn stabiler Code kollidiert.

Es scheint, als könnten Sie etwas Ähnliches mit neuen Begriffen tun, die dem hinzugefügt werden
Auftakt?

Am Donnerstag, 7. Juni 2018, 11:47 Uhr schrieb Simon Sapin [email protected] :

@SergioBenitez https://github.com/SergioBenitez Das haben wir bereits getan
Nachts für eine Weile, und leider war es eine bahnbrechende Abwechslung für Kisten
die ihr eigenes TryFrom- oder TryInto-Merkmal definieren (zur Verwendung, während die std
diejenigen sind instabil), genug, dass wir es rückgängig gemacht haben.

Ich denke, die Lektion, die das std libs-Team hier lernen muss, ist, wann
Es gibt eine Eigenschaft, die wir vielleicht dem Vorspiel hinzufügen möchten, wir sollten es tun
also sobald es implementiert ist und nicht auf die Stabilisierung warten.

Für TryFrom und TryInto wäre eine Lösung jedoch eine andere
Auftakt für die Ausgabe 2018. Dies wurde informell aber vorher diskutiert
Ich habe es nicht abgelegt gefunden, also habe ich #51418 geöffnet
https://github.com/rust-lang/rust/issues/51418 .


Sie erhalten dies, weil Sie diesen Thread abonniert haben.
Antworten Sie direkt auf diese E-Mail und zeigen Sie sie auf GitHub an
https://github.com/rust-lang/rust/issues/33417#issuecomment-395525170 ,
oder den Thread stumm schalten
https://github.com/notifications/unsubscribe-auth/AAC2lNbHvgBjWBk48-1UO311-LuUY5lPks5t6XUvgaJpZM4IXpys
.

In diesem Fall kollidieren nicht die Eigenschaften selbst (der Name des Elements im Vorspiel), sondern die Methoden, die durch diese Eigenschaft in den Geltungsbereich gebracht werden. Vielleicht gibt es noch eine ähnliche Optimierung, die wir vornehmen können, aber sie ist subtiler.

@SimonSapin , was ist der aktuelle Plan für die TryFrom/TryInto-Stabilisierung? Ich konnte nichts Konkretes finden, außer dass es am 29. April zurückgesetzt wurde.

@nayato Es wird bei der (erneuten) Stabilisierung des Never-Typs https://github.com/rust-lang/rust/issues/35121 blockiert, der selbst auf https://github.com/rust-lang/rust/ blockiert ist.

@SimonSapin Der Typ never wird nur hier verwendet. Könnten wir TryFrom ohne diese Implementierung stabilisieren und landen, wenn es nie stabilisiert ist? TryFrom ist eine ziemlich wichtige Schnittstelle, auch abgesehen von der Äquivalenz mit Try .

Leider nicht. Es wäre eine bahnbrechende Änderung, dieses pauschale Impl hinzuzufügen, nachdem die Eigenschaft stabilisiert wurde und die Crates.io-Bibliotheken die Möglichkeit hatten, beispielsweise TryFrom<Foo> for Bar zu implementieren, während sie auch From<Foo> for Bar als Impls haben überlappen würden.

Die bisherige Entscheidung war, dass es wichtig genug war, TryFrom auf diese Weise mit From $ „kompatibel“ zu machen, um die Stabilisierung zu blockieren.

Wahrscheinlich naiver und dummer Gedanke:

Was ist, wenn rustc in der Zwischenzeit einen ständigen Fehler-Flusen erhält, der für Error<_, !> ausgelöst wird, was jegliche Impls im Userland verhindert und später das Hinzufügen von impl s ermöglicht?

Oder ein instabiles impl<T: From<U>, U> TryFrom<U> for T mit einem beliebigen Fehlertyp. Können Trait-Impls instabil sein?

@jethrogb Nein

Ich bin mir nicht sicher, ob ich ganz verstehe, warum dies beim Typ never blockiert ist, aber die Tatsache, dass dies der Fall ist, deutet darauf hin, dass Rust ein wichtiger Mechanismus fehlt. Es scheint, dass es eine Möglichkeit geben sollte, die gewünschte Implementierung für zukünftige Definitionen zu reservieren. Vielleicht ist dieser Mechanismus so etwas wie das Instabilmachen, aber idealerweise wäre es ein Mechanismus, der auch von Nicht-Standard-Crates verwendet werden kann. Kennt jemand einen Lösungsvorschlag für dieses Problem?

Es ist wegen dieser Impl blockiert:

impl<T, U> TryFrom<U> for T where T: From<U> {
    type Error = !;

    fn try_from(value: U) -> Result<Self, Self::Error> {
        Ok(T::from(value))
    }
}

@rust-lang/libs Was halten Sie davon, zu enum Infallible {} zurückzukehren, anstatt nie zu tippen, um die Blockierung aufzuheben?

Ich persönlich habe nichts dagegen, enum Infalliable {} zu haben und es später in type Infalliable = ! zu ändern.

Wir haben dies beim gestrigen Libs-Triage-Meeting besprochen, konnten uns aber nicht erinnern, ob eine solche Änderung nach der Stabilisierung in Ordnung wäre oder ob wir diese Idee bereits wegen möglicher Brüche verworfen hatten oder was diese Brüche sein würden.

Woran wir uns erinnert haben, ist, dass wir nur einen solchen Austausch vornehmen müssen: Wenn zwei oder mehr stabile leere Aufzählungen (separate Typen) der Standardbibliothek später zum selben Typ werden, würde eine Kiste mit einigen impl s für beide kaputt gehen wenn sich die Impls überlappen (oder identisch sind). Zum Beispiel impl From<std::convert::Invallible> for MyError und impl From<std::string::ParseError> for MyError .

Übrigens haben wir std::string::ParseError , was, soweit ich das beurteilen kann, die einzige leere Aufzählung in der 1.30.0-Standardbibliothek ist. Wenn wir also sicher sein können, dass es bei diesem Plan keine anderen Probleme gibt, könnten wir:

  • Bewegen Sie string::ParseError nach convert::Infallible
  • Exportieren Sie es an seinem alten Speicherort mit pub use oder pub type erneut (macht das einen Unterschied?)
  • Verwenden Sie es in der TryFrom Decke impl
  • Ersetzen Sie später in derselben Version, in der der Typ never stabilisiert ist, sowohl string::ParseError als auch convert::Infallible durch type _ = ! und verwenden Sie ! direkt dort, wo sie verwendet wurden.
  • (Optional) Geben Sie später Verfallswarnungen für die alten Aliase aus

Nachdem ich meiner eigenen Kiste gerade und widerstrebend ein TryFrom -Platzhaltermerkmal hinzugefügt habe, und zugegebenermaßen ohne die Auswirkungen des RFC und der Stabilisierungsbemühungen vollständig zu verstehen, bin ich überrascht, dass eine pauschale TryFrom für From mit dem Fehlertyp Infallible / ! ist das, was das aufhält? Sind das nicht zweitrangige Ziele, nachdem man stabile std TryFrom und pauschale TryInto Eigenschaften etabliert hat? Ich meine, selbst wenn der Anstieg von From auf TryFrom fehlt (wobei ich den Zweck nicht ganz verstehe), wäre es nicht weniger Abwanderung, wenn nicht jede Kiste, die ihn benötigt, hinzugefügt wird seine eigenen TryFrom ?

Das offensichtliche Problem, wenn TryFrom ohne die pauschale Impl für From geliefert wird, ist, dass Crates TryFrom und From für die gleichen Typen implementieren könnten (möglicherweise genau deshalb, weil die Blanket Impl ist nicht vorhanden), und sie würden brechen, wenn Blanket Impl zu libstd hinzugefügt wird.

Obwohl, wenn ich darüber nachdenke, wäre es vielleicht keine bahnbrechende Veränderung mit Spezialisierung?

Hmm, bitte verzeihen Sie mir noch einmal, wenn ich das Offensichtliche übersehe . Es ist fast so, als ob diese 1,5-jährigen RFC/Tracking-Odysseen einen fortlaufenden FAQ-Abschnitt benötigen, um den logischen Ablauf zu verstehen. Was wäre, wenn die Anleitung wäre, From nur für unfehlbare Conversions und TryFrom nur für fehlbare Conversions zu implementieren? Ergibt das nicht ein ähnliches praktisches Endergebnis, während es weniger Hindernisse für den Versand in Standard- und Anpassungskisten bietet?

Ja, wie @glandium sagte, ist das Hinzufügen des pauschalen Impls, nachdem die Merkmale bereits stabil waren, eine bahnbrechende Änderung. Die Stabilisierung ist noch nicht fertig, und es ist nicht klar, ob sie diese Art von Schnittpunkt-Impls sowieso zulassen würde (anstatt nur "streng spezifischere" Impls).

Die Bereitstellung von Anleitungen (Dokumente?), Die besagen, dass Sie keine Programme schreiben sollen, die kaputt gehen, ist nicht gut genug, um Breaking Changes zu rechtfertigen. Ein Bruch würde auf jeden Fall trotzdem passieren.

Was ist notwendig, damit der in https://github.com/rust-lang/rust/issues/33417#issuecomment -423073898 skizzierte Plan in die Tat umgesetzt wird? Was kann getan werden, um zu helfen?

Es ist eine etwas undankbare Aufgabe, aber es würde helfen, wenn jemand dieses Tracking-Problem und https://github.com/rust-lang/rust/issues/35121 durchgehen könnte, um zu überprüfen, ob es ein Problem mit diesem Plan gibt, den wir Ich habe vorher darüber diskutiert und es seitdem vergessen, insbesondere ob das Ersetzen enum Infallible durch type Infallible = !; , nachdem die Aufzählung in einer früheren Version stabil war, eine bahnbrechende Änderung sein könnte.

Muss die Aufzählung Infallible zusammen mit dem Merkmal stabilisiert werden? Wenn es instabil gehalten wird, kann niemand es benennen, und es sollte später in Ordnung sein, es gegen ! auszutauschen?

@seanmonstar Nein, Sie könnten mit <u16 as TryFrom<u8>>::Error darauf verweisen und es wird als stabiler Name angesehen. Zeuge:

// src/lib.rs
#![feature(staged_api)]
#![stable(since = "1.0.0", feature = "a")]

#[stable(since = "1.0.0", feature = "a")]
pub trait T1 {
    #[stable(since = "1.0.0", feature = "a")]
    type A;
}

#[unstable(issue = "12345", feature = "b")]
pub struct E;

#[stable(since = "1.0.0", feature = "a")]
impl T1 for u8 {
    type A = E;
}
// src/bin/b.rs
extern crate a;

trait T3 {}

impl T3 for <u8 as a::T1>::A {}
impl T3 for a::E {}

fn main() {}

Das erste T3-Impl verursacht keinen Fehler. Nur das zweite T3-impl verursacht den Fehler E0658 „Use of Instable Library Feature“.

Das ist .... Wow, ich wollte nur gebissen werden >_<

Ich persönlich verwende den Trick, einen Typ in einem nicht exportierten Modul öffentlich zu machen, um nicht benennbare Typen zurückzugeben, und während jemand, der das tut, was Sie sagen, einen Bruch bedeuten würde, wenn er das tut, sind meine Gefühle "Schande über ihn" und ziehen es nicht in Betracht Anpassen des nicht benennbaren Typs als Breaking.

Es macht mir persönlich nichts aus sicherzustellen, dass jede dieser nicht-wirklich-nie Aufzählungen in libstd gleich ist, und sie dann in einen Typ-Alias ​​in zu ändern! wenn das stabil wird. Scheint mir vernünftig.

Was ist die Design-Wahl für eine fehlbare Konvertierung in einen Nicht- Copy -Typ, unabhängig von diesem ganzen Nie-Type-Aufwand? Es sollte darauf geachtet werden, dass die gegebene Eingabe nicht gelöscht wird, damit sie im Fehlerfall zurückgenommen werden kann.

Beispielsweise können wir bei String::from_utf8 sehen, dass der Fehlertyp die eigene Eingabe Vec<u8> enthält, um sie zurückgeben zu können :

// some invalid bytes, in a vector
let sparkle_heart = vec![0, 159, 146, 150];

match String::from_utf8(sparkle_heart) {
    Ok(string) => {
        // owned String binding in this scope
        let _: String = string;
    },
    Err(err) => {
        let vec: Vec<u8> = err.into_bytes(); // we got the owned vec back !
        assert_eq!(vec, vec![0, 159, 146, 150]);
    },
};

Wenn wir also eine Implementierung von String: TryFrom<Vec<u8>> erhalten würden, wäre zu erwarten, dass <String as TryFrom<Vec<u8>>>::Error FromUtf8Error ist, richtig?

Ja, das Zurückgeben des Eingabewerts im Fehlertyp ist eine gültige Option. Eine Referenz mit impl<'a> TryFrom<&'a Foo> for Bar zu nehmen ist eine andere.

In diesem speziellen Beispiel bin ich mir jedoch nicht sicher, ob ein TryFrom immpl angemessen ist. UTF-8 ist nur eine der möglichen Dekodierungen von Bytes in Unicode, und from_utf8 spiegelt dies in seinem Namen wider.

Es ist eine etwas undankbare Aufgabe, aber es würde helfen, wenn jemand dieses Tracking-Problem und #35121 durchgehen könnte, um zu überprüfen, ob es ein Problem mit diesem Plan gibt, den wir zuvor besprochen und seitdem vergessen haben, insbesondere ob enum Infallible ersetzt wird type Infallible = !; , nachdem die Aufzählung in einer früheren Version stabil war, könnte eine bahnbrechende Änderung sein.

In dieser Ausgabe oder #35121 wurden keine konkreten Probleme damit aufgezeigt. Es gab Bedenken hinsichtlich der Möglichkeit, dass ! in gewisser Weise etwas Besonderes sein könnten, was ungehemmte Typen nicht sind. Aber keine Bedenken in der PR und es ist aus den Code-Review-Kommentaren klar, dass das Enum stabil werden könnte (obwohl das nie passiert ist). Hier sind Links zu dem, was ich gefunden habe.

Originelles Konzept
Ein abstraktes Anliegen
Grünes Licht vom Lib-Team

Gefolgt von:

44174 29. September: Der Typ Unfehlbar wurde hinzugefügt

47630 14. März: stabilisieren Sie den Never-Typ !

49038 22. März: Konvertiert Infallible zum Never-Typ !

49305 27. März: TryFrom stabilisiert

49518 30. März: aus Präludium entfernt

50121 21. April: nicht stabilisiert

In Bezug auf die pauschalen Implikationen, wenn ! stabilisiert wird,

würde das funktionieren?

impl<T, U> TryFrom<U> for T
where U: Into<T> {
    type Err = !;

    fn try_from(u: U) -> Result<Self, !> { Ok(u.into()) }
}

impl<T, U> TryInto<U> for T
where U: TryFrom<T> {
    type Err = U::Err;

    fn try_into(self) -> Result<U, !> { U::try_from(self) }
}

Auf diese Weise erhalten alle unfehlbaren Konvertierungen (mit From und Into ) ein entsprechendes TryFrom und TryInto Impl, wobei Err ! ist TryInto impli für jeden TryFrom impl, so wie wir einen Into impli für jeden From impli erhalten.

Wenn Sie auf diese Weise ein Impl schreiben wollten, würden Sie From dann Into dann TryFrom dann TryInto versuchen, und eines davon würde für Ihr Szenario funktionieren , wenn Sie unfehlbare Konvertierungen benötigen, würden Sie From oder Into (basierend auf Kohärenzregeln) verwenden, und wenn Sie fehlbare Konvertierungen benötigen, würden Sie TryFrom oder TryInto verwenden (basierend auf Kohärenzregeln). Wenn Sie Einschränkungen benötigen, können Sie TryInto oder Into auswählen, je nachdem, ob Sie mit fehlbaren Konvertierungen umgehen können. TryInto wären alle Conversions und Into wären alle unfehlbaren Conversions.

Dann können wir gegen ein impl TryFrom<T, Err = !> oder impl TryInto<T, Err = !> mit einem Hinweis fusseln, um impl From<T> oder impl Into<T> zu verwenden.

Ah, ich habe sie nicht gesehen (zu viele andere Impls haben die Dokumente überladen). Könnten wir uns ändern

impl<T, U> TryFrom<U> for T where    T: From<U>,

zu

impl<T, U> TryFrom<U> for T where    U: Into<T>,

da dies strikt mehr Impls erlaubt und Leute nicht bestrafen würde, die aus Kohärenzgründen nur Into implizieren können, erhalten sie auf diese Weise auch das automatische Implizieren für TryFrom und die From impl würde aufgrund des pauschalen impl für Into transitiv angewendet.

Möchten Sie es ausprobieren, sehen, ob es kompiliert, und eine Pull-Anforderung senden?

Ja, ich möchte es versuchen.

Gibt es einen Fall, in dem From<U> for T nicht implementiert werden kann, aber Into<T> for U und TryFrom<U> for T können es sein?

Ja, zum Beispiel

use other_crate::OtherType;

struct MyType;

// this impl will fail to compile
impl From<MyType> for OtherType {
    fn from(my_type: MyType) -> OtherType {
        // impl details that don't matter
    }
}

// this impl will not fail to compile
impl Into<OtherType> for MyType {
    fn into(self) -> OtherType {
        // impl details that don't matter
    }
}

Dies liegt an verwaisten Regeln.
TryFrom und From sind in Bezug auf die Waisenregeln gleich, und ebenso sind TryInto und Into in Bezug auf die Waisenregeln gleich

@KrishnaSannasi Ihr Beispiel wird kompiliert, schauen Sie sich einfach dieses Playground-Beispiel an

Ich denke, die aktuellste Kohärenzregel ist in diesem RFC beschrieben und beide Implementierungen sollten erlaubt sein.

struct MyType<T>(T);

impl<T> From<MyType<T>> for (T,) {
    fn from(my_type: MyType<T>) -> Self {
        unimplemented!()
    }
}

impl<T> Into<(T,)> for MyType<T> {
    fn into(self) -> (T,) {
        unimplemented!()
    }
}

Spielplatz
Ah ja, aber sobald Sie generische Parameter hinzufügen, fällt es um. Wenn Sie versuchen, jedes Impl einzeln zu kompilieren, wird das mit From nicht kompiliert, während das Into kompiliert wird.


Ein weiterer Grund, warum ich diese Änderung möchte, ist, dass derzeit, wenn Sie ein Into -Impl schreiben, Sie dies nicht tun und nicht automatisch ein TryInto -Impl erhalten können. Ich sehe dies als schlechtes Design an, da es nicht mit From und TryFrom ist.

Ich frage eher, ob es jemals einen Fall gibt, in dem Sie möchten, dass TryFrom<U> for T automatisch von Into<T> for U abgeleitet wird, aber wo From<U> for T nicht implementiert werden kann. Es erscheint mir unsinnig.

Ich verstehe auf jeden Fall, dass ich eine pauschale Impl Into bis TryInto haben möchte, aber leider erlaubt das Typsystem derzeit keine solche pauschale Impl, weil sie mit den anderen From bis TryFrom in Konflikt steht From bis Into pauschale Implikationen (oder zumindest ist das mein Verständnis).

Ich bin mir ziemlich sicher, dass diese Anwendungsfälle genau das waren, was der verlinkte RFC @kjetilkjeka beheben sollte. Nachdem dies implementiert ist, sollte es nie einen Fall geben, in dem Sie Into implementieren können, aber nicht From .

@scottjmaddox Ich möchte nicht unbedingt, dass Into TryFrom impliziert, wie ich möchte, dass Into TryInto impliziert. Auch From und Into sind semantisch äquivalent, nur weil wir das in unserem Trait-System nicht ausdrücken können, heißt das noch lange nicht

TryFrom<U> for T soll automatisch von Into<T> for U abgeleitet werden, aber where From<U> for T kann nicht implementiert werden.

ist unsinnig. In einer perfekten Welt hätten wir diese Unterscheidung nicht und es gäbe nur eine Eigenschaft, die zwischen den Typen konvertiert werden könnte, aber aufgrund von Einschränkungen im System haben wir jetzt zwei. Dies wird sich aufgrund von Stabilitätsgarantien nicht ändern, aber wir können diese beiden Merkmale als dasselbe Konzept behandeln und von dort aus weitermachen.

@clarcharr Selbst wenn dies der Fall ist, gibt es immer noch keine Möglichkeit, Into TryInto und TryFrom TryInto $ implizieren zu lassen, da die beiden Decken impls würde in Konflikt geraten. Ich möchte, dass Into TryInto impliziert und dass TryFrom TryInto impliziert. Der Weg, den ich vorschlage, wird dies tun, wenn auch indirekt.


Ich habe einen Kommentar zu meiner Pull-Anfrage abgegeben, in dem ich meine Hauptgründe für diese Änderung hier dargelegt habe


Dies ist weniger wichtig als die oben genannten Gründe, aber ich mag auch, dass mit diesem Impl alle unfehlbaren Konvertierungen jetzt ein fehlbares Gegenstück haben, wobei der Typ Err der fehlbaren Version ! ist.

Nur um das klarzustellen, ich will diese vier Impls

  • From impliziert Into (für Stabilität)
  • TryFrom impliziert TryInto oder TryInto impliziert TryFrom (weil sie semantisch äquivalent sind)
  • From impliziert TryFrom
  • Into impliziert TryInto

Es ist mir egal, ob sie direkt oder indirekt gemacht werden.

Oder wir könnten TryInto zu einem Alias ​​machen:

trait TryInto<T> = where T: TryFrom<Self>;

Ich habe keine Ahnung, ob das wirklich funktionieren würde, aber es scheint einfach genug.

Wo würde die Methode into definiert werden?

@clarcharr Wie @SimonSapin sagte, wo würde die into-Methode definiert werden. Wir haben keine Eigenschaftsaliase. Das müsste ein weiterer RFC sein. Und wir müssten diesen auf diesem RFC blockieren, was unerwünscht ist.

@KrishnaSannasi Es ist derzeit unmöglich, alle vier von From => Into , From => TryFrom , TryFrom => TryInto und Into => TryInto zu haben, da alles, was From implementiert, dies tun würde zwei konkurrierende Impls für TryInto haben (eins von From => Into => TryInto und ein anderes von From => TryFrom => TryInto ).

Da From => Into und TryFrom => TryInto beide kritisch sind, muss Into => TryInto geopfert werden. Oder zumindest ist das mein Verständnis.

Ja, ich habe den Alias TryInto nicht richtig verstanden, also ignoriere mich einfach ><

Ich stimme jedoch dem zu, was @scottjmaddox sagt.

@scottjmaddox

Um ein mögliches Missverständnis auszuräumen, entferne ich From auto impls TryFrom und ersetze es durch Into auto impls TryFrom .

Es ist derzeit unmöglich, alle vier von From => Into, From => TryFrom, TryFrom => TryInto und Into => TryInto zu haben

Das ist einfach falsch, schauen Sie sich einfach die vorgeschlagene Implementierung an.

Dieses:
From -> Into -> TryFrom -> TryInto

From automatisch implementiert Into
Into automatisch implementiert TryFrom
TryFrom automatisch implementiert TryInto

Als Erweiterung erhalten wir aufgrund transitiver automatischer Impls
From impliziert TryFrom (weil From automatisch Into und Into automatisch TryFrom impliziert)
Into impliziert TryInto (weil Into automatisch TryFrom und TryFrom automatisch TryInto impliziert)
jeweils mit 1 Indirektionsebene (ich finde das in Ordnung)

Somit sind alle meine Voraussetzungen erfüllt.
Wir könnten alle haben

Impf | Ebenen der Indirektion
-----------------------|-------------------
From impliziert Into | Keine Umleitung
TryFrom impliziert TryInto | Keine Umleitung
From impliziert TryFrom | 1 Ebene der Indirektion
Into impliziert TryInto | 1 Ebene der Indirektion


From -> Into -> TryInto -> TryFrom
Würde auch funktionieren, aber ich möchte die Konsistenz beibehalten und die Originalversion (siehe oben) ist konsistenter als diese Version


Hinweis zur Terminologie: (wenn ich sie verwende)

-> bedeutet automatische Implementierung
auto impl bezieht sich auf das tatsächlich eingegebene impl.
impliziert bedeutet, wenn ich dieses impl habe, bekomme ich auch jenes impl


Beachten Sie auch, dass ich eine Pull-Anfrage gestellt habe und alle Tests bestanden haben, was bedeutet, dass es kein Loch in meiner Logik gibt, dies ist möglich. Wir müssen nur diskutieren, ob dieses Verhalten gewollt ist. Ich denke schon, weil es die Konsistenz bewahrt (was sehr wichtig ist).

Pull-Anfrage

@KrishnaSannasi Ahhh, jetzt verstehe ich deinen Punkt. Ja, das macht Sinn und ich sehe jetzt, wie Ihre vorgeschlagene Änderung das Problem löst und das gewünschte Verhalten liefert. Danke für die ausführliche Erklärung!

Edit : Okay, warte, aber ... Ich verstehe immer noch nicht, warum die aktuellen Blanko-Implementierungen nicht ausreichen. Es gibt vermutlich einen Fall, in dem Sie Into implementieren könnten, aber nicht From ? Und doch ist es möglich, TryFrom automatisch abzuleiten?

Bearbeiten 2 : Okay, ich bin gerade zurückgegangen und habe Ihre Erklärung darüber gelesen, wie die Waisenregel Implementierungen von From verhindern kann. Es macht jetzt alles Sinn. Ich unterstütze diese Änderung voll und ganz. Es ist immer noch ein bisschen frustrierend, dass die Waisenregel so viele unbeabsichtigte Folgen hat, aber wir werden das hier nicht beheben, also macht es Sinn, das Beste aus den Dingen zu machen.

Wäre jemand von @rust-lang/libs bereit, FCP damit zu starten, nachdem https://github.com/rust-lang/rust/issues/49593 behoben wurde und never_type ein Kandidat für die Stabilisierung ist Noch einmal?

Diese Funktion wurde bereits durch FCP zur Stabilisierung durchlaufen. ( Vorschlag , Fertigstellung . ) Ich denke, es ist nicht notwendig, weitere 10 Tage Kommentierungsfrist zu durchlaufen.

Nachdem eine Stabilisierungs-PR für den Typ never gelandet ist, können wir eine (neue) Stabilisierungs-PR für TryFrom / TryInto erstellen und dort rfcbot verwenden, um sicherzustellen, dass die Teammitglieder sie sehen.

Wäre es möglich, neben der Stabilisierung die vorhandenen leeren Enum-Typen wie FromStringError in Aliase in ! zu ändern?

@clarcharr Ja. Aufgrund der Kohärenz von Trait Impl können wir dies nur für einen Typ tun. Zum Glück haben wir nur std::string::ParseError . (Und genau aus diesem Grund haben wir es verwendet, anstatt einen neuen Typ für impl FromString for PathBuf in https://github.com/rust-lang/rust/pull/55148 hinzuzufügen.)

Dies hat jedoch nichts mit TryFrom / TryInto zu tun. Dies ist ein Thema für https://github.com/rust-lang/rust/issues/49691 / https://github.com/rust-lang/rust/issues/57012 , da es im selben Release-Zyklus passieren muss als Stabilisierung von ! .

@SimonSapin Wäre es möglich, meine Pull-Anforderung (#56796) zusammenzuführen, bevor sich diese stabilisiert?

Sicher, ich habe es der Problembeschreibung hinzugefügt, damit wir den Überblick nicht verlieren.

Danke!

Könnte man das in Stable integrieren? Es ist eine Abhängigkeit für argdata für CloudABI.

@mcandre Ja. Dieser wartet derzeit auf https://github.com/rust-lang/rust/issues/57012 , der kürzlich entsperrt wurde. Ich hoffe, dass TryFrom es in Rust 1.33 oder 1.34 schafft.

Ich habe es satt, darauf zu warten, und ich habe (endlich) Freizeit. Wenn es irgendeinen Code oder Dokumentationsmaterial gibt, das ich tun könnte, um dies voranzutreiben, helfe ich freiwillig.

56796 wurde zusammengeführt. Das können wir also abhaken.

@icefoxen Ich denke, im Moment ist das Beste, was Sie tun können, Feedback (und Änderungen) zu den Dokumenten dieser Eigenschaften zu geben. Im Moment finde ich sie im Vergleich zu den Eigenschaften From und Into etwas mangelhaft.

Arbeiten an der Dokumentation. Kleine Unebenheit: Ich möchte assert_eq!(some_value, std::num::TryFromIntError(())); . Da TryFromIntError jedoch keinen Konstruktor und keine öffentlichen Felder hat, kann ich keine Instanz davon erstellen. Irgendwelche Ratschläge zum weiteren Vorgehen? Im Moment mache ich nur assert!(some_value_result.is_err()); .

Tut mir leid, wenn dies der falsche Ort ist, aber es sieht so aus, als ob in den Dokumenten ein Fehler für TryFromIntError ist - es listet impl Debug for TryFromIntError auf, obwohl es eigentlich keinen Code dafür gibt. Der Compiler generiert derzeit einen Fehler, wenn er versucht, ein Ergebnis aus einer TryFrom-Verwendung zu entpacken.

Soll es eine fmt::Debug Implikation dafür geben?

@marco9999

#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct TryFromIntError(());

Es hat ein Attribut #[derive(Debug)] .

Hmm ja, es gibt ... funktioniert etwas nicht richtig?

error[E0599]: no method named `unwrap` found for type `std::result::Result<usize, <T as std::convert::TryInto<usize>>::Error>` in the current scope
  --> src\types\b8_memory_mapper.rs:67:51
   |
67 |         let address: usize = T::try_into(address).unwrap();
   |                                                   ^^^^^^
   |
   = note: the method `unwrap` exists but the following trait bounds were not satisfied:
           `<T as std::convert::TryInto<usize>>::Error : std::fmt::Debug`

@marco9999 Dir fehlt wahrscheinlich eine generische Einschränkung. TryFromIntError wird nur von einigen Typen verwendet, aber Ihr T könnte alles sein:

fn foo<T: TryInto<u8>>(x: T) -> u8
where
    <T as TryInto<u8>>::Error: Debug,
{
    x.try_into().unwrap()
}

Wie auch immer, das ist ein bisschen vom Thema abgekommen, sorry an alle. IRC könnte ein besserer Ort sein, um diese Fragen zu stellen.

Ich möchte assert_eq!(some_value, std::num::TryFromIntError(()));

@icefoxen Mit TryFromIntError ist kein nützlicher Wert verbunden, daher scheint eine solche Behauptung nicht viel Wert zu haben. Wenn Sie ein Result<_, TryFromIntError> haben und es ein Err ist, gibt es keinen anderen Wert, den es haben könnte.

assert!(some_value_result.is_err());

Dies erscheint mir vernünftig.

Danke @glaebhörl.

Da ein blockierender Fehler behoben wurde (https://github.com/rust-lang/rust/issues/49593), hatte ich gehofft, dass der Never-Typ bald stabilisiert werden könnte® https://github.com/rust-lang/ rust/issues/57012 und entsperren Sie diese. Allerdings ist ein neues Problem (https://github.com/rust-lang/rust/issues/57012#issuecomment-460740678) aufgetaucht, und wir haben auch keinen Konsens über ein weiteres (https://github.com /rust-lang/rust/issues/57012#issuecomment-449098855).

In einem Libs-Meeting letzte Woche brachte ich also die Idee erneut zur Sprache, die, glaube ich, zuerst von @scottmcm in https://github.com/rust-lang/rust/issues/33417#issuecomment -299124605 vorgeschlagen wurde, um TryFrom zu stabilisieren TryInto ohne den Typ never und haben stattdessen eine leere Aufzählung, die später zu einem Alias ​​für ! gemacht werden könnte.

Als wir das letzte Mal darüber gesprochen haben (https://github.com/rust-lang/rust/issues/33417#issuecomment-423069246), konnten wir uns nicht erinnern, warum wir das beim letzten Mal nicht getan hatten.

Letzte Woche hat uns @dtolnay an das Problem erinnert: Bevor ! zu einem vollständigen Typ wird, kann es bereits anstelle des Rückgabetyps einer Funktion verwendet werden, um anzuzeigen, dass es nie zurückkehrt. Dazu gehören Funktionszeigertypen. Unter der Annahme, dass https://github.com/rust-lang/rust/pull/58302 in diesem Zyklus landet, wird Code wie dieser in Rust 1.34.0 gültig sein:

use std::convert::Infallible;
trait MyTrait {}
impl MyTrait for fn() -> ! {}
impl MyTrait for fn() -> Infallible {}

Da fn() -> ! und fn() -> Infallible zwei unterschiedliche (Zeiger-)Typen sind, überschneiden sich die beiden Impls nicht. Aber wenn wir die leere Aufzählung durch einen Alias ​​vom Typ pub type Infallible = !; ersetzen (wenn ! zu einem vollständigen Typ wird), beginnen sich die beiden Impls zu überlappen und Code wie der obige wird kaputt gehen.

Das Ändern der Aufzählung in einen Alias ​​wäre also eine bahnbrechende Änderung. In der Standardbibliothek würden wir es grundsätzlich nicht zulassen, aber in diesem Fall hatten wir das Gefühl:

  • Man muss sich große Mühe geben, Code zu erstellen, der durch diese Änderung beschädigt wird, daher scheint es unwahrscheinlich, dass dies in der Praxis vorkommt
  • Wir werden Crater verwenden, um zusätzliches Signal zu erhalten, wenn die Zeit gekommen ist
  • Wenn wir den Bruch als signifikant genug einschätzen, ist es eine Inkonsistenz, mit der wir leben können, wenn wir sowohl den Typ never als auch eine leere Aufzählung mit derselben Rolle haben.

Basierend auf dieser Diskussion habe ich https://github.com/rust-lang/rust/pull/58302 eingereicht, das sich jetzt in der Frist für die endgültige Stellungnahme befindet.

58015 sollte jetzt zur Überprüfung/Zusammenführung bereit sein.

@kennytm Ist es nicht möglich, bereits in Stable auf ! zu verweisen? Betrachten Sie zum Beispiel Folgendes:

trait MyTrait {
    type Output;
}

impl<T> MyTrait for fn() -> T {
    type Output = T;
}

type Void = <fn() -> ! as MyTrait>::Output;

Danach bezieht sich $#$ Void auf den Typ ! .

Das sieht nach einem Fehler aus, was bedeutet, dass sich Stabilitätsgarantien nicht darauf erstrecken. Die Verwendung des Never-Typs ( ! ) als Typ in irgendeiner Funktion ist immer noch instabil, zumindest bis #57012 zusammengeführt wird.

Wie kann ich bei der Dokumentation helfen? :-)

Oh, ich dachte, https://github.com/rust-lang/rust/pull/58015 wäre gelandet, aber das ist es noch nicht … Lassen Sie uns dort darüber diskutieren.

Könnte das Merkmal TryFrom eine Methode haben, um zu prüfen, ob das Argument konvertiert werden kann, ohne es zu verbrauchen?

fn check(value: &T) -> bool

Eine Möglichkeit, mit nicht konsumierender unmöglicher Konvertierung zu arbeiten, könnte darin bestehen, den konsumierten nicht konvertierbaren Wert zusammen mit dem zugehörigen Fehler zurückzugeben.

Hoppla, dies hätte von https://github.com/rust-lang/rust/pull/58302 geschlossen werden sollen. Schließt jetzt.


@o01eg Der typische Weg, eine nicht verbrauchende Konvertierung durchzuführen, besteht darin, zB TryFrom<&'_ Foo> anstelle von TryFrom<Foo> zu implementieren.

Warten Sie ... das sollte eigentlich nicht bis zur Landung am stabilen Donnerstag schließen, oder?

Nein, wir schließen Tracking-Probleme, wenn der PR, der das Feature stabilisiert, auf Master landet.

Nein, normalerweise schließen wir das Tracking-Problem, wenn die Stabilisierung oder Entfernung im master -Zweig landet. Danach gibt es nichts mehr zu verfolgen. (Es sei denn, es tritt ein neu gemeldeter Fehler auf, aber das behandeln wir separat.)

Tracking-Probleme werden von der PR geschlossen, die sie stabilisiert. Je nach Veröffentlichungszyklus kann dies bis zu 12 Wochen vor der Veröffentlichung von Stable dauern.

Habe es. Danke für die Aufklärung, alle! :)

@gregdegruy aktualisiere deine Version von Rust auf 1.34 oder höher.

War diese Seite hilfreich?
0 / 5 - 0 Bewertungen