Rust: Tracking-Problem für "?" - Operator- und "try" -Blöcke (Funktionen von RFC 243, "question_mark" und "try_blocks")

Erstellt am 5. Feb. 2016  ·  340Kommentare  ·  Quelle: rust-lang/rust

Tracking-Problem für rust-lang / rfcs # 243 und rust-lang / rfcs # 1859.

Implementierungsprobleme:

  • [x] ? Operator, der ungefähr try! - # 31954 entspricht
  • [x] try { ... } Ausdruck - https://github.com/rust-lang/rust/issues/39849

    • [x] do catch { ... } Syntaxfrage lösen


    • [x] entscheiden, ob Catch-Blöcke den Ergebniswert "umbrechen" sollen (zuerst angesprochen in https://github.com/rust-lang/rust/issues/41414, jetzt neu in https://github.com/rust- abgerechnet) lang / rust / issue / 70941)

    • [] Beheben Sie Probleme mit der Typinferenz ( try { expr? }? erfordert derzeit irgendwo eine explizite Typanmerkung).

  • [x] Design des Merkmals Try festlegen (https://github.com/rust-lang/rfcs/pull/1859)

    • [x] Implementieren Sie das neue Merkmal Try (anstelle von Carrier ) und konvertieren Sie ? , um es zu verwenden (https://github.com/rust-lang/rust/pull) / 42275)

    • [x] Fügen Sie Impls für Option usw. und eine geeignete Testfamilie hinzu (https://github.com/rust-lang/rust/pull/42526).

    • [x] Fehlermeldungen verbessern, wie im RFC beschrieben (https://github.com/rust-lang/rust/issues/35946)

  • [x] reserviere try in der neuen Ausgabe
  • [x] Blockieren Sie try{}catch (oder andere folgende IDents), um den Designbereich für die Zukunft offen zu lassen, und zeigen Sie den Leuten, wie sie stattdessen mit match tun können, was sie wollen
A-error-handling B-RFC-approved B-RFC-implemented B-unstable C-tracking-issue F-try_blocks Libs-Tracked T-lang T-libs

Hilfreichster Kommentar

@ mark-im Ich glaube nicht, dass wir nach der Stabilisierung vernünftigerweise von einem zum anderen wechseln können. So schlecht Ok-Wrapping auch für mich ist, inkonsistentes Ok-Wrapping, das zu erraten versucht, ob Sie es wollen oder nicht, wäre noch schlimmer.

Alle 340 Kommentare

Der beiliegende RFC beschreibt ein Desugaring basierend auf Return / Break. Bekommen wir das auch oder wird es nur eine Sonderbehandlung für ? und catch im Compiler geben?

BEARBEITEN: Ich denke, Return / Break ist eine ausgezeichnete Idee, die von ? und catch ist. Wenn die Antwort also Nein lautet, werde ich wahrscheinlich einen separaten RFC dafür eröffnen.

Die gekennzeichnete Rückgabe / Unterbrechung dient nur zu Erklärungszwecken.

Am Freitag, 5. Februar 2016, um 15:56 Uhr, Jonathan Reem [email protected]
schrieb:

Der beiliegende RFC beschreibt ein Desugaring basierend auf dem gekennzeichneten Return / Break.
bekommen wir das auch oder wird es nur eine spezielle Behandlung geben? und
im Compiler fangen?

- -
Antworte direkt auf diese E-Mail oder sieh sie dir auf GitHub an
https://github.com/rust-lang/rust/issues/31436#issuecomment -180551605.

Eine weitere ungelöste Frage, die wir vor der Stabilisierung lösen müssen, ist, welcher Vertrag, dem impl s von Into folgen müssen, lauten sollte - oder ob Into überhaupt das richtige Merkmal ist für die Fehlerübertragung hier. (Vielleicht sollte dies ein weiterer Checklistenpunkt sein?)

@reem

Ich denke, Return / Break ist eine ausgezeichnete Idee ... Ich werde wahrscheinlich einen separaten RFC dafür eröffnen.

Bitte!

Zum Thema Carrier ist hier ein wesentliches Beispiel für ein solches Merkmal, das ich zu Beginn des RFC-Prozesses zurückgeschrieben habe.
https://gist.github.com/thepowersgang/f0de63db1746114266d3

Wie wird dies beim Parsen behandelt?

struct catch {
    a: u8
}

fn main() {

    let b = 10;
    catch { a: b } // struct literal or catch expression with type ascription inside?

}

@petrochenkov Nun, die Definition konnte das Parsen nicht beeinflussen, aber ich denke, wir haben immer noch eine Lookahead-Regel, die auf dem zweiten Token nach { , : in diesem Fall basiert, also sollte es immer noch so sein als Strukturliteral analysiert.

Ebenfalls

let c1 = catch { a: 10 };
let c2 = catch { ..c1 }; // <--

struct catch {}
let c3 = catch {}; // <--

+ https://github.com/rust-lang/rfcs/issues/306 wenn (wann!) implementiert.
Es scheint, dass es außer Strukturliteralen keine Konflikte gibt.

In Anbetracht der obigen Beispiele bin ich für die einfachste Lösung (wie üblich) - behandeln Sie catch { in Ausdruckspositionen immer als Beginn eines catch -Blocks. Niemand nennt ihre Strukturen sowieso catch .

Es wäre einfacher, wenn ein Schlüsselwort anstelle von catch .

@bluss ja, ich gebe zu, keiner von ihnen ist großartig ... override scheint der einzige zu sein, der nahe ist. Oder wir könnten do , heh. Oder eine Kombination, obwohl ich nicht sofort großartige sehe. do catch ?

do ist die einzige, die IMO nahe zu sein scheint. Eine Keyword-Suppe mit do als Präfix ist etwas unregelmäßig und keinem anderen Teil der Sprache ähnlich? Ist while let eine Keyword-Suppe? Das fühlt sich jetzt gut an, wenn du daran gewöhnt bist.

Port try! , um ?

Kann ? nicht portiert werden, um stattdessen try! verwenden? Dies würde den Anwendungsfall ermöglichen, in dem Sie einen Result Rückgabepfad erhalten möchten, z. B. beim Debuggen. Mit try! dies ziemlich einfach. Sie überschreiben einfach das Makro am Anfang der Datei (oder in lib / main.rs):

macro_rules! try {
    ($expr:expr) => (match $expr {
        Result::Ok(val) => val,
        Result::Err(err) => {
            panic!("Error occured: {:?}", err)
        }
    })
}

Sie erhalten eine Panikstapel-Ablaufverfolgung ab dem ersten Auftreten von try! im Rückgabepfad Result . Wenn Sie try!(Err(sth)) tun, wenn Sie einen Fehler anstelle von return Err(sth) entdecken, erhalten Sie sogar den vollständigen Stack-Trace.

Aber wenn man ausländische Bibliotheken debuggt, die von Leuten geschrieben wurden, die diesen Trick nicht implementiert haben, verlässt man sich auf die Verwendung von try! irgendwo höher in der Kette. Und jetzt, wenn die Bibliothek den Operator ? mit fest codiertem Verhalten verwendet, ist es fast unmöglich, einen Stacktrace zu erhalten.

Es wäre cool, wenn das Überschreiben von try! auch den Operator ? beeinflussen würde.

Später, wenn das Makrosystem mehr Funktionen erhält, können Sie sogar in Panik geraten! nur für bestimmte Typen.

Wenn für diesen Vorschlag ein RFC erforderlich ist, lassen Sie es mich bitte wissen.

Im Idealfall könnte ? einfach erweitert werden, um Stack-Trace-Unterstützung direkt bereitzustellen, anstatt sich auf die Fähigkeit zu verlassen, try! zu überschreiben. Dann würde es überall funktionieren.

Stapelspuren sind nur ein Beispiel (obwohl es mir sehr nützlich erscheint).
Wenn das Carrier-Merkmal funktioniert, kann dies möglicherweise solche Erweiterungen abdecken?

Am Sonntag, den 7. Februar 2016 um 16:14 Uhr, Russell Johnston [email protected]
schrieb:

Im Idealfall ? könnte einfach erweitert werden, um Stack-Trace-Unterstützung direkt bereitzustellen,
anstatt sich auf die Fähigkeit zu verlassen, try! außer Kraft zu setzen. Dann würde es funktionieren
überall.

- -
Antworte direkt auf diese E-Mail oder sieh sie dir auf GitHub an
https://github.com/rust-lang/rust/issues/31436#issuecomment -181118499.

Ohne spekulieren zu wollen, denke ich, dass es funktionieren könnte, wenn auch mit einigen Problemen.

Stellen Sie sich den üblichen Fall vor, in dem Code einen Result<V,E> zurückgibt. Jetzt müssten wir zulassen, dass mehrere Implementierungen des Merkmals Carrier nebeneinander existieren. Um nicht auf E0119 zu ? wird, muss der Benutzer den gewünschten importieren Implementierung.

Dies würde erfordern, dass jeder, auch diejenigen, die nicht debuggen möchten, ihre gewünschte Trait-Implementierung importieren, wenn ? wird. Es würde keine Option für einen vordefinierten Standard geben.

Möglicherweise kann E0117 auch ein Problem sein, wenn Sie benutzerdefinierte Carrier -Implementierungen für Result<V,E> möchten, bei denen alle Typen außerhalb der Grenzen liegen. Daher sollte libstd bereits eine Reihe von Implementierungen der Carrier bereitstellen panic! ing Implementierung, vielleicht mehr).

Die Möglichkeit, über ein Makro zu überschreiben, würde eine größere Flexibilität bieten, ohne den ursprünglichen Implementierer zusätzlich zu belasten (sie müssen ihre gewünschte Implementierung nicht importieren). Ich sehe aber auch, dass Rost noch nie einen makrobasierten Operator hatte und dass die Implementierung von ? über ein Makro nicht möglich ist, wenn catch { ... } funktionieren soll, zumindest nicht ohne zusätzliche Sprachelemente ( return_to_catch , throw , gekennzeichnet mit break mit param (wie in RFC 243 verwendet).

Ich bin mit jedem Setup einverstanden, das es einem ermöglicht, Result Stacktraces mit einem Err Rückgabepfad zu erhalten, während nur eine sehr kleine Menge Code geändert werden muss, vorzugsweise oben in der Datei. Die Lösung sollte auch unabhängig davon funktionieren, wie und wo der Typ Err implementiert ist.

Nur um sich auf das Bikeshedding einzulassen: catch in { ... } fließt ziemlich gut.

catch! { ... } ist eine weitere Backcompat-Option.

Auch nicht, dass ich erwarte, dass dies irgendetwas ändert, sondern ein Hinweis, dass dies mehrarmige Makros zerstören wird, die $i:ident ? akzeptieren, auf die gleiche Weise, wie die Typzuweisung $i:ident : $t:ty gebrochen hat.

Übertreiben Sie die Abwärtskompatibilität nicht, sondern behandeln Sie catch als Schlüsselwort, gefolgt von { (möglicherweise nur in Ausdrucksposition, aber ich bin mir nicht sicher, ob sich dies in Bezug auf die Kompatibilität stark ändert).

Ich kann mir auch einige mögliche Probleme vorstellen, bei denen es nicht um Strukturliterale geht (z. B. let catch = true; if catch {} ). Ich bevorzuge jedoch eine geringfügige Änderung gegenüber einer hässlicheren Syntax.

Hatten wir sowieso keine zum Hinzufügen neuer Keywords? Wir könnten eine Art from __future__ Opt-In für neue Syntax anbieten; oder geben Sie eine Versionsnummer in der Rostsprache in der Befehlszeile / in Cargo.toml an.
Ich bezweifle sehr, dass wir langfristig nur mit den bereits reservierten Schlüsselwörtern arbeiten können. Wir möchten nicht, dass unsere Keywords je nach Kontext jeweils drei verschiedene Bedeutungen haben.

Genau. Dies ist nicht einmal der erste RFC, bei dem dies aufgetreten ist (https://github.com/rust-lang/rfcs/pull/1444 ist ein weiteres Beispiel). Ich gehe davon aus, dass es nicht das letzte sein wird. (Auch default von https://github.com/rust-lang/rfcs/pull/1210, obwohl es kein RFC ist, für den ich bin.) Ich denke, wir müssen einen Weg finden, dies hinzuzufügen Ehrliche Schlüsselwörter, anstatt herauszufinden, wie man die Grammatik für jeden neuen Fall ad-hoc hackt.

War nicht das ganze Argument, nicht mehrere Schlüsselwörter vor 1.0 zu reservieren, dass wir definitiv eine Möglichkeit einführen würden, der Sprache neue Schlüsselwörter abwärtskompatibel hinzuzufügen (möglicherweise durch explizite Anmeldung), also gab es keinen Grund? Scheint, als wäre jetzt eine gute Zeit.

@japaric Möchten Sie Ihre alte PR wiederbeleben und übernehmen?

@aturon Meine Implementierung hat einfach foo? auf die gleiche Weise wie try!(foo) desugiert. Es funktionierte auch nur bei Methoden- und Funktionsaufrufen, dh foo.bar()? und baz()? funktionieren, aber quux? und (quux)? nicht. Wäre das für eine erste Implementierung in Ordnung?

@japaric Was war der Grund, es auf Methoden und Funktionsaufrufe zu beschränken? Wäre das Parsen als allgemeiner Postfix-Operator nicht einfacher?

Was war der Grund, es auf Methoden und Funktionsaufrufe zu beschränken?

Der einfachste Weg (für mich), die ? -Erweiterung zu testen

Wäre das Parsen als allgemeiner Postfix-Operator nicht einfacher?

wahrscheinlich

@japaric Es wäre wahrscheinlich gut, es auf einen vollständigen Postfix-Operator zu verallgemeinern (wie @eddyb vorschlägt), aber es ist in Ordnung, ? mit dem einfachen Desugaring zu landen und später catch hinzuzufügen.

@aturon Okay, ich werde bis nächste Woche in die Postfix-Version schauen, wenn mich niemand schlägt :-).

meine PR in # 31954 neu basiert / aktualisiert :-)

Was ist mit der Unterstützung für die Bereitstellung von Stack-Traces? Ist das geplant?

Ich hasse es, der + 1-Typ zu sein, aber Stapelspuren haben in der Vergangenheit gute Teile meiner Zeit gerettet. Vielleicht bei Debug-Builds, und wenn Sie den Fehlerpfad treffen, die? Betreiber könnte die Datei / Zeile an ein Vec in Ergebnis anhängen? Vielleicht könnte der Vec auch nur debuggen?

Und das könnte entweder aufgedeckt oder in einen Teil der Fehlerbeschreibung umgewandelt werden ...

Ich gerate immer wieder in die Situation, in der ich try! / ? in Iteratoren verwenden möchte, die Option<Result<T, E>> . Leider funktioniert das momentan nicht wirklich. Ich frage mich, ob das Carrier-Merkmal überladen werden könnte, um dies zu unterstützen, oder würde das stattdessen zu einem allgemeineren From ?

@hexsel Ich wünschte wirklich, der Typ Result<> würde eine vec von Anweisungszeigern im Debug enthalten und? würde daran anhängen. Auf diese Weise können DWARF-Informationen verwendet werden, um eine lesbare Stapelverfolgung zu erstellen.

@mitsuhiko Aber wie könnten Sie Result erstellen und mit Mustern übereinstimmen? Es ist nur ein enum atm.

Was die Option -Verpackung betrifft, glaube ich, dass Sie Some(catch {...}) .

Momentan ist es meine Gewohnheit, try!(Err(bla)) anstelle von return Err() , damit ich das try-Makro später mit einem in Panik geratenen Makro überschreiben kann, um eine Rückverfolgung zu erhalten. Es funktioniert gut für mich, aber der Code, mit dem ich mich beschäftige, ist sehr niedrig und verursacht hauptsächlich die Fehler. Ich muss immer noch ? vermeiden, wenn ich externen Code verwende, der Result zurückgibt.

@eddyb Es würde Sprachunterstützung benötigen, um versteckte Werte zu enthalten, die Sie auf andere Weise manipulieren müssen. Ich habe mich gefragt, ob es auf andere Weise möglich ist, aber ich kann nicht sehen, wie. Der einzige andere Weg wäre eine standardisierte Fehlerbox gewesen, die zusätzliche Daten enthalten kann, aber es ist nicht erforderlich, Fehler in der Box zu haben, und die meisten Leute tun dies nicht.

@mitsuhiko Ich kann mir eine neue (Standard-) Methode für das Merkmal Error und TLS vorstellen.
Letzteres wird manchmal von Desinfektionsmitteln verwendet.

@eddyb , das jedoch nur funktioniert, wenn das Ergebnis identifiziert werden kann und erforderlich ist, dass es

@mitsuhiko Die TLS? Nicht wirklich, Sie müssen nur in der Lage sein, den Fehler nach Wert zu vergleichen.

Oder auch nur nach Typ (mit Verknüpfung von From Ein- und Ausgängen), wie viele gleichzeitige Fehler, von denen Sie Stacktraces möchten, müssen jemals gleichzeitig weitergegeben werden?

Ich bin dagegen, Result -spezifische Compiler-Hacks hinzuzufügen, wenn einfachere Lösungen funktionieren.

@eddyb der Fehler geht den Stapel nach oben. Was Sie wollen, ist die EIP auf jeder Stapelebene, nicht nur dort, wo sie ihren Ursprung hat. Auch Fehler sind a) derzeit nicht vergleichbar und b) nur weil sie gleich sind, heißt das nicht, dass sie der gleiche Fehler sind.

Von wie vielen gleichzeitigen Fehlern, von denen Sie Stacktraces möchten, muss jemals gleichzeitig weitergegeben werden

Jeder Fehler wurde abgefangen und als anderer Fehler wiedergegeben.

Ich bin persönlich dagegen, ergebnisspezifische Compiler-Hacks hinzuzufügen, wenn einfachere Lösungen funktionieren.

Ich sehe nicht, wie eine einfachere Lösung funktioniert, aber vielleicht fehlt mir dort etwas.

Sie können den Anweisungszeiger bei jedem ? speichern und mit dem Fehlertyp korrelieren.
"Jeder Fehler wurde abgefangen und als anderer Fehler wiedergegeben." Aber wie würden Sie diese Informationen aufbewahren, wenn sie in Result versteckt wären?

Aber wie würden Sie diese Informationen erhalten, wenn sie im Ergebnis versteckt wären?

Sie müssen diese Informationen nicht im Ergebnis speichern. Was Sie jedoch speichern müssen, ist eine eindeutige ID für den Ursprung des Fehlers, damit Sie ihn korrelieren können. Und da das Fehlermerkmal nur ein Merkmal ist und keinen Speicher hat, könnte es stattdessen im Ergebnis gespeichert werden. Der Befehlszeiger vec selbst müsste keinesfalls im Ergebnis gespeichert werden, das an TLS gehen könnte.

Eine Möglichkeit wäre, dass Sie eine Methode failure_id(&self) für das Ergebnis aufrufen und eine i64 / uuid oder etwas zurückgeben, das den Ursprung des Fehlers identifiziert.

Dies würde Sprachunterstützung erfordern, egal was passiert, denn was Sie brauchen, ist, dass der Compiler, wenn das Ergebnis den Stapel nach oben durchläuft, eine Anweisung einfügt, um den Stapelrahmen aufzuzeichnen, durch den er fällt. Die Rückgabe eines Ergebnisses würde also in Debug-Builds anders aussehen.

"Der Compiler fügt eine Anweisung ein, um den Stapelrahmen aufzuzeichnen, durch den er fällt" - aber ? ist explizit, dies ist keine Ausnahme, oder möchten Sie nicht nur die ? aufzeichnen, die er durchlaufen hat?

Wie würde diese ID überhaupt erhalten bleiben, wenn Sie den Fehler manuell entpacken und dann wieder in ein Err einfügen?

"Und weil das Fehlermerkmal nur ein Merkmal ist und keinen Speicher hat, könnte es stattdessen im Ergebnis gespeichert werden."
Hierfür gibt es eine Variante: Die Implementierung des Merkmals Error im Compiler als Sonderfall verwendet werden, um dem Typ ein zusätzliches ganzzahliges Feld hinzuzufügen. Durch das Erstellen des Typs wird eine zu generierende ID ausgelöst und durch Kopieren / Löschen Inkrementieren / Dekrementieren Sie den Refcount effektiv (und löschen Sie ihn schließlich aus dem TLS "In-Flight-Error-Set", wenn Result::unwrap nicht verwendet wird).

Dies würde jedoch im Widerspruch zum Merkmal Copy . Ich meine, genau wie Ihr Plan, wenn Sie Result ein spezielles Verhalten hinzufügen, das nicht durch ? oder andere spezifische Benutzeraktionen ausgelöst wird, können die Copy Invarianten ungültig werden.

BEARBEITEN : An dieser Stelle können Sie auch ein Rc<ErrorTrace> einbetten.
EDIT2 : Was soll ich überhaupt sagen, Sie können die zugehörige Fehlerverfolgung auf catch löschen.
EDIT3 : Eigentlich sehen Sie unten eine bessere Erklärung.

"Der Compiler fügt eine Anweisung ein, um den Stapelrahmen aufzuzeichnen, durch den er fällt" - aber? ist explizit, dies ist nichts wie Ausnahmen, oder möchten Sie nicht nur die aufnehmen? es ging durch?

Das funktioniert nicht, weil es zu viele Frames gibt, durch die Sie fallen können und die nicht ? . Geschweige denn, dass nicht jeder Fehler mit nur ? .

Wie würde diese ID überhaupt erhalten bleiben, wenn Sie den Fehler manuell entpacken und dann wieder in einen Err einfügen?

Deshalb müsste es Compiler-Unterstützung sein. Der Compiler müsste lokale Variablen verfolgen, die Ergebnisse sind, und es ist am besten, die Ergebnis-ID für erneute Wraps weiterzugeben. Wenn dies zu magisch ist, kann es auf eine Teilmenge von Operationen beschränkt sein.

Das funktioniert nicht, weil es zu viele Frames gibt, durch die Sie fallen können und die nicht ? . Geschweige denn, dass nicht jeder Fehler mit nur ? .

Okay, ich könnte sehen, dass die Rückgabe von Result direkt in komplexen Funktionen mit mehreren Rückgabepfaden speziell behandelt wird (von denen einige frühe Rückgaben von ? wären).

Wenn dies zu magisch ist, kann es auf eine Teilmenge von Operationen beschränkt sein.

Oder ganz explizit gemacht. Haben Sie Beispiele für Nicht- ? Re-Wrapping, die vom Compiler auf magische Weise verfolgt werden müssten?

@eddyb Der häufigste Fall der manuellen Behandlung von Fehlern ist ein IoError, bei dem Sie eine Teilmenge behandeln möchten:

loop {
  match establish_connection() {
    Ok(conn) => { ... },
    Err(err) => {
      if err.kind() == io::ErrorKind::ConnectionRefused {
        continue;
      } else {
        return Err(err);
      }
    }
  }
}

@mitsuhiko Dann würde es definitiv funktionieren, die ID in io::Error belassen .

Um es zusammenzufassen, eine Vec<Option<Trace>> "sparse integer map" in TLS, die mit struct ErrorId(usize) verschlüsselt ist und auf die 3 lang-Elemente zugreifen:

  • fn trace_new(Location) -> ErrorId , wenn ein Fehlerwert erstellt wird, der nicht const ist
  • fn trace_return(ErrorId, Location) , kurz vor der Rückkehr von einer Funktion, die als -> Result<...> deklariert wurde (dh keine generische Funktion bei der Rückgabe, dass diese _ mit einem Result -Typ dort verwendet werden soll)
  • fn trace_destroy(ErrorId) , wenn ein Fehlerwert gelöscht wird

Wenn die Transformation in MIR durchgeführt wird, kann Location aus den Span des Befehls berechnet werden, wodurch entweder ein Fehlerwert erstellt oder in Lvalue::Return , was viel zuverlässiger ist als ein Befehlszeiger IMO (kein einfacher Weg, dies in LLVM zu bekommen, müssten Sie für jede bestimmte Plattform Inline asm ausgeben).

@eddyb

Wird das nicht zu einem Aufblähen in binärer Größe führen?

@ arielb1 Sie würden es im Debug-Modus nur tun, wenn die Debuginfo die Binärdatei ohnehin aufbläht - Sie könnten die Debuginfo auch geschickt wiederverwenden.

@eddyb was ist ein Ort in diesem Fall? Ich bin mir nicht sicher, was beim Lesen der IP schwierig ist. Sicher, Sie benötigen benutzerdefinierte JS für jedes Ziel, aber das ist nicht wirklich schwer.

@mitsuhiko Es könnten die gleichen Informationen sein, die wir für Überlaufprüfungen und andere vom Compiler core::panicking::panic .

Warum so viele Stapelinformationen in ein Error / Result packen, während der Stapel abgewickelt wird? Warum nicht einfach den Code ausführen, während der Stapel noch vorhanden ist? Was ist beispielsweise, wenn Sie an Variablen auf dem Pfad des Stapels interessiert sind? Ermöglichen Sie den Benutzern einfach, benutzerdefinierten Code auszuführen, wenn ein Err aufgerufen wird, z. B. um einen Debugger ihrer Wahl aufzurufen. Dies ist, was try! bereits bietet, da es sich um ein (überschreibbares) Makro handelt. Der einfachste Weg ist, in Panik zu geraten, wenn ein Err -Fall gedruckt wird, vorausgesetzt, man hat das Programm mit den richtigen Parametern gestartet.

Mit try können Sie in einem Err -Fall alles tun, was Sie wollen, und Sie können die Makrokiste weit oder sehr umfangreich überschreiben, um leistungskritischen Code nicht zu berühren, z. B. wenn der Fehler schwer zu reproduzieren ist und Sie müssen einen Großteil des perf-kritischen Codes ausführen.

Niemand müsste einen Bruchteil der Informationen in einem künstlichen Stapel aufbewahren, den man aufbaut, weil die reale zerstört wird. Alle Methoden zum Überschreiben von Makros könnten verbessert werden mit:

  • Da ? auf ähnliche Weise überschreibbar ist, ist es am einfachsten, ? als Zucker für try! - insbesondere, um nicht entstehende Fehler an der Kistengrenze als zu erkennen in meinem Kommentar oben skizziert.
  • mit einem leistungsfähigeren Makrosystem, wie dem Abgleichen des Typs, um noch mehr Flexibilität zu ermöglichen. Ja, man könnte darüber nachdenken, dies in das traditionelle Merkmalssystem zu integrieren, aber Rost erlaubt kein Überschreiben der Implementierung von Merkmalen, so dass dies etwas kompliziert sein wird

@ est31 Der Stapel ist nicht automatisch "abgewickelt" wie in einer Panik, dies ist syntaktischer Zucker für frühe Renditen.
Und ja, Sie könnten den Compiler dazu bringen, Aufrufe einiger leerer Funktionen mit festen Namen einzufügen, auf die Sie einen Haltepunkt setzen können. Dies ist ziemlich einfach (und verfügt auch über Informationen, mit denen Sie dies tun können, z. B. call dump(data) - wobei dump und data sind Argumente, die Debugger sehen können.

@eddyb Ich denke, leere Funktionen würden keinen Anwendungsfall zulassen, z. B. einige "kanarische" Debug-Instanzen in einer großen Bereitstellung zu behalten, um zu sehen, ob Fehler in Protokollen auftreten, und dann zurück zu gehen und Dinge zu beheben. Ich verstehe, dass es vorzuziehen ist, proaktiv als reaktiv zu sein, aber nicht alles ist leicht vorherzusagen

@hexsel Ja, deshalb bevorzuge ich die TLS-basierte Methode, bei der Result::unwrap oder eine neue ignore -Methode (oder sogar immer, wenn der Fehler gelöscht wird?) den Trace an stderr ausgibt.

@eddyb Wenn Sie den Anweisungszeiger oder etwas, das vom Wert abgeleitet ist, zu einem Stapel wie einer Datenstruktur in TLS hinzufügen, ? oder try! und Sie am Ende abfangen, ist das Endergebnis dasselbe. Es ist großartig, dass Rost die Fehlerausbreitung nicht automatisch macht. Mir hat sehr gut gefallen, dass Java alle Ausnahmen nach dem Schlüsselwort throws aufgelistet hat. Rost ist eine gute Verbesserung.

@hexsel Ein übergeordneter try! ) würde dies ermöglichen - Sie können jeden gewünschten Code ausführen und sich bei jedem Protokollierungssystem anmelden. Sie würden eine Erkennung für "Dupes" benötigen, wenn mehrere try den gleichen Fehler abfangen, der sich auf dem Stapel ausbreitet.

@ est31 Das Überschreiben von try! funktioniert nur in Ihrem eigenen Code (es ist buchstäblich Makroimport-Shadowing), es müsste etwas anderes sein, wie unsere "schwachen Lang-Elemente".

@ est31 Das ist nicht wirklich richtig (über das Abwickeln), die Spuren und der reale Stapel haben nicht unbedingt eine Beziehung, da das Verschieben von Result s nicht in der ursprünglichen Rückverfolgung nach oben gehen muss, sondern gehen kann auch seitwärts.
Wenn die Binärdatei optimiert ist, ist der größte Teil der Rückverfolgung weg, und wenn Sie keine Debug-Informationen haben, ist Location streng überlegen. Sie könnten sogar ein verschleiertes Plugin ausführen, das alle Quellinformationen durch zufällige Hashes ersetzt, die von den Entwicklern eines Closed-Source-Produkts abgeglichen werden können.

Debugger sind nützlich (und abgesehen davon mag ich die sauberere Backtrace-Ausgabe von lldb ), aber sie sind kein Allheilmittel, und wir geben bereits einige Informationen zu Panik aus, damit Sie Hinweise darauf erhalten Was ist los.

Darüber - Ich hatte einige Gedanken über einen Trick auf Typsystemebene, bei dem {Option,Result}::unwrap ein zusätzliches Typargument haben würde, wobei standardmäßig ein Typ verwendet wird, der von der Position abhängt, von der aus die Funktion aufgerufen wurde, sodass die Panik von diesen einen Weg hat nützlichere Standortinformationen.

Mit dem Fortschritt bei der Wertparametrisierung ist dies möglicherweise immer noch eine Option, aber definitiv nicht die einzige, und ich möchte Result Spuren nicht sofort verwerfen, sondern ich versuche, ein Modell zu finden, das _implementierbar_ ist.

Das Überschreiben von try! ist überhaupt keine Lösung, da es in Ihrer eigenen Kiste enthalten ist. Das ist als Debugging-Erfahrung nicht akzeptabel. Ich habe bereits viel davon versucht, um mit dem aktuellen try! . Besonders wenn Sie viele Kisten haben und Fehler auf dem Weg durch den Stapel mehrmals umgewandelt werden, ist es fast unmöglich herauszufinden, woher der Fehler stammt und warum. Wenn ein solches Pflaster die Lösung ist, müssen wir die Fehlerbehandlung im Allgemeinen für große Rust-Projekte erneut prüfen.

@eddyb Ist Ihr Vorschlag also, dass wir den Dateinamen, den Funktionsnamen, die Zeilennummer und die Spaltennummer buchstäblich in diesen Vektor einbetten? Dies erscheint äußerst verschwenderisch, insbesondere weil diese Informationen in DWARF bereits in einem viel besser verarbeitbaren Format enthalten sind. Mit DWARF können wir den gleichen Prozess auch relativ billig in der Produktion verwenden, während diese Art von Standortinformationen so verschwenderisch zu sein scheint, dass niemand jemals eine Release-Binärdatei damit ausführen würde.

Wie wäre es wesentlich verschwenderischer als DWARF-Informationen? Dateinamen würden dedupliziert, und auf x64 hat das Ganze die Größe von 3 Zeigern.

@mitsuhiko also im Grunde stimmen Sie der allgemeinen Richtung zu, aber nicht den technischen Besonderheiten?

Wie einfach ist es, DWARF-Informationen einer allgemeinen Rust-API zur Verfügung zu stellen?

@eddyb, da DWARF-Informationen nicht in der Release-Binärdatei enthalten sind, sondern separate Dateien. So kann ich die Debug-Dateien auf Symbolservern haben und eine gestrippte Binärdatei an Benutzer senden.

@mitsuhiko Oh, das ist ein ganz anderer Ansatz als ich angenommen habe. Rust unterstützt diesen Geldautomaten derzeit nicht, AFAIK, aber ich stimme ihm zu.

Denken Sie, dass Anweisungszeiger im Vergleich zu zufälligen Bezeichnern, die von Ihrem Build-System zum Debuggen von Release-Binärdateien generiert wurden, tatsächlich so nützlich sind?

Ich habe die Erfahrung gemacht, dass es bei Inlining-Debuggern schwierig ist, einen Großteil des Stack-Trace wiederherzustellen, mit Ausnahme der expliziten Selbst- / gegenseitigen Rekursion und sehr großer Funktionen.

Ja, try! ist in Ihrer Kiste enthalten. Wenn Sie einen Funktionszeiger oder ähnliches wie Bibliothekscode übergeben und ein Fehler in Ihrer Funktion vorliegt, funktioniert der try-Ansatz weiterhin. Wenn eine von Ihnen verwendete Kiste einen internen Fehler oder Fehler aufweist, benötigen Sie möglicherweise den Quellcode, um bereits zu debuggen, wenn die zurückgegebenen Informationen von Err nicht hilfreich sind. Stapelspuren sind nur hilfreich, wenn Sie Zugriff auf den Code haben. Daher müssen geschlossene Quellbibliotheken (deren Code Sie nicht ändern können) die Unterstützung auf die eine oder andere Weise überprüfen.

Wie wäre es, einfach beide Ansätze zu aktivieren und den Entwickler entscheiden zu lassen, was für ihn am besten ist? Ich bestreite nicht, dass der TLS-basierte Ansatz keine Vorteile hat.

Das Try-Modell ist sehr einfach zu implementieren. Desugar ? bis try , nur zusätzliche Spracherweiterungen sind erforderlich, wenn catch hereinkommt (Sie hätten dieses Verhalten innerhalb des fest codierten Verhaltens hinzugefügt von ? sowieso)

@eddyb "Denken Sie, dass Anweisungszeiger im Vergleich zu zufälligen Bezeichnern, die von Ihrem Build-System zum Debuggen von Release-Binärdateien generiert wurden, tatsächlich so nützlich sind?"

So funktioniert das Debuggen nativer Binärdateien im Allgemeinen. Wir (Sentry) verwenden dies derzeit fast ausschließlich für den iOS-Support. Wir bekommen einen Crash Dump und lösen die Adressen mit llvm-symbolizer in die tatsächlichen Symbole auf.

@mitsuhiko Da wir bereits libbacktrace eingebettet haben, könnten wir damit Codezeiger auf Quellspeicherorte auflösen, also bin ich nicht ganz dagegen.

@eddyb ja. Ich habe mir nur den Panikcode angesehen. Wäre schön, wenn das tatsächlich eine API wäre, die Rust-Code verwenden könnte. Ich kann sehen, dass dies an einigen weiteren Stellen nützlich ist.

Darüber - Ich hatte einige Gedanken über einen Trick auf Typsystemebene, bei dem {Option, Ergebnis} :: unwrap ein zusätzliches Typargument haben würde, das standardmäßig einen Typ verwendet, der von der Position abhängt, von der aus die Funktion aufgerufen wurde, sodass die Panik von diesen ausfällt haben viel nützlichere Standortinformationen.

Apropos ...

@glaebhoerl Hah, vielleicht lohnt es sich dann tatsächlich, das zu verfolgen? Zumindest als instabiles Experiment.

@eddyb Keine Ahnung. Wahrscheinlich lohnt es sich, zuerst mit einigen GHCern darüber zu diskutieren. Ich habe nur beiläufig davon gehört und gerade einen Link gegoogelt. Und Rust hat keine tatsächlichen impliziten Parameter wie GHC. Ob Standardtypparameter stattdessen funktionieren könnten, ist eine interessante Frage (wahrscheinlich eine, über die Sie mehr nachgedacht haben als ich).

Nur ein Gedanke: Es wäre nützlich, einen Schalter zu haben, der rustc dazu veranlasst, im Sonderfall eine Err so zu konstruieren, dass vor der Rückkehr eine fn(TypeId, *mut ()) -Funktion mit der Nutzlast aufgerufen wird . Dies sollte ausreichen, um mit grundlegenden Dingen wie dem Filtern von Fehlern basierend auf der Nutzlast, dem Einfangen in einen Debugger, wenn ein interessierender Fehler auftritt, oder dem Erfassen von Rückverfolgungen für bestimmte Arten von Fehlern zu beginnen.

PR 33389 fügt experimentelle Unterstützung für das Merkmal Carrier . Da es nicht Teil des ursprünglichen RFC war, sollte es eine besonders enge Prüfungs- und Diskussionsphase erhalten, bevor wir zu FCP wechseln (das wahrscheinlich für den Rest des ? -Operators vom FCP getrennt sein sollte). Weitere Informationen finden Sie in diesem Diskussionsthread .

Ich bin dagegen, ? auf Option s zu erweitern.

Der Wortlaut des RFC ist ziemlich klar darüber, dass es beim Operator ? darum geht, Fehler / "Ausnahmen" zu verbreiten.
Die Verwendung von Option zur Meldung eines Fehlers ist das falsche Werkzeug. Die Rückgabe von None ist Teil des normalen Workflows eines erfolgreichen Programms, während die Rückgabe von Err immer auf einen Fehler hinweist.

Wenn wir jemals einige Bereiche der Fehlerbehandlung verbessern möchten (z. B. durch Hinzufügen von Stack-Traces), bedeutet die Implementierung von ? auf Option , dass wir ? von den Änderungen ausschließen müssen .

@ Tomaka könnten wir die Diskussion auf dem Diskussionsthread fortsetzen ? (Ich habe Ihren Einwand bereits zusammengefasst. ) Ich persönlich finde, dass lange Diskussionen über GH ziemlich unhandlich werden, und es wäre auch schön, die Diskussion über diesen bestimmten Punkt von anderen zukünftigen Punkten oder Fragen zu trennen, die sich möglicherweise ergeben.

@eddyb Hier sind die Dokumente der veröffentlichten Version der impliziten Callstacks-GHC-Funktion, die ich zuvor erwähnt habe .

Hier gibt es seit einiger Zeit keine Updates mehr. Arbeitet jemand daran, dies voranzutreiben? Sind die verbleibenden Aufgaben im OP noch korrekt? Kann hier irgendetwas E-Hilfe gesucht werden?

Ich habe letztes Wochenende herumgespielt, wenn es möglich wäre, einen Sentry- Client für Rust zu schreiben, der irgendeinen Sinn ergibt. Da die meisten Fehlerbehandlungen jetzt tatsächlich auf Ergebnissen anstatt auf Panik basieren, habe ich festgestellt, dass die Nützlichkeit davon stark auf den Punkt beschränkt ist, an dem ich mich gerade entschlossen habe, dies ganz aufzugeben.

Ich ging als Beispiel zur Codebasis crates.io, um zu versuchen, ein Fehlermeldesystem darin zu integrieren. Dies brachte mich zurück zu diesem RFC, weil ich wirklich denke, dass es unmöglich sein wird, eine ordnungsgemäße Fehlerberichterstattung zu erhalten, wenn wir den Befehlszeiger nicht irgendwie aufzeichnen können, wenn die Ergebnisse weitergegeben und in die verschiedenen Stacktraces konvertiert werden. Ich sehe bereits, dass dies ein massiver Schmerz ist, wenn ich nur lokale komplexe Logikfehler debugge, bei denen ich heutzutage Panik hinzufüge, von der ich denke, dass der Fehler kommt.

Leider sehe ich derzeit nicht, wie die IP ohne massive Änderungen an der Funktionsweise der Ergebnisse aufgezeichnet werden könnte. Hat noch jemand mit dieser Idee herumgespielt?

Wir haben dies im @ rust-lang / lang-Meeting besprochen. Einige Dinge, die auftauchten:

Erstens besteht definitiv Interesse daran, dass sich ? so schnell wie möglich stabilisiert. Ich denke jedoch, dass die meisten von uns auch gerne sehen würden, dass ? mit Option ? operiert und nicht nur Result (aber nicht, glaube ich, bool , as wurde ebenfalls vorgeschlagen). Eine Sorge bei der Stabilisierung ist, dass wenn wir uns stabilisieren, ohne irgendeine Eigenschaft anzubieten, die die Verwendung anderer Typen als Result erlaubt, es nicht abwärtskompatibel ist, sie später hinzuzufügen.

Zum Beispiel schreibe ich selbst regelmäßig Code wie diesen:

let v: Vec<_> = try!(foo.iter().map(|x| x.to_result()).collect());

wo ich mich auf try! , um Typinferenz zu informieren, dass ich erwarte, dass collect Result<Vec<_>, _> . Wenn ? , kann diese Folgerung in Zukunft fehlschlagen.

In früheren Diskussionen haben wir jedoch auch entschieden, dass eine Änderung des RFC erforderlich ist, um die Feinheiten jeder Art von "Träger" -Eigenschaft zu erörtern. Natürlich sollte dieser RFC so schnell wie möglich geschrieben werden, aber wir würden es vorziehen, den Fortschritt bei ? in dieser Diskussion nicht zu blockieren.

Ein Gedanke, den wir hatten, war, dass wenn wir die Implementierung von @nrc nehmen und das Merkmal instabil machen und es nur für Result und einen privaten Dummy-Typ implementieren, dies die Inferenz unterdrücken sollte, während immer noch nur ? verwendbar mit Result .

Ein letzter Punkt: Ich denke, die meisten von uns würden es vorziehen, wenn Sie ? für einen Option verwenden, muss der Rückgabetyp Ihrer Funktion auch Option (nicht z Result<T,()> ). Es ist interessant festzustellen, dass dies bei den Inferenzbeschränkungen hilfreich ist, da wir letztendlich in vielen Fällen aus dem deklarierten Rückgabetyp schließen können, welcher Typ erforderlich ist.

Der Grund dafür, dass keine gegenseitige Konvertierung gewünscht wird, besteht darin, dass dies wahrscheinlich zu einer losen Logik führt, ähnlich wie C if x zulässt, selbst wenn x einen integralen Typ hat. Das heißt, wenn Option<T> einen Wert bezeichnet, bei dem None Teil der Domäne dieses Werts ist (wie es sollte) und Result<> (normalerweise) den Ausfall einer Funktion darstellt Um erfolgreich zu sein, scheint die Annahme, dass None bedeutet, dass die Funktion fehlerhaft sein sollte, verdächtig (und wie eine Art willkürliche Konvention). Aber diese Diskussion kann auf den RFC warten, nehme ich an.

Der Grund dafür, dass keine Interkonvertierung gewünscht wird, besteht darin, dass dies wahrscheinlich zu einer losen Logik führt, ähnlich wie C if x zulässt, selbst wenn x einen integralen Typ hat. Das heißt, wenn Option<T> einen Wert bezeichnet, bei dem None Teil der Domäne dieses Werts ist (wie es sollte) und Result<> (normalerweise) den Ausfall einer Funktion darstellt Um erfolgreich zu sein, scheint die Annahme, dass None bedeutet, dass die Funktion fehlerhaft sein sollte, verdächtig (und wie eine Art willkürliche Konvention). Aber diese Diskussion kann auf den RFC warten, nehme ich an.

Dem stimme ich von Herzen zu.

Eine andere Frage, bei der wir uns auf eine Torstabilisierung geeinigt hatten, war die Festlegung der Verträge, denen impl s des Merkmals From gehorchen sollten (oder welches Merkmal wir auch immer für Err -Upcasting verwenden) ).

@glaebhoerl

Eine andere Frage, bei der wir uns auf eine Torstabilisierung geeinigt hatten, bestand darin, die Verträge festzulegen, denen die Implikationen des From-Merkmals entsprechen sollten (oder welches Merkmal wir auch für Err-Upcasting verwenden).

Tatsächlich. Können Sie mein Gedächtnis auffrischen und mit einigen Beispielen von Dingen beginnen, die Ihrer Meinung nach verboten sein sollten? Oder vielleicht nur die Gesetze, an die Sie denken? Ich muss zugeben, dass ich mich vor solchen "Gesetzen" hüte. Zum einen neigen sie dazu, in der Praxis ignoriert zu werden - Menschen nutzen das tatsächliche Verhalten, wenn es ihren Zwecken entspricht, auch wenn es über die beabsichtigten Grenzen hinausgeht. Das führt zu einer weiteren Frage: Wenn wir Gesetze hätten, würden wir sie für irgendetwas verwenden? Optimierungen? (Scheint mir allerdings unwahrscheinlich.)

Wie ist übrigens der Zustand von catch Ausdrücken? Sind sie implementiert?

Traurigerweise Nein :(

Am Dienstag, den 26. Juli 2016 um 06:41:44 Uhr -0700 schrieb Alexander Bulaev:

Wie ist übrigens der Zustand von catch Ausdrücken? Sind sie implementiert?


Sie erhalten dies, weil Sie den Thread verfasst haben.
Antworte direkt auf diese E-Mail oder sieh sie dir auf GitHub an:
https://github.com/rust-lang/rust/issues/31436#issuecomment -235270663

Vielleicht sollten Sie die Implementierung planen? Es gibt nichts Deprimierenderes als akzeptierte, aber nicht implementierte RFCs ...

cc # 35056

Zu Ihrer Information, https://github.com/rust-lang/rfcs/pull/1450 (Typen für Enum-Varianten) würde einige interessante Möglichkeiten eröffnen, Carrier implementieren. Zum Beispiel so etwas wie:

trait Carrier {
    type Success: CarrierSuccess;
    type Error: CarrierError;
}

trait CarrierSuccess {
    type Value;
    fn into_value(self) -> Self::Value;
}

// (could really use some HKT...)
trait CarrierError<Equivalent: CarrierError> {
    fn convert_error(self) -> Equivalent;
}

impl<T, E> Carrier for Result<T, E>
{
    type Success = Result::Ok<T, E>;
    type Error = Result::Err<T, E>;
}

impl<T, E> CarrierSuccess for Result::Ok<T, E> {
    type Value = T;
    fn into_value(self) -> Self::Value {
        self.0
    }
}

impl<T, E1, E2> CarrierError<Result::Err<T, E2>> for Result::Err<T, E1>
    where E2: From<E1>,
{
    fn convert_error(self) -> Result::Err<T, E2> {
        Err(self.into())
    }
}

impl<T> Carrier for Option<T>
{
    type Success = Option::Some<T>;
    type Error = None;
}

impl<T> CarrierSuccess for Option::Some<T> {
    type Value = T;
    fn into_value(self) -> Self::Value {
        self.0
    }
}

impl<T> CarrierError<Option::None> for Option::None {
    fn convert_error(self) -> Option::None {
        self
    }
}

fn main() {
    let value = match might_be_err() {
        ok @ Carrier::Success => ok.into_value(),
        err @ Carrier::Error => return err.convert_error(),
    }
}

Ich wollte nur einige Gedanken von https://github.com/rust-lang/rust/pull/35056#issuecomment -240129923 überkreuzen. Diese PR führt ein Carrier Merkmal mit Dummy-Typ ein. Die Absicht war es, zu schützen - insbesondere wollten wir ? stabilisieren, ohne die Interaktion mit der Typinferenz stabilisieren zu müssen. Diese Kombination aus Merkmal und Dummy-Typ schien sicher konservativ zu sein.

Die Idee war (glaube ich), dass wir dann einen RFC schreiben, in dem Carrier besprochen wird, und versuchen, das Design so zu ändern, dass es sich nur dann stabilisiert, wenn wir mit der Gesamtform zufrieden sind (oder möglicherweise Carrier entfernen)

Wenn ich etwas spekulativer spreche, gehe ich davon aus, dass wir, wenn wir ein Carrier -Eigenschaft übernehmen, die gegenseitige Umwandlung zwischen Carriern nicht zulassen möchten (während dieses Merkmal im Grunde eine Möglichkeit ist, in / von Result umzuwandeln ? auf Option anwenden, ist dies in Ordnung, wenn das fn Option zurückgibt. und wenn Sie ? auf Result<T,E> anwenden, ist das in Ordnung, wenn das fn Result<U,F> zurückgibt, wobei E: Into<F> ; Aber wenn Sie ? auf ein Option anwenden und das fn Result<..> zurückgibt, ist das nicht in Ordnung.

Trotzdem ist diese Art von Regel im heutigen Typensystem schwer auszudrücken. Der naheliegendste Ausgangspunkt wäre so etwas wie HKT (was wir natürlich nicht wirklich haben, aber lassen Sie uns das vorerst ignorieren). Das ist jedoch offensichtlich nicht perfekt. Wenn wir es verwenden würden, würde man annehmen, dass der Parameter Self für Carrier die Art type -> type -> type , da Result Carrier implementieren kann . Das würde es uns ermöglichen, Dinge wie Self<T,E> -> Self<U,F> auszudrücken. Es würde jedoch nicht unbedingt für Option , das die Art type -> type (all dies würde natürlich genau davon abhängen, welche Art von HKT-System wir übernommen haben, aber ich glaube nicht, dass wir ' Ich gehe den ganzen Weg zu "Lambdas vom allgemeinen Typ"). Noch extremer könnte ein Typ wie bool (obwohl ich Carrier für bool nicht implementieren möchte, würde ich erwarten, dass einige Leute Carrier für einen neuen Typ implementieren möchten würde bool).

Was ich in Betracht gezogen hatte, war, dass die Schreibregeln für ? selbst etwas Besonderes sein könnten: Zum Beispiel könnten wir sagen, dass ? nur auf einen nominalen Typ Foo<..> von _some_ angewendet werden kann Art, und dass es mit dem Merkmal Carrier gegen diesen Typ übereinstimmt, aber es erfordert, dass der Rückgabetyp des einschließenden fn auch Foo<..> . Wir würden also Foo mit neuen Typparametern instanziieren. Der Nachteil dieser Idee ist, dass wir diese Einschränkung nicht durchsetzen können, wenn weder der Typ, auf den ? angewendet wird, noch der Typ des einschließenden fn bekannt ist, ohne eine neue Art von Merkmalsverpflichtung hinzuzufügen. Es ist auch eher ad-hoc. :) Aber es würde funktionieren.

Ein anderer Gedanke, den ich hatte, ist, dass wir das Merkmal Carrier überdenken könnten. Die Idee wäre, Expr: Carrier<Return> wobei Expr der Typ ist, auf den ? angewendet wird, und Return der Typ der Umgebung ist. Zum Beispiel könnte es vielleicht so aussehen:

trait Carrier<Target> {
    type Ok;
    fn is_ok(&self) -> bool; // if true, represents the "ok-like" variant
    fn unwrap_into_ok(self) -> Self::Ok; // may panic if not ok
    fn unwrap_into_error(self) -> Target; // may panic if not error
}

Dann expr? Desugars an:

let val = expr;
if Carrier::is_ok(&val) {
    val.unwrap_into_ok()
} else {
    return val.unwrap_into_error();
}

Der Hauptunterschied besteht darin, dass Target nicht der Typ _error_ ist, sondern ein neuer Typ Result . So könnten wir zum Beispiel das folgende Impl hinzufügen:

impl<T,U,E,F> Carrier<Result<U,F>> for Result<T,E>
    where E: Into<F>
{
    type Ok = T;
    fn is_ok(&self) -> bool { self.is_ok() }
    fn unwrap_into_ok(self) -> Self::Ok { self.unwrap() }
    fn unwrap_into_error(self) -> { Err(F::from(self.unwrap_err())) }
}

Und dann könnten wir hinzufügen:

impl<T> Carrier<Option<T>> for Option<T> {
    type Ok = T;
    fn is_ok(&self) -> bool { self.is_some() }
    fn unwrap_into_ok(self) -> Self::Ok { self.unwrap() }
    fn unwrap_into_error(self) -> { debug_assert!(self.is_none()); None }
}

Und schließlich könnten wir für bool so implementieren:

struct MyBool(bool);
impl<T> Carrier<MyBool> for MyBool {
    type Ok = ();
    fn is_ok(&self) -> bool { self.0 }
    fn unwrap_into_ok(self) -> Self::Ok { debug_assert!(self.0); () }
    fn unwrap_into_error(self) -> { debug_assert!(!self.0); self }
}

Jetzt ist diese Version flexibler. Zum Beispiel könnten wir zulassen, dass die gegenseitige Konvertierung zwischen Option -Werten in Result konvertiert wird, indem ein impl hinzugefügt wird, wie:

impl<T> Carrier<Result<T,()>> for Option<T> { ... }

Aber natürlich müssen wir nicht (und wir würden nicht).

@Stebalien

Zu Ihrer Information, rust-lang / rfcs # 1450 (Typen für Enum-Varianten) würde einige interessante Möglichkeiten für die Implementierung von Carrier eröffnen

Als ich diese Idee schrieb, die ich gerade geschrieben habe, dachte ich darüber nach, Typen für Enum-Varianten zu haben und wie sich das auf die Dinge auswirken könnte.

Eine Sache, die mir beim Schreiben von Code mit ? aufgefallen ist, ist, dass es leicht ärgerlich ist, kein "throw" -Schlüsselwort zu haben - insbesondere, wenn Sie Err(foo)? schreiben, tut der Compiler dies nicht. Wenn Sie wissen, dass dies zurückkehren wird, müssen Sie return Err(foo) schreiben. Das ist in Ordnung, aber dann erhalten Sie die into() -Konvertierungen nicht, ohne sie selbst zu schreiben.

Dies kommt in Fällen wie:

let value = if something_or_other() { foo } else { return Err(bar) };

Oh, ich sollte noch etwas hinzufügen. Die Tatsache, dass wir impls erlauben, die Typinferenz zu beeinflussen, sollte bedeuten, dass foo.iter().map().collect()? in einem Kontext, in dem das fn Result<..> zurückgibt, ich vermute, dass keine Typanmerkungen erforderlich wären, da, wenn wir wissen, dass die Wenn der Rückgabetyp Result , würde möglicherweise nur ein Impl zutreffen (zumindest lokal).

Oh, und eine etwas bessere Version meines Merkmals Carrier wäre wahrscheinlich:

trait Carrier<Target> {
    type Ok;
    fn into_carrier(self) -> Result<Self::Ok, Target>;
}

wo Sie es implementieren würden wie:

impl<T,U,E,F> Carrier<Result<U,F>> for Result<T,E>
    where E: Into<F>
{
    type Ok = T;
    fn into_carrier(self) -> Result<T, Result<U,F>> {
        match self { Ok(v) => Ok(v), Err(e) => Err(e.into()) }
    }
}

Und expr? würde Code generieren wie:

match Carrier::into_carrier(expr) {
    Ok(v) => v,
    Err(e) => return e,
}

Ein Nachteil (oder ein Vorteil ...) davon ist natürlich, dass die Into -Konvertierungen in die Impls verschoben werden, was bedeutet, dass die Leute sie möglicherweise nicht verwenden, wenn es sinnvoll ist. Bedeutet aber auch, dass Sie sie deaktivieren können, wenn (für Ihren speziellen Typ) dies nicht gewünscht ist.

@nikomatsakis IMO, das Merkmal sollte IntoCarrier und IntoCarrier::into_carrier sollte ein Carrier (eine neue Aufzählung) zurückgeben, anstatt das Ergebnis wie dieses wiederzuverwenden. Das ist:

enum Carrier<C, R> {
    Continue(C),
    Return(R),
}
trait IntoCarrier<Return> {
    type Continue;
    fn into_carrier(self) -> Carrier<Self::Continue, Return>;
}

@Stebalien sicher, scheint in Ordnung.

Nominierung für die Diskussion (und möglicher FCP des ? -Operators allein) beim lang-Team-Meeting. Ich gehe davon aus, dass wir in den nächsten Tagen eine Art vorübergehendes Carrier-Merkmal bei FCP landen müssen.

Ich öffnete rust-lang / rfcs # 1718, um das Carrier-Merkmal zu besprechen.

Höre, höre! Der Operator ? jetzt speziell in den letzten Kommentarzeitraum ein . Diese Diskussion dauert ungefähr für diesen Veröffentlichungszyklus, der am 18. August begann. Die Neigung besteht darin , den Operator ? zu stabilisieren .

In Bezug auf das Trägermerkmal landete eine temporäre Version in # 35777, die sicherstellen sollte, dass wir die Freiheit haben, in beide Richtungen zu entscheiden, indem unerwünschte Interaktionen mit Typinferenz verhindert werden.

@ rust-lang / lang Mitglieder, bitte kreuzen Sie Ihren Namen an, um die Zustimmung zu signalisieren. Hinterlasse einen Kommentar mit Bedenken oder Einwänden. Andere, bitte hinterlassen Sie Kommentare. Vielen Dank!

  • [x] @nikomatsakis
  • [x] @nrc
  • [x] @aturon
  • [x] @eddyb
  • [] @pnkfelix (im Urlaub)

Ich frage mich, ob tokio-basierte Bibliotheken am Ende viel and_then . Dies wäre ein Argument dafür, dass foo().?bar() eine Abkürzung für foo().and_then(move |v| v.bar()) , damit Ergebnisse und Futures dieselbe Notation verwenden können.

Um ganz klar zu sein, in diesem FCP geht es um die Funktion question_mark , nicht catch , richtig? Der Titel dieser Ausgabe impliziert die Verfolgung dieser beiden Funktionen in dieser einen Ausgabe.

@ Seanmonstar Letzteres wurde noch nicht einmal implementiert, also ja. Wenn der FCP zu einer Akzeptanz führt, wird dies vermutlich geändert, um catch zu verfolgen.

Um ganz klar zu sein, in diesem FCP geht es um die Funktion question_mark, nicht catch, richtig? Der Titel dieser Ausgabe impliziert die Verfolgung dieser beiden Funktionen in dieser einen Ausgabe.

Ja, nur die Funktion question_mark .

Follow-up auf https://github.com/rust-lang/rfcs/issues/1718#issuecomment -241764523. Ich hatte gedacht, das aktuelle Verhalten ? könnte in "Aktuelle Fortsetzung binden" verallgemeinert werden, aber der map_err(From::from) Teil von ? macht es etwas mehr als nur Binden: /. Wenn wir insgesamt eine From -Instanz für das Ergebnis hinzufügen, könnte die Semantik v? => from(v.bind(current-continuation)) .

In diesem internen Thread wurde viel über die relativen Vorzüge von ? diskutiert:

https://internals.rust-lang.org/t/the-operator-will-be-harmful-to-rust/3882/

Ich habe gerade keine Zeit für eine ausführliche Zusammenfassung. Ich erinnere mich, dass sich die Kommentare auf die Frage konzentrieren, ob ? ausreichend sichtbar ist, um auf Fehler aufmerksam zu machen, aber ich übersehen wahrscheinlich andere Facetten der Diskussion. Wenn jemand anderes Zeit zum Zusammenfassen hat, wäre das großartig!

Ich habe noch nie zuvor einen Kommentar abgegeben und dies ist möglicherweise viel zu spät, aber ich finde den Operator ? verwirrend, wenn er als versteckte Rückgabeanweisung verwendet wird, wie @hauleth in der Diskussion hervorgehoben hat, die Sie mit @ verknüpft haben Nikomatsakis.

Bei try! war es offensichtlich, dass es irgendwo eine Rendite geben könnte, weil ein Makro das kann. Mit dem ? als verstecktem return wir drei Möglichkeiten, Werte von einer Funktion zurückzugeben:

  • implizite Rückgabe
  • explizite Rückgabe
  • ?

Ich mag das aber, wie @CryZe sagte:

Auf diese Weise ist es jedem bekannt, der Fehler wird bis zum Ende weitergeleitet, wo Sie damit umgehen können, und es gibt keine impliziten Rückgaben. So könnte es ungefähr aussehen:

lass a = versuchen! (x? .y? .z);

Dies hilft sowohl, den Code präziser zu gestalten, als auch eine Rückgabe nicht zu verbergen. Und es ist aus anderen Sprachen wie coffeescript .

Wie würde sich eine Auflösung von ? auf der Ausdrucksebene anstelle der Funktionsebene auf die Zukunft auswirken? Für alle anderen Anwendungsfälle scheint es mir in Ordnung zu sein.

Ich mag das aber, wie @CryZe sagte:

Auf diese Weise ist es jedem bekannt, der Fehler wird bis zum Ende weitergeleitet, wo Sie damit umgehen können, und es gibt keine impliziten Rückgaben. So könnte es ungefähr aussehen:

lass a = versuchen! (x? .y? .z);

Ich habe das postuliert. Ich denke, es wäre eine perfekte Lösung.

Mit try!, War es offensichtlich, dass es irgendwo eine Rückkehr geben könnte, weil ein Makro das kann.

Es ist nur offensichtlich, weil Sie mit der Funktionsweise von Makros in Rust vertraut sind. Was genau das gleiche sein wird, wenn ? stabil, weit verbreitet und in jedem Intro zu Rust erklärt ist.

@conradkleinespel

Wir hätten drei Möglichkeiten, Werte von einer Funktion zurückzugeben:

  • implizite Rückgabe

Rust hat keine "impliziten Rückgaben", sondern Ausdrücke, die einen Wert ergeben. Dies ist ein wichtiger Unterschied.

if foo {
    5
}

7

Wenn Rust "implizite Rückgabe" hätte, würde dieser Code kompiliert. Aber das tut es nicht, du brauchst return 5 .

Welches wird einmal genau das gleiche sein? ist stabil, weit verbreitet und wird in jedem Intro zu Rust erklärt.

Ein Beispiel dafür finden Sie unter https://github.com/rust-lang/book/pull/134

Mit try!, War es offensichtlich, dass es irgendwo eine Rückkehr geben könnte, weil ein Makro das kann.
Es ist nur offensichtlich, weil Sie mit der Funktionsweise von Makros in Rust vertraut sind. Welches wird einmal genau das gleiche sein? ist stabil, weit verbreitet und wird in jedem Intro zu Rust erklärt.

In jeder Sprache, die mir bekannt ist, bedeutet "Makros" "hier sind Drachen" und dort kann alles passieren. Also würde ich das umformulieren in "weil Sie mit der Funktionsweise von Makros vertraut sind", ohne "in Rust".

@hauleth

lass a = versuchen! (x? .y? .z);

Ich habe das postuliert. Ich denke, es wäre eine perfekte Lösung.

Ich bin absolut anderer Meinung. Sie erhalten ein magisches Symbol, das nur in try! und nicht außerhalb funktioniert.

@hauleth

lass a = versuchen! (x? .y? .z);
Ich habe das postuliert. Ich denke, es wäre eine perfekte Lösung.
Ich bin absolut anderer Meinung. Da erhalten Sie ein magisches Symbol, das nur im Versuch funktioniert! und nicht draußen.

Ich habe nicht gesagt, dass ? nur in try! funktionieren soll. Was ich gesagt habe ist, dass ? wie ein Pipe-Operator funktionieren sollte, der Daten in den Stream drückt und Fehler zurückgibt, sobald sie auftreten. try! würde in diesem Fall nicht benötigt, könnte aber im selben Kontext verwendet werden, wie er jetzt verwendet wird.

@steveklabnik Ich 5 nicht implizit zurückgegeben, aber nehmen wir Folgendes :

fn test() -> i32 {
    5
}

Hier wird 5 implizit zurückgegeben, nicht wahr? Im Gegensatz zu return 5; würden Sie in Ihrem Beispiel benötigen. Dies bietet zwei verschiedene Möglichkeiten, einen Wert zurückzugeben. Was ich an Rust etwas verwirrend finde. Das Hinzufügen eines dritten würde IMO nicht helfen.

Es ist nicht. Es ist das Ergebnis eines Ausdrucks, insbesondere des Funktionskörpers. "implizite Rückgabe" impliziert, dass Sie irgendwie implizit von überall zurückkehren können, aber das ist nicht wahr. Keine andere ausdrucksbasierte Sprache nennt dies eine "implizite Rückgabe", da dies mein Codebeispiel oben wäre.

@steveklabnik Okay, danke, dass hast , dies zu erklären: +1:

Es ist alles gut! Ich kann total sehen, woher du kommst, es sind nur zwei verschiedene Dinge, die die Leute oft falsch benutzen. Ich habe Leute gesehen, die angenommen haben, dass "implizite Rückgabe" bedeutet, dass Sie die ; einfach irgendwo in der Quelle weglassen können, um zurückzukehren ... das wäre sehr schlecht: smile:

@hauleth Die? Operator wäre in diesem Fall nur syntaktischer Zucker für and_then. Auf diese Weise könnten Sie es in viel mehr Fällen verwenden und es müsste nicht schwer sein, die Rückkehr zu verpassen. Dies ist auch, was alle anderen Sprachen haben, die eine? Operator. Rost? Operator in der aktuellen Implementierung wäre das genaue Gegenteil von dem, was alle anderen Sprachen tun. Auch and_then ist der funktionale Ansatz und wird trotzdem empfohlen, da er einen klaren Kontrollfluss hat. Also nur machen? syntaktischer Zucker für and_then und dann den aktuellen Versuch behalten! für explizit "auspacken und zurückgeben" scheint die viel sauberere situation zu sein, indem die rückgaben sichtbarer gemacht werden und die? Bediener flexibler (indem er in Fällen ohne Rückgabe wie Mustervergleich verwendet werden kann).

Genau.

Łukasz Niemier
[email protected]

Wiadomość napisana przez Christopher Serr [email protected] w dniu 02.09.2016, o godz. 21:05:

@hauleth https://github.com/hauleth Die? Operator wäre in diesem Fall nur syntaktischer Zucker für and_then. Auf diese Weise könnten Sie es in viel mehr Fällen verwenden und es müsste nicht schwer sein, die Rückkehr zu verpassen. Dies ist auch, was alle anderen Sprachen haben, die eine? Operator. Rost? Operator in der aktuellen Implementierung wäre das genaue Gegenteil von dem, was alle anderen Sprachen tun. Auch and_then ist der funktionale Ansatz und wird trotzdem empfohlen, da er einen klaren Kontrollfluss hat. Also nur machen? syntaktischer Zucker für and_then und dann den aktuellen Versuch behalten! für explizit "auspacken und zurückgeben" scheint die viel sauberere situation zu sein, indem die rückgaben sichtbarer gemacht werden und die? Bediener flexibler (indem er in Fällen ohne Rückgabe wie Mustervergleich verwendet werden kann).

- -
Sie erhalten dies, weil Sie erwähnt wurden.
Antworten Sie direkt auf diese E-Mail, zeigen Sie sie auf GitHub https://github.com/rust-lang/rust/issues/31436#issuecomment -244461722 an oder schalten Sie den Thread https://github.com/notifications/unsubscribe-auth/ stumm.

Und als ich an einer Pull-Anfrage für das Rust-Repo arbeitete, musste ich tatsächlich mit Code arbeiten, der das? Betreiber und in der Tat hat es die Lesbarkeit für mich wirklich verletzt, wie es gerade gefällt; war super versteckt (mental, weil es nur Lärm ist, der im Gehirn herausgefiltert wird) und ich habe es viel übersehen. Und das finde ich ziemlich beängstigend.

@steveklabnik nennen wir es "implizite Rückkehr", weil wir nicht die einzigen sind .

@hauleth huh, in all meinen Jahren von Ruby habe ich noch nie von jemandem gehört, der es implizite Rückkehr nennt. Ich behaupte immer noch, dass es die falsche Art ist, darüber nachzudenken.

Ich habe in einigen Projekten ? und es try! vorgezogen, hauptsächlich, weil es sich in der Postfix-Position befindet. Im Allgemeinen wird "echter" Rust-Code bei main ziemlich in die Result 'Monade' eingegeben und verlässt ihn nur an gelegentlichen Blattknoten. und von einem solchen Code wird nur erwartet, dass er Fehler immer weitergibt. Zum größten Teil spielt es keine Rolle, welche Ausdrücke Fehler erzeugen - sie werden alle nur wieder in den Stapel zurückgeschickt, und das möchte ich nicht sehen, wenn ich die Hauptlogik des Codes durchlese.

Mein Hauptanliegen bei ? ist, dass ich mit Methodenmakros den gleichen Vorteil - Postfix-Position - erzielen kann, wenn sie existieren. Ich habe andere Bedenken, dass wir möglicherweise durch die Stabilisierung der aktuellen Formulierung die zukünftige Ausdruckskraft bei der Fehlerbehandlung einschränken - die aktuelle Result -Konvertierung reicht nicht aus, um die Fehlerbehandlung in Rust so ergonomisch zu gestalten, wie ich es gerne hätte. Wir haben bereits einige Konstruktionsfehler mit der Rust-Fehlerbehandlung gemacht, die schwer zu beheben sind, und dies könnte uns tiefer vertiefen. obwohl ich keine konkreten Beweise habe.

Ich habe das schon oft geschrieben, aber ich bin absolut verliebt in ? und die Möglichkeiten des Carrier-Merkmals. Ich habe ein Projekt vollständig auf ? umgestellt und es hat viele Dinge möglich gemacht (insbesondere in Bezug auf die Verkettung), die mit try! zu komplex waren. Ich habe auch zum Spaß einige andere Projekte durchgesehen, um zu sehen, wie sie mit ? und insgesamt habe ich damit keine Probleme festgestellt.

Als solches gebe ich der Stabilisierung von ? eine enorme +1 auf der Grundlage eines besser benannten Merkmals Carrier das idealerweise auch einige der anderen Fälle abdeckt, die ich in der anderen Diskussion darüber angesprochen habe.

Mein Hauptanliegen mit? ist, dass ich den gleichen Vorteil - Postfix-Position - mit Methodenmakros erhalten könnte, wenn sie existieren.

Vielleicht brauchen wir dafür einen RFC? Die meisten Leute scheinen die Funktionalität von? Zu mögen, aber nicht die? selbst.

Vielleicht brauchen wir dafür einen RFC? Die meisten Leute scheinen die Funktionalität von? Zu mögen, aber nicht die? selbst.

Es gibt einen RFC mit vielen Diskussionen dafür. Außerdem weiß ich nicht, woher Sie die "meisten Leute" haben. Wenn es von den Teilnehmern dieser Ausgabe stammt, werden Sie natürlich mehr Leute sehen, die dagegen argumentieren, da die Stabilisierung bereits die Standardaktion des Teams ist.
Das ? wurde vor dem Zusammenschluss des RFC ausführlich diskutiert, und als Unterstützer ist es ziemlich anstrengend, dasselbe tun zu müssen, wenn über die Stabilisierung gesprochen wird.

Wie auch immer, ich werde hier meine +1 für @mitsuhikos Gefühle

Hierfür gibt es einen RFC mit vielen Diskussionen. Außerdem weiß ich nicht, woher Sie die "meisten Leute" haben. Wenn es von den Teilnehmern dieser Ausgabe stammt, werden Sie natürlich mehr Leute sehen, die dagegen argumentieren, da die Stabilisierung bereits die Standardaktion des Teams ist.

Entschuldigung, mein Kommentar war zu kurz. Ich bezog mich auf das Erstellen eines RFC für eine Art von "Methodenmakros", zum Beispiel func1().try!().func2().try!() (Soweit ich weiß, ist dies derzeit nicht möglich).

Persönlich mag ich das? Betreiber, aber ich teile die gleichen Bedenken wie @brson , und ich denke, es wäre gut, Alternativen zu @nikomatsakis verknüpft hat, gibt es definitiv immer noch Streit um diese Funktion, auch wenn es sich immer wieder um dieselben Argumente handelt. Wenn es jedoch keine praktikablen Alternativen gibt, ist eine Stabilisierung am sinnvollsten.

Es erscheint verfrüht, ein Feature zu stabilisieren, ohne es vollständig implementiert zu haben - in diesem Fall den catch-Ausdruck {..}.

Ich habe bereits zuvor meine Besorgnis über diese Funktion geäußert und halte es immer noch für eine schlechte Idee. Ich denke, ein Postfix-Operator für bedingte Rückgabe ist anders als alles andere in einer anderen Programmiersprache und drängt Rust über sein bereits überlastetes Komplexitätsbudget hinaus.

@mcpherrinm Andere Sprachen haben stattdessen bei jedem Aufruf zur Fehlerbehandlung Abwicklungspfade ausgeblendet. operator() als "Operator für bedingte Rückgabe" bezeichnen?

Das Komplexitätsbudget unterscheidet sich nur syntaktisch von try! , zumindest dem Teil, über den Sie sich beschweren.
Ist das Argument gegen try! -schweren Code, den ? nur lesbarer macht?
Wenn ja, dann würde ich zustimmen, wenn es eine andere ernsthafte Alternative als "überhaupt keine Automatisierung der Fehlerausbreitung haben" gibt.

Vorschlag eines Kompromisses: https://github.com/rust-lang/rfcs/pull/1737

Es hat vielleicht keine Chance, akzeptiert zu werden, aber ich versuche es trotzdem.

Ich mag die Idee von @keeperofdakeys über "Methodenmakros". Ich denke nicht, dass die ? -Syntax aus dem gleichen Grund akzeptiert werden sollte, aus dem der ternäre Operator nicht in Rostlesbarkeit ist. Das ? selbst sagt nichts. Stattdessen würde ich viel lieber die Möglichkeit sehen, das Verhalten von ? mit den "Methodenmakros" zu verallgemeinern.

a.some_macro!(b);
// could be syntax sugar for
some_macro!(a, b); 
a.try!();
// could be syntax sugar for
try!(a); 

Auf diese Weise wird klar, wie sich das Verhalten verhält, und es ermöglicht eine einfache Verkettung.

Methodenmakros wie result.try!() scheinen eine allgemeinere Verbesserung der Sprachergonomie zu sein und fühlen sich weniger ad-hoc an als ein neuer ? -Operator.

@ Brson

Ich habe andere Bedenken, dass wir möglicherweise durch die Stabilisierung der aktuellen Formulierung die zukünftige Ausdruckskraft bei der Fehlerbehandlung einschränken - die aktuelle Ergebniskonvertierung reicht nicht aus, um die Fehlerbehandlung in Rust so ergonomisch zu gestalten, wie ich es gerne hätte

Dies ist ein interessanter Punkt. Es lohnt sich, etwas Zeit darauf zu verwenden (vielleicht können Sie und ich uns ein bisschen unterhalten). Ich bin damit einverstanden, dass wir es hier besser machen könnten. Das vorgeschlagene Design für ein Carrier -Eigenschaft (siehe https://github.com/rust-lang/rfcs/issues/1718) kann hier hilfreich sein, insbesondere in Kombination mit Spezialisierung, da es die Dinge flexibler macht.

Ich bezweifle wirklich, dass Methodenmakros eine gute Erweiterung der Sprache darstellen würden.

macro_rules! Makros werden derzeit analog zu freien Funktionen deklariert und werden noch analoger, wenn das neue Importsystem für sie übernommen wird. Was ich damit meine ist, dass sie als Elemente der obersten Ebene deklariert und wie Elemente der obersten Ebene aufgerufen werden. Bald werden sie auch wie Elemente der obersten Ebene importiert.

So funktionieren Methoden nicht. Methoden haben folgende Eigenschaften:

  1. Kann nicht in einem Modulbereich deklariert werden, muss jedoch innerhalb eines impl -Blocks deklariert werden.
  2. Werden mit dem Typ / Merkmal importiert, dem der Block impl ist, anstatt direkt importiert zu werden.
  3. Werden auf der Grundlage ihres Empfängertyps versendet, anstatt auf der Grundlage eines einzelnen, eindeutigen Symbols in diesem Bereich versandt zu werden.

Da Makros vor der Typüberprüfung erweitert werden, kann, soweit ich das beurteilen kann, keine dieser Eigenschaften für Makros gelten, die die Methodensyntax verwenden. Natürlich könnten wir nur Makros haben, die Methodensyntax verwenden, aber auf die gleiche Weise wie 'freie' Makros versendet und importiert werden, aber ich denke, die Ungleichheit würde dies zu einer sehr verwirrenden Funktion machen.

Aus diesen Gründen halte ich es nicht für eine gute Wahl, ? zu verzögern, da man glaubt, dass eines Tages "Methodenmakros" auftauchen könnten.

Darüber hinaus denke ich, dass es eine Linie gibt, an der einige Konstrukte so weit verbreitet und wichtig sind, dass sie von Makros zu Zucker gefördert werden sollten. for Schleifen sind ein gutes Beispiel. Das Verhalten von ? ist ein wesentlicher Bestandteil von Rusts Fehlerbehandlungsgeschichte, und ich denke, es ist angemessen, dass es sich um erstklassigen Zucker anstelle eines Makros handelt.

@hauleth , @CryZe

Um auf diejenigen zu antworten, die vorschlagen, dass ? ein and_then -Operator sein sollte, funktioniert dies in Sprachen wie Kotlin (ich bin mit Coffeescript nicht vertraut) gut, da sie häufig Erweiterungsfunktionen verwenden, dies ist jedoch nicht der Fall so einfach in Rost. Grundsätzlich sind die meisten Verwendungen von and_then nicht maybe_i.and_then(|i| i.foo()) , sondern maybe_i.and_then(|i| Foo::foo(i)) Ersteres könnte als maybe_i?.foo() ausgedrückt werden, letzteres jedoch nicht. Man könnte sagen, dass Foo::foo(maybe_i?, maybe_j?) zu maybe_i.and_then(|i| maybe_j.and_then(|j| Foo::foo(i, j))) aber das ist noch verwirrender, als nur zu sagen, dass Rost früh zurückkehrt, wenn man die ersten ? trifft, die zu einem Fehler führen. Dies wäre jedoch wohl mächtiger.

@Stebalien Im akzeptierten RFC macht catch { Foo::foo(maybe_i?, maybe_j?) } , was Sie wollen.

@eddyb Guter Punkt. Ich denke, ich kann das "Allerdings wäre dies wohl mächtiger" weglassen. Es kommt auf impliziten Fang / expliziten Versuch im Vergleich zu explizitem Fang / impliziten Versuch an:

let x: i32 = try Foo::foo(a?.b?.c()?));
let y: Result<i32, _> = Foo::foo(a?.b?.c()?);

Gegen:

let x: i32 = Foo::foo(a?.b?.c()?);
let y: Result<i32, _> = catch  Foo::foo(a?.b?.c()?);

(Modulo-Syntax)

@Stebalien Ein weiteres Beispiel: Wenn ich Foo an eine Funktion bar , müsste ich mit Ihrem Vorschlag:

bar(Foo::foo(a?.b?.c()?)?)

Ist es das, was du vorhast? Beachten Sie, dass die zusätzlichen ? , ohne die bar ein Result anstelle eines Foo .

@eddyb Wahrscheinlich. Hinweis: Ich schlage dies nicht wirklich vor! Ich behaupte, dass die Verwendung von ? als Rohrbetreiber bei Rost nicht besonders nützlich ist, ohne dass der Fall Foo::foo(bar?) kann.

Nur um zu bemerken, dass ich die Idee von Methodenmakros hasse und mir keine Sprachfunktion vorstellen kann, die ich stärker ablehnen würde. Sie verwirren die Phasen des Compilers, und wenn wir nicht wirklich grundlegende Änderungen an der Sprache vornehmen, können sie auf keinen Fall existieren und haben kein überraschendes Verhalten. Sie sind auch schwer vernünftig zu analysieren und mit ziemlicher Sicherheit nicht abwärtskompatibel.

@Stebalien , mit ? als Pipe-Operator Foo::foo(bar?) würde folgendermaßen aussehen: Foo::foo(try!(bar)) und bar(Foo::foo(a?.b?.c()?)?) (unter der Annahme, dass Foo::foo : fn(Result<_, _>) -> Result<_, _> ): bar(try!(Foo::foo(a?.b?.c()?))) .

@hauleth mein Punkt war, dass Foo::foo(bar?)? viel häufiger als bar?.foo()? in Rost ist. Um nützlich zu sein, müsste ? diesen Fall unterstützen (oder eine andere Funktion müsste eingeführt werden). Ich postulierte einen Weg, dies zu tun, und zeigte, dass dieser Weg zumindest chaotisch sein würde. Der gesamte Punkt von ? besteht darin, das Schreiben von try!(foo(try!(bar(try!(baz()))))) (2x Klammern!) Zu vermeiden. Es ist normalerweise nicht möglich, dies als try!(baz()?.bar()?.foo()) neu zu schreiben.

Aber Sie können immer tun:

try!(baz().and_then(bar).and_then(foo))

Łukasz Niemier
[email protected]

Wiadomość napisana przez Steven Allen [email protected] w dniu 05.09.2016, o godz. 15:39:

@hauleth https://github.com/hauleth Mein Punkt war, dass Foo :: foo (Bar?)? ist viel häufiger als bar? .foo ()? in Rost. Um nützlich zu sein ,? müsste diesen Fall unterstützen (oder eine andere Funktion müsste eingeführt werden). Ich postulierte einen Weg, dies zu tun, und zeigte, dass dieser Weg zumindest chaotisch sein würde. Der ganze Punkt von? soll vermeiden können, try! (foo (try! (bar (try! (baz ())))) (2x die Klammern!) zu schreiben; Es ist normalerweise nicht möglich, dies als try neu zu schreiben! (baz () ?. bar ()?. foo ()).

- -
Sie erhalten dies, weil Sie erwähnt wurden.
Antworten Sie direkt auf diese E-Mail, zeigen Sie sie auf GitHub https://github.com/rust-lang/rust/issues/31436#issuecomment -244749275 an oder schalten Sie den Thread https://github.com/notifications/unsubscribe-auth/ stumm.

In einem leicht verwandten Zusammenhang scheint es, dass die? -Funktion hauptsächlich von Buildern verwendet wird, sodass wir möglicherweise die Notwendigkeit der? -Funktion vermeiden könnten, indem wir eine einfache Möglichkeit bieten, Builder zu konstruieren, die ein Ergebnis umschließen. Ich habe hier etwas vorgeschlagen, aber es könnte etwas mehr Arbeit erfordern.

https://github.com/colin-kiegel/rust-derive-builder/issues/25

Vielen Dank für Ihre Gedanken zu den Methodenmakroideen @nrc und @withoutboats . Es ist gut, einige konkrete Gründe zu hören, warum sie nicht funktionieren würden.

@nielsle Ich glaube nicht, dass es richtig ist zu sagen, dass ? "hauptsächlich" von Bauherren verwendet wird. Während Builder ein Beispiel sind, bei dem ich denke, dass der Vorteil eines leichten Postfix-Operators wirklich zum Tragen kommt, bevorzuge ich in jedem Kontext ? gegenüber try! .

@nielsle in Bezug auf Futures war ich ursprünglich aus ähnlichen Gründen besorgt. Aber nachdem ich darüber nachgedacht habe, denke ich, dass async / await ? in diesem Zusammenhang die Notwendigkeit von (await future)?.bar() tun.

(Vielleicht wäre es schön, einen Suffix-Operator anstelle des Schlüsselworts await damit keine Parens erforderlich sind. Oder vielleicht würde eine sorgfältig abgestimmte Priorität ausreichen.)

Ich würde auf jeden Fall gerne eine Dokumentation sehen, bevor wir uns stabilisieren. Ich habe in der Referenz nachgesehen und keine Erwähnung gefunden. Wo sollen wir diese Funktion dokumentieren?

@cbreeden Ich weiß, dass @steveklabnik es im Allgemeinen vermeidet, instabile Funktionen zu dokumentieren, da es eine Zeitverschwendung ist, wenn sie niemals stabilisiert werden. Ich weiß nicht, ob wir jemals zuvor die Stabilisierung beim Schreiben von Dokumentationen blockiert haben.

@solson Du hast Recht, dies war wahrscheinlich nicht der richtige Ort, um es

Ich denke, der wichtige Teil dieses RFC besteht darin, etwas von der rechten Seite eines Ausdrucks zu haben, das sich wie try! verhält, da dies das Lesen von sequentiellen / verketteten Verwendungen von "try" _much_ besser lesbar macht und "catch" hat . Ursprünglich war ich ein 100% iger Befürworter der Verwendung von ? als Syntax, aber kürzlich bin ich auf einen (sauberen!) Code gestoßen, der bereits ? was mich darauf aufmerksam gemacht hat, dass außerhalb von einfachen Beispielen ? wird extrem leicht übersehen . Was mich jetzt glauben lässt, dass die Verwendung von ? als Syntax für "den neuen Versuch" ein großer Fehler sein könnte.

Daher schlage ich vor, dass es eine gute Idee sein könnte, vor dem Abschluss eine Umfrage durchzuführen (mit Benachrichtigung darüber in den Foren), um eine Rückmeldung über die Verwendung von ? oder anderen Symbolen als Syntax zu erhalten . Optimal mit Anwendungsbeispiel in einer längeren Funktion. Beachten Sie, dass ich nur in Betracht ziehe, dass es eine gute Idee sein könnte, ? umzubenennen, um nichts anderes zu ändern. Die Umfrage könnte mögliche andere Namen auflisten, die in der Vergangenheit aufgetaucht sind, wie ?! (oder ?? ) oder einfach so etwas wie "benutze? Vs. mehr als für Zeichen verwenden".

Übrigens. Wenn Sie ? möglicherweise auch die Leute zufrieden gestellt, die es nicht mögen, da es dieselbe Syntax wie der optionale Typ anderer Sprachen hat. Und diejenigen, die es zu einer optionalen Syntax für Rostoptionstypen machen möchten. (Hierdurch sind keine Bedenken, die ich teile).

Außerdem denke ich, dass mit einer solchen Umfrage Leute erreicht werden könnten, die normalerweise nicht am RFC-Prozess teilnehmen. Normalerweise wird dies möglicherweise nicht benötigt, aber try! => ? ist eine sehr große Änderung für jeden, der Rostcode schreibt und / oder liest.

PS:
Ich habe einen Kern mit der oben genannten Funktion in verschiedenen Variationen ("?", "?!", "??") erstellt, indem ich nicht weiß, ob es mehr gegeben hat. Außerdem gab es einen RFC zum Umbenennen von ? in ?! der zu dieser Diskussion umgeleitet wurde.

Entschuldigung, über einen möglichen Neustart einer bereits langen Diskussion: smiley_cat :.

(Beachten Sie, dass ?? schlecht ist, wenn Sie weiterhin ? für Option einführen möchten, da expr??? nicht eindeutig wäre.)

? wird extrem leicht übersehen

Was diskutieren wir hier? Völlig unmarkierter Code? Regelmäßiges Durchsuchen von hervorgehobenem Code?
Oder aktiv nach ? in einer Funktion suchen?

Wenn ich in meinem Editor ein ? auswähle, werden _all_ die anderen ? in der Datei mit einem hellgelben Hintergrund hervorgehoben, und die Suchfunktion funktioniert auch, sodass ich das letzte nicht sehe als eine Schwierigkeit für mich.

In anderen Fällen würde ich dieses Problem lieber durch eine bessere Hervorhebung lösen, als mit ?? oder ?! enden.

@dathinab Ich denke .try! () oder so wäre noch besser, aber das würde UFCS für Makros erfordern.

let namespace = namespace_opt.ok_or(Error::NoEntry).try!();

Auf diese Weise ist es schwer zu übersehen, aber genauso einfach, wenn nicht sogar einfacher zu tippen als .unwrap() .

@eddyb : Es geht um normalen hervorgehobenen Code, zB auf Github. ZB beim Lesen einer Codebasis. Aus meiner Sicht fühlt es sich irgendwie falsch an, wenn ich eine starke Hervorhebung benötige, um ein ? nicht leicht zu übersehen, was sowohl einen anderen Rückweg (/ Pfad zum Abfangen) einführen als auch möglicherweise den Typ einer Variablen von Result<T> ändern würde T

@CryZe : Ich stimme dir zu, aber ich glaube nicht, dass wir das in naher Zukunft bekommen können. Und etwas zu haben, das etwas kürzer als .try!() ist, ist auch nicht so schlecht.

@CryZe Ich mag diese Syntax auch, aber @withoutboats erwähnte einige solide Gründe, warum Methodenmakros die Sprache
Andererseits befürchte ich, dass Methodenmakros, wenn sie erscheinen, nicht gut mit ? .

Ich bin nicht von Natur aus dagegen, zwei Zeichen für dieses Siegel zu verwenden, aber ich habe mir die Beispiele angesehen und festgestellt, dass die Änderung ? für mich nicht mehr sichtbar erscheint.

Genau das gleiche. Ich denke, es muss eine Art Schlüsselwort sein, da Symbole im Allgemeinen eher für die Strukturierung verwendet werden, sowohl in normaler Sprache als auch in Programmiersprachen.

@CryZe : Vielleicht wäre so etwas wie ?try eine Art Schlüsselwort. Aber um ehrlich zu sein, ich mag es nicht ( ?try ).

@eddyb

und die Suchfunktion funktioniert auch

Wenn Sie nach ? suchen, werden Fehlalarme angezeigt, bei längeren Versionen höchstwahrscheinlich nicht.

Auf der Suche nach ? wird falsch positiv auftauchen, während längere Versionen höchstwahrscheinlich nicht.

Ich gehe davon aus, dass Syntax-Textmarker dies intern erledigen werden (Vermeidung von Fehlalarmen). Tatsächlich können sie sogar eine Art "möglicherweise zurückkehren" -Marker in den Rand einfügen (neben den Zeilennummern).

Zum Beispiel,
screen-2016-09-15-175131

Wow das hilft definitiv sehr. Aber dann müssen Sie wirklich sicherstellen, dass Sie
Haben Sie Ihren Editor richtig konfiguriert, was Sie nicht immer tun können (wie
auf GitHub zum Beispiel).

2016-09-15 23:52 GMT + 02: 00 Steven Allen [email protected] :

Zum Beispiel,
[Bild: Bildschirm-2016-09-15-175131]
https://cloud.githubusercontent.com/assets/310393/18568833/1deed796-7b6d-11e6-99af-75f0d7ddd778.png

- -
Sie erhalten dies, weil Sie erwähnt wurden.
Antworte direkt auf diese E-Mail und sieh sie dir auf GitHub an
https://github.com/rust-lang/rust/issues/31436#issuecomment -247465972,
oder schalten Sie den Thread stumm
https://github.com/notifications/unsubscribe-auth/ABYmbjyrt07NXKMUdmlBfaciRZq7uBVEks5qqb4sgaJpZM4HUm_-
.

@CryZe Ich bin mir ziemlich sicher, dass GitHub nur einen Open-Source-Syntax-

Ich denke, im Allgemeinen ist es eine gute Idee für das Rust-Projekt, eine Empfehlung abzugeben, dass Syntax-Textmarker für Rust einen gut sichtbaren Stil für ? sollten, um die Bedenken hinsichtlich der Sichtbarkeit auszugleichen.

Ich denke, ? ist die beste Wahl, die wir hier treffen können, und wir haben sie in Miri ziemlich häufig verwendet.

try!(...) ist aufdringlich und macht es schwierig, den Fehlerfluss zu ignorieren und nur den Happy-Path zu lesen. Dies ist ein Nachteil im Vergleich zu den unsichtbaren Ausnahmen traditioneller Sprachen. Das Erweitern von ? auf ein komplexeres Keyword hätte den gleichen Nachteil.

Auf der anderen Seite kann ich mit ? , wenn mir der Fehlerfluss egal ist, ihn ignorieren und in den Hintergrund treten lassen. Und wenn ich mich wirklich um den Fehlerfluss kümmere, kann ich immer noch ? . Das helle Hervorheben von ? ist für mich nicht einmal notwendig, aber wenn es anderen Menschen hilft, ist das großartig. Dies ist eine Verbesserung gegenüber unsichtbaren Ausnahmen und try! .

Der Wechsel zu einem trivial größeren Siegel wie ?! würde mir in keiner Weise helfen, aber das Lesen und Schreiben von Fehlerbehandlungscode geringfügig verschlechtern.

Vielen Dank an alle für eine herzliche abschließende Kommentierungsphase (sowie einen vorherigen internen Thread ). Angesichts der Tatsache, dass es sich um den bislang am

Lassen Sie mich zunächst zur Sache kommen: Das @ rust-lang / lang-Team hat beschlossen, den Operator ? zu stabilisieren, wenn er auf Werte vom Typ Result angewendet wird. Beachten Sie, dass die Funktion catch nicht stabilisiert wird (und tatsächlich noch nicht implementiert wurde). In ähnlicher Weise befindet sich das sogenannte "Carrier-Merkmal", mit dem ? auf Typen wie Option , noch in der Vor-RFC-Diskussionsphase . Wir haben jedoch in der aktuellen Implementierung Schritte unternommen, um sicherzustellen, dass wir das Merkmal Carrier später hinzufügen können (was einige meiner früheren Bedenken hinsichtlich einer möglichen Interaktion mit Inferenz anspricht).

Ich möchte mir etwas Zeit nehmen, um die Diskussion zusammenzufassen, die seit Beginn des FCP am 22. August stattgefunden hat. Viele dieser Themen sind auch im ursprünglichen RFC-Thread aufgetreten. Wenn Sie den Thread lesen möchten , versuchen der FCP-Kommentar und der Wiederholungskommentar in diesem Thread, die Konversation eingehend zu behandeln. In einigen Fällen werde ich auf Kommentare in diesem ursprünglichen Thread verweisen, wenn diese ausführlicher sind als die entsprechenden Kommentare aus diesem Thread.

Der Gültigkeitsbereich des Operators ? sollte der aktuelle Ausdruck sein, nicht die aktuelle Funktion.

Wenn ein Fehler auftritt, gibt das heutige Makro try! diesen Fehler bedingungslos an die aufrufende Funktion weiter (mit anderen Worten, es führt mit dem Fehler ein return ). Wie derzeit entworfen, folgt der Operator ? diesem Präzedenzfall, jedoch mit der Absicht, ein Schlüsselwort catch , mit dem der Benutzer einen eingeschränkteren Bereich angeben kann. Dies bedeutet zum Beispiel, dass x.and_then(|b| foo(b)) als catch { foo(x?) } Im Gegensatz dazu verwenden einige neuere Sprachen den Operator ? , um etwas Analogeres zu and_then zu bedeuten.

Sobald catch implementiert ist, ist dies letztendlich ? in Rust besser geeignet ist:

  • Das Makro try! hat sich vielfach als sehr nützlicher Standard erwiesen . ? ist als Ersatz für try! gedacht, daher ist es natürlich, dass es sich gleich verhält.
  • ? in Rust wird hauptsächlich zur Verbreitung von Ergebnissen verwendet , was eher Ausnahmen entspricht. Alle Ausnahmesysteme übertragen standardmäßig Fehler aus der aktuellen Funktion in den Aufrufer (auch solche, für die wie Swift ein Schlüsselwort erforderlich ist).

    • Im Gegensatz dazu hat ? in Swift, Groovy usw. mit Nullwerten oder Optionstypen zu tun.

    • Wenn wir ein Carrier-Merkmal übernehmen, kann der Operator ? in Rust in diesen Szenarien weiterhin verwendet werden (und insbesondere in Verbindung mit catch ), aber es ist nicht der Hauptanwendungsfall.

  • Viele Verwendungen von and_then in Rust würden heute in jedem Fall durch eine asynchrone Wartezeit-Transformation subsumiert werden

Das ? verdeckt den Kontrollfluss, da es schwer zu erkennen ist.

Ein häufiges Problem ist, dass der Operator ? zu leicht zu übersehen ist . Dies ist eindeutig ein Balanceakt. Einen leichten Operator Mit macht es einfach , wenn Sie wollen - aber es ist wichtig , können einige Hinweise auf , wo Fehler (im Gegensatz zu Ausnahmen, der impliziten Steuerstrom einführen) auftreten zu lassen. Darüber hinaus ist es einfach, ? durch Syntaxhervorhebung (z. B. 1 , 2 ) leichter zu erkennen.

Warum nicht Methodenmakros?

Einer der großen Vorteile von ? ist, dass es in der Post-Fix-Position verwendet werden kann, aber
Wir könnten ähnliche Vorteile von "Methodenmakros" wie foo.try! . Dies stimmt zwar, öffnet Methode Makros viel Komplexität auf mich , vor allem , wenn man sie wie Methoden verhalten soll ( zum Beispiel von der Art des Empfängers der ausgelöst basierte und nicht lexikalischen Gültigkeitsbereich verwendet wird ). Darüber hinaus hat die Verwendung eines Methodenmakros wie foo.try! ein deutlich schwereres Gewichtsgefühl als foo? (siehe vorherigen Punkt).

Welche Verträge sollten From anbieten?

In der ursprünglichen RFC-Diskussion haben wir beschlossen, die Frage [ob es "Verträge" für From ] ((https://github.com/rust-lang/rust/issues/31436#issuecomment) zu verschieben -180558025). Der allgemeine Konsens des lang-Teams ist, dass man den Operator ? als Aufruf des Merkmals From sollte und dass die From natürlich alles können Dies ist aufgrund ihrer Typensignaturen zulässig. Beachten Sie, dass die Rolle des Merkmals From hier ohnehin ziemlich begrenzt ist: Es wird einfach zum Konvertieren von einer Art von Fehler in eine andere verwendet (jedoch immer im Kontext eines Result ).

Allerdings möchte ich darauf hinweisen, dass die Diskussion über einen „Träger“ Charakterzug ist noch nicht Annahme starke Konventionen sind wichtiger in diesem Szenario. Insbesondere kann das Trägermerkmal definieren, was "Erfolg" und "Misserfolg" für einen Typ ausmacht und ob eine Art von Wert (z. B. ein Option ) in einen anderen umgewandelt werden kann (z. B. a Result ). Viele haben argumentiert, dass wir keine willkürlichen Konvertierungen zwischen "fehlerhaften" Typen unterstützen wollen (z. B. sollte ? nicht in der Lage sein, Option in Result umzuwandeln). Da Endbenutzer das Merkmal Carrier für ihre eigenen Typen nach Belieben implementieren können, ist dies letztendlich eine Richtlinie, aber ich denke, es ist eine wichtige.

Port versuchen! benutzen ?

Ich denke nicht, dass wir dies jemals abwärtskompatibel machen können und wir sollten die Implementierung von try! Ruhe lassen. Sollten wir try! ablehnen?

@nrc Warum ist es nicht abwärtskompatibel?

@withoutboats try!(x) ist (x : Result<_, _>)? und wir könnten es wahrscheinlich so implementieren, wenn wir es wollten, aber im Allgemeinen könnten x? auf alles schließen, was die Carrier Merkmal (in der Zukunft), ein Beispiel ist iter.collect()? das mit try! nur Result aber realistisch Option .

Das macht Sinn. Ich dachte, wir hätten akzeptiert, dass das Hinzufügen von Impls zu std zu Inferenzmehrdeutigkeiten führen kann. warum nicht auch das hier akzeptieren?

In jedem Fall denke ich, dass try! veraltet sein sollte.

? ist in einem Builder-Muster wie dem Kontext nützlicher, während try in einer verschachtelten Methode wie dem Kontext nützlicher ist. Ich denke, es sollte nicht veraltet sein, was auch immer das bedeutet.

@ est31 Sie machen im ? ist normalerweise streng sauberer (wieder Modulo-Inferenz). Können Sie Beispiele nennen?

@eddyb foo()?.bar()?.baz() ist schöner als try!(try!(foo()).bar()).baz() und try!(bar(try!(foo()))) ist schöner als bar(foo()?)?

Ich finde ? in beiden Fällen besser lesbar. Es reduziert die unnötige Unordnung in Klammern.

Wann wird dieses Land auf Stall landen?

@ofek das ist für die ganze Sache, die noch nicht vollständig ist, also ist es schwer zu sagen. https://github.com/rust-lang/rust/pull/36995 stabilisierte die grundlegende Syntax ? , die in 1.14 stabil sein sollte.

Vergessen Sie nicht, das hinzuzufügen? Betreiber auf die Referenz jetzt, da es stabil ist: https://doc.rust-lang.org/nightly/reference.html#unary -operator-Ausdrücke

Und @bluss wies darauf hin, dass das Buch ebenfalls veraltet ist: https://doc.rust-lang.org/nightly/book/syntax-index.html

@ Tomaka

Ich bin gegen eine Verlängerung? zu Optionen.

Es muss nicht für Fehlerzwecke verwendet werden. Wenn ich zum Beispiel eine Wrapper-Methode verwende, um get und sie durch eine Funktion zuzuordnen, möchte ich in der Lage sein, den Fall None .

? wurde als nur für Fehler aufgeschlagen; Die allgemeinere Notation für eine solche allgemeine Verbreitung war kein Ziel.

Am 29. Oktober 2016, 11:08 -0400, schrieb ticki [email protected] :

@tomaka (https://github.com/tomaka)

Ich bin gegen eine Verlängerung? zu Optionen.

Es muss nicht für Fehlerzwecke verwendet werden. Wenn ich zum Beispiel eine Wrapper-Methode verwende, um get zu übernehmen und sie durch eine Funktion zuzuordnen, möchte ich in der Lage sein, den Fall None weiter zu verbreiten.

- -
Sie erhalten dies, weil Sie erwähnt wurden.
Antworten Sie direkt auf diese E-Mail, zeigen Sie sie auf GitHub (https://github.com/rust-lang/rust/issues/31436#issuecomment-257096575) an oder schalten Sie den Thread stumm (https://github.com/notifications/unsubscribe) -auth / AABsipGIpTF1-7enk-z_5JRYYtl46FLPks5q42DCgaJpZM4HUm_-).

Dies (= ? selbst) ist jetzt eine stabile Funktion! (Aus Rost 1.13)

Zwei Dokumentationsprobleme:

  • [x] Aktualisieren Sie das Kapitel zur Fehlerbehandlung im Buch Nr. 37750
  • [x] Aktualisieren Sie Carrier Trait-Dokumente für die aktuelle Situation # 37751

Beachten Sie, dass weder catch noch das Merkmal Carrier ordnungsgemäß implementiert wurden, sondern nur die bloße Funktion ? .

Carrier vorhanden, und bei Verwendung von ? wird auf Fehlermeldungen verwiesen. Daher ist es besser, wenn das Carrier-Problem behoben und nicht abgelehnt wurde. Die Dokumente müssen ebenfalls aktualisiert werden, da sich die Dokumente darauf beziehen, dass sie für Option implementiert werden. (Was falsch ist).

35946 sollte jede Erwähnung von Carrier aus den Fehlermeldungen entfernen. Es hört sich so an, als sollten wir zumindest die Erwähnung von Option aus den Carrier -Dokumenten entfernen.

Ich füge T-libs für dieses Problem aufgrund der Wechselwirkung mit dem Carrier Charakterzug

Hallo; In # 31954 wird der Upcast für den Fehlertyp mit From (und ähnlich wie im aktuellen Kopf ). In RFC 243 wird jedoch eindeutig angegeben, dass Into für die Konvertierung verwendet werden soll.

Gibt es einen Grund, warum stattdessen From verwendet wurde? Beim Versuch, auf einen generischen Fehlertyp (z. B. io::Error oder etwas anderes in einer externen Kiste) hochzuspielen, kann From für lokale Fehlertypen nicht implementiert werden.

(FWIW als Autor von RFC 243 Ich habe nicht sehr genau darüber nachgedacht, ob From oder Into vorzuziehen ist und möglicherweise die richtige Wahl getroffen hat oder nicht. Das soll nur heißen Die Frage sollte auf der Grundlage der Vorzüge (die an dieser Stelle möglicherweise die Abwärtskompatibilität beinhalten) und nicht der Angaben im RFC entschieden werden.

Leider würde es eine Regression geben. Wenn keine (nicht triviale) From<...> -Instanz für einen Fehlertyp implementiert ist, kann der Compiler den Typ in bestimmten Fällen ableiten, wenn er ihn bei Verwendung von Into (der Menge von From nicht ableiten kann Into Instanzen ist beim Kompilieren einer Kiste nicht bekannt.

Siehe https://play.rust-lang.org/?gist=6d3ee9f93c8b40094a80d3481b12dd00 ("vereinfacht" aufgrund eines realen Problems mit fmt::Error in src / librustc / util / ppaux.rs # L81 )

Es gibt einen neuen RFC, der den alten in Bezug auf die Beschreibung von ? ersetzen wird: rust-lang / rfcs / pull / 1859

Ich bin -1, wenn es darum geht, catch -Blöcke überhaupt zu haben, wenn sie bereits viel präziser mit den Schließungen sind.

Verschlüsse stören die Anweisungen zur Flusskontrolle. ( break , continue , return )

Nachdem https://github.com/rust-lang/rfcs/pull/1859 zusammengeführt wurde und behauptet, dass wir dieses Problem als Tracking-Problem wiederverwenden, möchte ich vorschlagen, dass wir die catch neu bewerten

Ich schlage nicht vor, dass wir es sofort loswerden, aber die Begeisterung für catch war nie so groß wie für ? und ich denke, es lohnt sich sicherzustellen, dass es im Licht immer noch Sinn macht der Redewendungen, die für ? entstanden sind und die voraussichtlich im Lichte von Try auftauchen werden.

Ich bin immer noch gespannt auf den Fang; Ich musste heute früher einen Code auf eine Weise verzerren, die hätte vermieden werden können, wenn der Fang stabil gewesen wäre.

Die Funktion wurde jeden Abend mit der Syntax do catch implementiert, nicht wahr?

@withoutboats Ja, es ist derzeit do catch , da catch { ... } mit Strukturliteralen ( struct catch { }; catch { } ) in Konflikt steht.
.
@archshift Wie @SimonSapin oben ausgeführt hat, beeinträchtigen Schließungen break , continue und return .

@bstrie Ich finde, dass ich catch will; Beim Refactoring kommt es häufig vor.

Mir war nicht klar, dass wir do catch als Syntax benötigen wollten. Angesichts der Tatsache, dass das Risiko eines Bruches in der realen Welt äußerst gering zu sein scheint (beide verstoßen gegen die Richtlinien zur Benennung von Strukturen und müssten den Konstruktor als ersten Ausdruck in der Anweisung haben (was außerhalb der Rückgabeposition selten vorkommt)), könnten wir vielleicht rustfmt nutzen umschreibende Bezeichner in catch_ umschreiben? Wenn es dafür Rust 2.0 erfordert, dann habe ich immer gesagt, dass Rust 2.0 sowieso nur triviale Änderungen enthalten sollte ...: P.

@bstrie Wir wollen auf keinen Fall langfristig do catch benötigen. Die Diskussion, die zur Verwendung dieser Syntax geführt hat, ist hier: https://github.com/rust-lang/rust/pull/39921

Hervorragend, danke für den Kontext.

Ich bin gerade hierher gekommen, weil ich gehofft hatte, dass catch stabil ist und lernen musste, dass dies nicht der Fall ist - also ja, absolut, jetzt, wo ? stabil ist, wäre es großartig, auch catch zu haben

Ich wollte sehen, wie weit wir mit dem Rest gekommen waren, und sah, wie die Diskussion aufkam.

Ich habe über eine wahrscheinlich dumme Idee nachgedacht, die in solchen Fällen helfen könnte: Erlaube optional das Präfixieren von Schlüsselwörtern mit @ oder einem anderen Siegel, und lasse dann alle neuen Schlüsselwörter nur das Siegel verwenden. Wir hatten auch ein ähnliches Problem mit der Coroutine-Diskussion. Ich kann mich nicht erinnern, ob ich dort als Lösung darauf eingegangen bin - ich hätte es vielleicht getan -, aber es sieht so aus, als würde dies immer wieder auftauchen.

FWIW, C # unterstützt tatsächlich das Gegenteil: @ für die Verwendung von Schlüsselwörtern als Bezeichner. Dies ist häufig in Razor der Fall, wo Sie Dinge wie new { <strong i="6">@class</strong> = "errorbox" } , um Eigenschaften für HTML-Knoten festzulegen.

@scottmcm
Das ist interessant. Ich wusste es nicht. Aber für Rust müssen wir wegen der Kompatibilität den anderen Weg gehen.

Eine gute Idee für sie. Das .net-Ökosystem verfügt über viele Sprachen, die alle unterschiedliche Schlüsselwörter haben und sich gegenseitig anrufen können.

Eine andere Möglichkeit für die Zukunft von Schlüsselwörtern: Setzen Sie sie hinter ein Attribut, wie eine Art stabiles #[feature] .

Ich denke, dieses Problem wird auf lange Sicht eine allgemeinere Lösung benötigen.

@camlorn Dieser Thread könnte Sie interessieren, insbesondere Aarons Idee zu Rust "Epochen": https://internals.rust-lang.org/t/pre-rfc-stable-features-for-breaking-changes/5002

Ich denke, wir sollten https://github.com/rust-lang/rust/issues/42327 aus der Problembeschreibung hier verlinken. (Vielleicht sollte auch der RFC-Text aktualisiert werden, um dort statt hier zu verlinken.)

(BEARBEITEN: Ich habe dort einen Kommentar gepostet, nicht sicher, wer ihn bereits abonniert hat oder nicht!)

@camlorn Es könnte jedoch den Pfad "rustfmt führt eine triviale Neufassung für eine Weile durch, dann wird es zu einem Schlüsselwort" öffnen. Die AKA nutzt die Notluke "Es ist nicht kaputt, wenn es eine zusätzliche Anmerkung gibt, die hätte geschrieben werden können, damit es in beiden Fällen funktioniert". Und ähnlich wie bei UFCS für Inferenzänderungen könnte ein hypothetisches Modell "In vollständig ausgearbeiteter Form speichern" alte Kisten am Laufen halten.

Es würde mir nichts ausmachen, wenn die Syntax für den Catch-Block nur do { … } oder sogar ?{ … }

do hat die nette Eigenschaft, bereits ein Schlüsselwort zu sein. Es hat die zweifelhafte (abhängig von Ihrer Perspektive) Eigenschaft, Haskell-ähnliche do -Notation aufzurufen, obwohl dies seine früheren Verwendungen nie gestoppt hat, und diese ist im Anwendungsfall etwas näher.

es sieht auch so aus, verhält sich aber anders als die vorgeschlagenen Javascripts do-Ausdrücke

Dieser Vorschlag ist für Rust, der bereits nackte Blöcke als Ausdrücke verwendet, nicht wirklich relevant.

Ich habe nur auf die mögliche Verwirrung hingewiesen, da beide gleich aussehen würden, aber völlig unterschiedliche Dinge tun würden.

Es hat die zweifelhafte (abhängig von Ihrer Perspektive) Eigenschaft, die Haskell-ähnliche Do-Notation aufzurufen

@rpjohnst Ergebnis und Option sind Monaden, daher ist es zumindest konzeptionell ähnlich. Es sollte auch vorwärtskompatibel sein, um es in Zukunft zu erweitern und alle Monaden zu unterstützen.

Richtig, in der Vergangenheit war der Einwand, dass wenn wir do für etwas hinzufügen, das konzeptionell Monaden ähnelt, aber ohne sie vollständig zu unterstützen, dies die Menschen traurig machen würde.

Während wir es wahrscheinlich mit der vollständigen Do-Notation vorwärtskompatibel machen könnten, sollten wir wahrscheinlich nicht die vollständige Do-Notation hinzufügen. Es ist nicht zusammensetzbar mit Kontrollstrukturen wie if / while / for / loop oder break / continue / return , die wir innerhalb und zwischen catch (oder in diesem Fall do ) Blöcken verwenden können müssen. (Dies liegt daran, dass die vollständige Do-Notation in Bezug auf Funktionen höherer Ordnung definiert ist. Wenn wir den Inhalt eines catch -Blocks in eine Reihe verschachtelter Abschlüsse einfügen, steuern Sie plötzlich den Fluss aller Unterbrechungen.)

Also am Ende dieses Nachteil von do ist , dass es wie do-Notation aussieht , ohne tatsächlich do-Notation zu sein, und ohne einen guten Weg in der Zukunft zu werden do-Notation entweder. Persönlich bin ich damit völlig einverstanden, weil Rust sowieso keine do -Notation bekommt - aber das ist die Verwirrung.

@nikomatsakis , # 42526 wird zusammengeführt, Sie können es als erledigt in der Tracking-Liste markieren :)

Ist es möglich, das Schlüsselwort catch kontextbezogen zu machen, es jedoch zu deaktivieren, wenn ein struct catch im Gültigkeitsbereich ist, und eine Verfallswarnung auszugeben?

Ich bin mir nicht sicher, wie angemessen dies ist, aber ich bin auf ein Problem gestoßen, das möglicherweise dadurch behoben werden muss, dass Sie manchmal einen OK-Wert anstelle eines Fehlerwerts abbrechen möchten, der derzeit möglich ist, wenn Sie return sofern Sie dies häufig möchten Brechen Sie einen _inner_ Fehler durch ein _outer_ OK ab. Zum Beispiel, wenn eine Funktion say Option<Result<_,_>> zurückgibt, was

Insbesondere habe ich ein Makro in einem Projekt, das ich gerade ausgiebig benutze:

macro-rules! option_try {
    ( $expr:expr ) => {
        match $expr {
            Ok(x)  => x,
            Err(e) => return Some(Err(e.into())),
        }
    }
}

Es kommt sehr häufig vor, dass eine Funktion, die innerhalb einer Iterator::next -Implementierung aufgerufen wird, bei einem Fehler sofort mit Some(Err(e)) abgebrochen werden muss. Dieses Makro funktioniert innerhalb eines normalen Funktionskörpers, jedoch nicht innerhalb eines catch-Blocks, da catch nicht kategorisch return erfasst, sondern nur die spezielle Syntax ? .

Obwohl beschriftete Retouren am Ende die gesamte catch überflüssig machen würden, nicht wahr?

Es sieht so aus, als wäre # 41414 fertig. Könnte jemand das OP aktualisieren?

Update: RFC rust-lang / rfcs # 2388 ist jetzt zusammengeführt und daher muss catch { .. } durch try { .. } .
Siehe das Tracking-Problem direkt über diesem Kommentar.

Was bedeutet dies für die Edition 2015, befindet sich die Syntax do catch { .. } noch auf dem Weg zur Stabilisierung, oder wird diese gelöscht und nur über try { .. } in Edition 2018+ unterstützt?

@ Nemo157 Letzteres.

Gibt es im aktuellen Vorschlag eine try ... catch ... zwei Anweisungen? Wenn ja, verstehe ich die Semantik nicht.

Wie auch immer, wenn es bei diesem Vorschlag nur um Desugaring geht, bin ich cool damit. dh Ändert sich ein catch -Block nur dort, wo der Operator ? endet?

Wann würden wir vorwärts gehen, da alle Kontrollkästchen im oberen Beitrag aktiviert sind? Wenn es immer noch ungelöste Probleme gibt, müssen neue Kontrollkästchen hinzugefügt werden.

Es gibt wahrscheinlich noch viele ungelöste Fragen, die nicht aufgezeichnet wurden.
Zum Beispiel ist das OK-Wrapping-Verhalten nicht im lang-Team festgelegt, das Design des Try ist nicht abgeschlossen und so weiter. Wir sollten dieses Problem wahrscheinlich in mehrere gezieltere aufteilen, da es wahrscheinlich seine Nützlichkeit überlebt hat.

Hmm ... es stört mich, dass diese Fragen nicht aufgezeichnet wurden.

@ mark-im Um das zu verdeutlichen, denke ich, dass sie irgendwo gewesen sind; aber nicht an einem Ort; Es ist ein bisschen verstreut in verschiedenen RFCs und Problemen und so weiter. Wir müssen sie also an den richtigen Stellen aufzeichnen.

Das Design des Hintergrundmerkmals wird unter https://github.com/rust-lang/rust/issues/42327 verfolgt. Dort wird ausführlich über Schwachstellen in der aktuellen und eine mögliche neue Richtung diskutiert. (Ich plane, dort eine Vor-RFC für eine Änderung vorzunehmen, sobald sich 2018 ein wenig beruhigt hat.)

Ich denke also, dass hier nur noch try{} übrig ist, und die einzige Meinungsverschiedenheit, die ich dafür kenne, sind Dinge, die im RFC geregelt und in einem der oben genannten Probleme erneut bestätigt wurden. Es könnte jedoch immer noch gut sein, ein eindeutiges Tracking-Problem zu haben.

Ich werde ein Kontrollkästchen für die eine ausstehende Implementierungsaufgabe hinzufügen, von der ich weiß, dass sie noch erledigt werden muss ...

@scottmcm Ich weiß, dass @joshtriplett Bedenken hinsichtlich des OK-Wrappings hatte (vermerkt im try RFC) und ich persönlich möchte break bei der anfänglichen Stabilisierung von try { .. } so einschränken dass du nicht loop { try { break } } und so machen kannst.

@Centril

so dass Sie nicht loop { try { break } } tun können

Im Moment können Sie break in einem Nicht-Loop-Block verwenden, und es ist richtig: break sollte nur in Loops verwendet werden. Um einen try Block frühzeitig zu verlassen, besteht die Standardmethode darin, Err(e)? zu schreiben. und es erzwingt, dass sich frühe Blätter immer im "abnormalen" Kontrollpfad befinden.

Mein Vorschlag ist also, dass der von Ihnen gezeigte Code zulässig sein sollte und die loop brechen sollte, nicht nur die try .

Der unmittelbare Vorteil ist, wenn Sie break , wissen Sie, dass es aus einer Schleife herausbricht, und Sie können es jederzeit durch ein continue ersetzen. Außerdem müssen Sie den Haltepunkt nicht mehr kennzeichnen, wenn Sie try -Blöcke in einem loop und die Schleife verlassen möchten.

@Centril Danke, dass du diese angehoben hast.

In Bezug auf break würde ich persönlich gut sagen können, dass try break egal ist und in die enthaltende Schleife übergeht. Ich möchte nur nicht, dass break mit try interagiert.

Was das Ok -Verpacken betrifft, ja, ich möchte das ansprechen, bevor ich try stabilisiere.

@centril Ja, ich bin mir bewusst. Aber es ist wichtig sich daran zu erinnern , dass das ist Wieder- Wieder- das Thema zu erhöhen. Der RFC entschied sich dafür , es wurde ohne es implementiert, aber dann wurde die ursprüngliche Absicht wieder übernommen , und die Implementierung wurde geändert, um dem RFC zu folgen. Meine große Frage ist also, ob sich wesentliche Fakten geändert haben, insbesondere angesichts der Tatsache, dass dies eines der lautesten Themen ist, die ich je auf RFCs + IRLO diskutiert habe.

@scottmcm Wie Sie wissen, bin ich natürlich damit einverstanden, Ok -wrapping beizubehalten;) und ich stimme zu, dass das Problem als gelöst betrachtet werden sollte.

Ich wollte dies nur kommentieren, nicht sicher, ob dies das Richtige ist:

Im Wesentlichen ist eine Situation, die ich habe, Rückrufe in einem GUI-Framework - anstatt ein Option oder Result , müssen sie ein UpdateScreen , um dem Framework mitzuteilen, ob der Bildschirm angezeigt wird muss aktualisiert werden oder nicht. Oft brauche ich überhaupt keine Protokollierung (es ist einfach nicht praktisch, jeden kleinen Fehler anzumelden) und gebe einfach ein UpdateScreen::DontRedraw wenn ein Fehler aufgetreten ist. Mit dem aktuellen Operator ? muss ich dies jedoch die ganze Zeit schreiben:

let thing = match fs::read(path) {
    Ok(o) => o,
    Err(_) => return UpdateScreen::DontRedraw,
};

Da ich über den Try-Operator nicht von einem Result::Err in ein UpdateScreen::DontRedraw konvertieren kann, wird dies sehr mühsam - oft habe ich einfache Suchvorgänge in Hash-Maps, die fehlschlagen können (was kein Fehler ist) ) - so oft in einem Rückruf habe ich 5 - 10 Verwendungen des Operators ? . Da das oben Gesagte sehr ausführlich zu schreiben ist, besteht meine aktuelle Lösung darin, impl From<Result<T>> for UpdateScreen wie folgt zu verwenden und dann eine innere Funktion im Rückruf wie folgt zu verwenden:

fn callback(data: &mut State) -> UpdateScreen {
     fn callback_inner(data: &mut State) -> Option<()> {
         let file_contents = fs::read_to_string(data.path).ok()?;
         data.loaded_file = Some(file_contents);
         Some(())
     }

    callback_inner(data).into()
}

Da der Rückruf als Funktionszeiger verwendet wird, kann ich kein -> impl Into<UpdateScreen> (aus irgendeinem Grund ist die Rückgabe eines impl für Funktionszeiger derzeit nicht zulässig). Die einzige Möglichkeit für mich, den Operator Try überhaupt zu verwenden, besteht darin, den Trick der inneren Funktion auszuführen. Es wäre schön, wenn ich einfach so etwas machen könnte:

impl<T> Try<Result<T>> for UpdateScreen {
    fn try(original: Result<T>) -> Try<T, UpdateScreen> {
        match original {
             Ok(o) => Try::DontReturn(o),
             Err(_) => Try::Return(UpdateScreen::DontRedraw),
        }
    }
}

fn callback(data: &mut State) -> UpdateScreen {
     // On any Result::Err, convert to an UpdateScreeen::DontRedraw and return
     let file_contents = fs::read_to_string(data.path)?;
     data.loaded_file = Some(file_contents);
     UpdateScreen::Redraw
}

Ich bin mir nicht sicher, ob dies mit dem aktuellen Vorschlag möglich wäre, und wollte nur meinen Anwendungsfall zur Prüfung hinzufügen. Es wäre großartig, wenn ein benutzerdefinierter Try-Operator so etwas unterstützen könnte.

BEARBEITEN:
Ich machte einen Fehler.


Ignoriere diesen Beitrag


Könnte dies besser mit Typinferenz spielen, schlägt es selbst in einfachen Fällen fehl.

fn test_try(a: u32, b: u32) {
    let div = if b != 0 {
        Some(a / b)
    } else {
        None
    };

    let x // : Option<_> // why is this type annotation necessary
    = try { div? + 1 };

    println!("{:?}", x);
}

Wenn dies neu geschrieben wird, um einen Abschluss anstelle des try-Blocks zu verwenden (und dabei das automatische Umbrechen zu verlieren), erhalten wir

fn test_closure(a: u32, b: u32) {
    let div = if b != 0 {
        Some(a / b)
    } else {
        None
    };

    let x =  (|| (div? + 1).into())();

    println!("{:?}", x);
}

Dafür ist keine Typanmerkung erforderlich, aber wir müssen das Ergebnis umbrechen.

Spielplatz

@KrishnaSannasi Ihr Spielplatz ), da Into die Ausgabe nicht einschränkt und Sie sie nirgendwo verwenden, wo dies später der Fall ist.

Dies scheint hauptsächlich ein Problem mit dem Merkmal Try und nicht try Blöcken Into keine Typinformationen von den Eingaben an die Ausgabe weitergegeben Der Ausgabetyp muss durch seine spätere Verwendung bestimmbar sein. Unter https://github.com/rust-lang/rust/issues/42327 gibt es eine Menge Diskussionen über das Merkmal. Ich habe es nicht gelesen, daher bin ich mir nicht sicher, ob einer der Vorschläge dort dieses Problem beheben könnte.

@ Nemo157

Ja, ich habe in letzter Minute Änderungen an meinem Code vorgenommen, um ihn zu verwenden, und das nicht getestet. Mein Fehler.

Wie weit sind wir von der Stabilisierung der Versuchsblöcke entfernt? Es ist die einzige Funktion, die ich abends brauche: D.

@Arignir

Ich glaube, wenn dies einmal getan ist, kann es stabilisiert werden.

Blockieren Sie try {} catch (oder andere folgende IDents), um den Designbereich für die Zukunft offen zu lassen, und weisen Sie die Benutzer darauf hin, wie sie stattdessen mit Match das tun können, was sie wollen

Gibt es kein Mittelweg-Design, um das Feature jetzt zuzulassen und gleichzeitig die Möglichkeit zu lassen, Designraum für die Zukunft offen zu lassen (und daher einen eventuellen catch -Block)?

Die PR, die ich gemacht habe, sollte dieses Kästchen sowieso abhaken, CC @nikomatsakis

Ich habe gestern zum ersten Mal versucht, dies zu verwenden, und ich war ein wenig überrascht, dass dies:

#![feature(try_blocks)]
fn main() -> Result<(), ()> {
    let x: () = try {
        Err(())?
    }?;
    Ok(x)
}

kompiliert nicht wegen

error[E0284]: type annotations required: cannot resolve `<_ as std::ops::Try>::Ok == _`

Vielmehr musste ich tun

#![feature(try_blocks)]
fn main() -> Result<(), ()> {
    let x: Result<(), ()> = try {
        Err(())?
    };
    let x = x?;
    Ok(x)
}

stattdessen.

Das war anfangs verwirrend, also lohnt es sich vielleicht, die Fehlermeldung zu ändern oder in --explain erwähnen?

Wenn Sie das Fragezeichen in Ihrem ersten Beispiel etwas nach unten verschieben

#![feature(try_blocks)]
fn main() -> Result<(), ()> {
    let x: () = try {
        Err(())?
    };
    Ok(x?)
}

Sie erhalten eine bessere Fehlermeldung. Der Fehler tritt auf, weil Rust aufgrund seiner Allgemeinheit nicht entscheiden kann, in welchen Typ das try { ... } soll. Da dieser Typ nicht aufgelöst werden kann, kann er den Typ <_ as Try>::Ok nicht kennen, weshalb Sie den Fehler erhalten haben, den Sie gemacht haben. (weil der ? Bediener die auspackt Try Art und gibt den Rücken Try::Ok Typen). Rust kann nicht alleine mit dem Typ Try::Ok Er muss durch das Merkmal Try und den Typ, der dieses Merkmal implementiert, aufgelöst werden. (Dies ist eine Einschränkung der aktuellen Funktionsweise der Typprüfung.)

Alles für diese Funktion ist implementiert, richtig? Wenn ja, wie lange wollen wir uns darauf konzentrieren, bevor wir uns stabilisieren?

Ich dachte, es sei immer noch eine offene Frage, ob wir das wollten oder nicht. Insbesondere wurde diskutiert, ob wir hier die Sprache der Ausnahmen verwenden wollen (try, catch).

Persönlich bin ich stark dagegen, den Eindruck zu erwecken, dass Rust so etwas wie Ausnahmen hat. Ich denke, die Verwendung des Wortes catch ist eine schlechte Idee, da jeder, der aus einer Sprache mit Ausnahmen kommt, davon ausgeht, dass sich dies abwickelt, und dies nicht. Ich würde erwarten, dass es verwirrend und schmerzhaft ist, zu unterrichten.

Insbesondere wurde diskutiert, ob wir hier die Sprache der Ausnahmen verwenden wollen (try, catch).

Ich denke, https://github.com/rust-lang/rfcs/pull/2388 hat definitiv entschieden, ob try als Name akzeptabel ist. Dies ist keine offene Frage. Aber die Definition des Merkmals Try sowie der Umhüllung von Ok scheint zu sein.

Ok -Wrapping wurde bereits im ursprünglichen RFC festgelegt, dann während der Implementierung entfernt und später erneut hinzugefügt. Ich sehe nicht, wie es eine offene Frage ist.

@rpjohnst Nun, es liegt daran, dass Josh mit der Entscheidung mit dem ursprünglichen RFC nicht einverstanden ist ... :) Es ist eine geklärte Angelegenheit für mich . Siehe https://github.com/rust-lang/rust/issues/31436#issuecomment -427096703, https://github.com/rust-lang/rust/issues/31436#issuecomment -427252202 und https: // github.com/rust-lang/rust/issues/31436#issuecomment -437129491. Wie auch immer ... der Punkt meines Kommentars war, dass try als "Sprache der Ausnahmen" eine geklärte Angelegenheit ist.

Woah, wann ist das passiert? Das Letzte, woran ich mich erinnere, waren Diskussionen über Interna. Ich bin auch sehr gegen Ok-Wrapping :(

Eww. Ich kann nicht glauben, dass das passiert ist. Ok -Wrapping ist so schrecklich (es bricht die sehr vernünftige Intuition, dass alle Rückgabeausdrücke in einer Funktion vom Rückgabetyp der Funktion sein sollten). Also ja, definitiv mit @ mark-im. Reicht Joshs Uneinigkeit aus, um dieses Thema offen zu halten und mehr Diskussionen darüber zu führen? Ich würde ihn gerne dabei unterstützen, nicht dass es als Nicht-Teammitglied etwas bedeutet.

Ok -Wrapping, wie in RFC 243 akzeptiert (buchstäblich dasjenige, das den Operator ? , wenn Sie sich gefragt haben, wann dies passiert ist), ändert nichts an den Typen der Funktionsrückgabeausdrücke. So hat es RFC 243 definiert: https://github.com/rust-lang/rfcs/blob/master/text/0243-trait-based-exception-handling.md#catch -expressions

Dieser RFC führt auch eine Ausdrucksform catch {..} , die dazu dient, den Operator ? zu "erfassen". Der Operator catch führt den zugehörigen Block aus. Wenn keine Ausnahme ausgelöst wird, lautet das Ergebnis Ok(v) wobei v der Wert des Blocks ist. Wenn andernfalls eine Ausnahme ausgelöst wird, lautet das Ergebnis Err(e) .

Beachten Sie, dass catch { foo()? } im Wesentlichen foo() .

Das heißt, es wird ein Block vom Typ T und bedingungslos umbrochen, um einen Wert vom Typ Result<T, _> zu erzeugen. Jede return -Anweisung im Block bleibt völlig unberührt. Wenn der Block der Endausdruck einer Funktion ist, muss die Funktion ein Result<T, _> .

Es wird seit Ewigkeiten auf diese Weise jeden Abend implementiert: https://play.rust-lang.org/?version=nightly&mode=debug&edition=2018&gist=88379a1607d952d4eae1d06394b50959. Dies wurde nach viel Diskussion durch die lang - Team getan in und verknüpft aus, dieses Thema anschauen : Rost-lang / Rost # 41414 (und dies ist auch an der Spitze dieser Frage verbunden sind ).

Am 28. Mai 2019, 17:48:27 Uhr PDT, schrieb Alexander Regueiro [email protected] :

Eww. Ich kann nicht glauben, dass das passiert ist. Ok -Verpackung ist so schrecklich (es
bricht die sehr vernünftige Intuition, dass alle Ausdrücke in a zurückgeben
Die Funktion sollte vom Rückgabetyp der Funktion sein. Also auf jeden Fall
mit @ mark-im dazu. Ist Joshs Uneinigkeit genug, um dies aufrechtzuerhalten?
offenes Thema und mehr Diskussion darüber? Ich würde ihn gerne unterstützen
im Kampf dagegen nicht, dass es als Nicht-Teammitglied etwas bedeutet.

Vielen Dank. Ich bin damit nicht nur für mich selbst nicht einverstanden. Ich vertrete auch die zahlreichen Menschen, die ich gesehen habe, um die gleiche Position auszudrücken, die ich einnehme.

@joshtriplett @ mark-im @alexreg

Kann einer von Ihnen erklären, warum Sie Ok als so unangenehm empfinden, oder einen Link zu einem Ort bereitstellen, der zuvor erklärt wurde? Ich ging suchen, aber in einer flüchtigen Ansicht sah ich nichts. Ich habe kein Pferd darin (ich habe dies nur wörtlich kommentiert, weil ich einen Monat lang alle Kästchen angekreuzt und keine Diskussion gesehen habe), aber jetzt, wo ich das Hornissennest getreten habe, möchte ich die Argumente besser verstehen.

Am Dienstag, den 28. Mai 2019 um 15:40:47 Uhr schrieb Russell Johnston:

Ok -Wrapping wurde bereits im ursprünglichen RFC festgelegt, dann während der Implementierung entfernt und später erneut hinzugefügt. Ich sehe nicht, wie es eine offene Frage ist.

Ich denke, Sie haben Ihre eigene Frage teilweise beantwortet. Ich denke nicht jeder
Die Beteiligung an der ursprünglichen RFC-Diskussion befand sich auf derselben Seite. try war
absolut etwas, was viele Leute wollten, aber es gab keinen Konsens
zum Ok-Wrapping.

Am Dienstag, den 28. Mai 2019 um 15:44:46 Uhr -0700 schrieb Mazdak Farrokhzad:

Wie auch immer ... der Punkt meines Kommentars war, dass try als "Sprache der Ausnahmen" eine geklärte Angelegenheit ist.

Zur Verdeutlichung finde ich die Metapher von "Ausnahmen" nicht ansprechend,
und viele der Versuche, Dinge wie try-fn und Ok-wrapping zu versuchen, scheinen zu sein
Versuchen Sie, die Sprache mit einem ausnahmeähnlichen Mechanismus zu fälschen.
Aber try selbst, um ? an etwas anderem als dem zu fangen
Funktionsgrenze, macht als Kontrollflusskonstrukt Sinn.

Am Dienstag, den 28. Mai 2019 um 23:37:33 Uhr -0700 schrieb Gabriel Smith:

Kann einer von Ihnen erklären, warum Sie Ok als so unangenehm empfinden?

Als einer von wenigen Gründen:

Am Dienstag, den 28. Mai 2019 um 17:48:27 Uhr -0700 schrieb Alexander Regueiro:

es bricht die sehr vernünftige Intuition, dass alle Rückgabeausdrücke in einer Funktion vom Rückgabetyp der Funktion sein sollten

Dies unterbricht verschiedene Ansätze, die Menschen für typgesteuertes Denken verwenden
über Funktionen und Codestruktur.

Ich habe hier sicherlich meine eigenen Gedanken, aber könnten wir dieses Thema jetzt bitte nicht noch einmal öffnen? Wir hatten gerade ein umstrittenes Gespräch über 500 Post über Syntax, daher möchte ich Landminen für eine Weile vermeiden.

Wenn dies im Lang-Team blockiert ist, das dies diskutiert, sollte das Kontrollkästchen "Auflösen, ob Catch-Blöcke den Ergebniswert (# 41414)" umbrechen sollen "erneut deaktiviert werden (möglicherweise mit einem Kommentar, der im Lang-Team blockiert ist), damit die Leute darauf schauen kennt dieses Tracking-Problem den Status?

Entschuldigung, ich versuche nicht, etwas wieder zu öffnen - wiederholen Sie einfach, was im Tracking-Problem als entschieden markiert ist und wann + wie das passiert ist.

@rpjohnst Danke für die Info!

@yodaldevoid Josh hat meine Gedanken ziemlich zusammengefasst.

Ich bin etwas weniger gegen Ok-Wrapping, das auf einen Block beschränkt ist (im Gegensatz zur Beeinflussung des Funktionstyps), aber ich denke, es stellt immer noch einen schlechten Präzedenzfall dar: Wie Josh sagte: „Ich finde die Metapher von Ausnahmen nicht ansprechend.“

@joshtriplett hat im Wesentlichen auch meine Ansichten zusammengefasst: Die Probleme sind die Eignung der "Ausnahme" -Metapher (wohl Panik + catch_unwind ist viel analogen) und typbasiertes Denken. Ich bin in der Tat in Ordnung mit try Blöcken als Scoping & Control-Flow-Mechanismus, aber nicht mit den radikaleren Punkten.

Okay, fair genug, lassen Sie uns nicht die ganze Debatte hier haben ... vielleicht deaktivieren Sie einfach das Kontrollkästchen wie vorgeschlagen und setzen Sie es wieder in die Lang-Team-Debatte (in ihrer eigenen Zeit) ein, wobei Sie einige der in diesem Thread erwähnten Gründe verwenden? Solange die Stabilisierung nicht beschleunigt wird, klingt das wohl vernünftig.

Wurde eine Syntax für Typanmerkungen vereinbart? Ich hatte auf einige try { foo()?; bar()?; }.with_context(|_| failure::err_msg("foon' n' barn'")?; gehofft, die nicht einmal im entferntesten an der Kompilierung interessiert sind: error[E0282]: type annotations needed .

https://play.rust-lang.org/?version=nightly&mode=debug&edition=2018&gist=4e60d44a8f960cf03307a809e1a3b5f2

Ich habe die Kommentare vor einiger Zeit gelesen (und das erneute Laden von 300 Kommentaren auf Github ist viel zu mühsam), aber ich erinnere mich, dass die meisten (wenn nicht alle) Beispiele bezüglich der Debatte um Try::Ok Wrapping Ok im Beispiel. In Anbetracht der Tatsache, dass Option Try Option implementiert, würde ich gerne wissen, wie sich dies auf die Position des Teams auf der Seite der Debatte auswirkt.

Jedes Mal, wenn ich Rust benutze, denke ich: "Mann, ich wünschte wirklich, ich könnte hier einen Try-Block verwenden", aber in etwa 30% der Fälle, weil ich wirklich wünschte, ich könnte Try für Option s verwenden (wie Ich habe es früher in Scala getan, wo die Syntax for für Monaden im Allgemeinen verwendet wurde, aber hier sehr ähnlich zu try ist.

Erst heute habe ich die json -Kiste verwendet und sie enthüllt die as_* -Methoden, die Optionen zurückgeben.

Mit den beiden Syntaxen wäre mein Beispiel gewesen:

match s {
  "^=" => |a, b| try { a.as_str()?.starts_with(b.as_str()?) }.unwrap_or(false),
  "$=" => |a, b| try { Some(a.as_str()?.ends_with(b.as_str()?)) }.unwrap_or(false),
  // original
  "$=" => |a, b| {
    a.as_str()
      .and_then(|a| b.as_str().map(|b| (a, b)))
      .map(|(a, b)| a.starts_with(b))
      .unwrap_or(false)
    },
}

Ich denke, dass es im Kontext ziemlich klar ist, ob der Rückgabetyp Option oder Result ist oder nicht, und außerdem spielt es keine Rolle (was das Codeverständnis betrifft). Transparent ist die Bedeutung klar: "Ich muss überprüfen, ob diese beiden Dinge gültig sind, und sie operieren." Wenn ich eine davon auswählen müsste, würde ich mich für die erste entscheiden, da ich nicht glaube, dass es zu einem Verlust des Verständnisses kommt, wenn man bedenkt, dass diese Funktion in einen größeren Kontext eingebettet ist, wie es try tun wird sei immer.

Als ich anfing, mir diesen Thread anzuschauen, war ich gegen Ok Wrapping, weil ich dachte, es wäre besser, explizit zu sein, aber seitdem habe ich angefangen, auf die Zeiten zu achten, in denen ich sagte: "Ich wünschte, ich könnte hier einen try-Block verwenden "und ich bin zu dem Schluss gekommen, dass Ok -Wrapping gut ist.

Ich dachte ursprünglich, dass nicht Ok Wrapping besser wäre, wenn Ihre letzte Anweisung eine Funktion ist, die den Typ zurückgibt, der Try implementiert, aber der Unterschied in der Syntax wäre

try {
  fallible_fn()
}

try {
  fallible_fn()?
}

Und in diesem Fall denke ich wieder, dass Ok -Verpackung besser ist, weil es klar macht, dass fallible_fn eine Try Rückgabefunktion ist, also ist es tatsächlich expliziter.

Ich möchte wissen, was die Opposition davon hält, und da ich in diesem Thread nicht viele andere sehen kann, @joshtriplett.

EDIT: Ich sollte erwähnen, dass ich dies nur aus der Perspektive der Ergonomie / des Leseverständnisses betrachtet habe. Ich habe keine Ahnung, ob einer mehr technische Vorteile als der andere in Bezug auf die Implementierung hat, wie zum Beispiel eine einfachere Schlussfolgerung.

Ich wollte try für einige verschachtelte Option Parsing ausprobieren:

#![feature(try_blocks)]

struct Config {
    log: Option<LogConfig>,
}

struct LogConfig {
    level: Option<String>,
}

fn example(config: &Config) {
    let x: &str = try { config.log?.level? }.unwrap_or("foo");
}

Dies schlägt fehl mit

error[E0282]: type annotations needed
  --> src/lib.rs:12:19
   |
12 |     let x: &str = try { config.log?.level? }.unwrap_or("foo");
   |                   ^^^^^^^^^^^^^^^^^^^^^^^^^^ cannot infer type
   |
   = note: type must be known at this point

Das nächste, was ich bekam, war

fn example(config: &Config) {
    let x: Option<&str> = try { &**config.log.as_ref()?.level.as_ref()? };
    let x = x.unwrap_or("foo");
}

Die as_ref sind ziemlich unglücklich. Ich weiß, dass Option::deref einigen hier helfen wird, aber nicht genug. Dies fühlt sich so an, als ob eine passende Ergonomie (oder eine verwandte Idee) ins Spiel kommen sollte.

Die mehreren Zeilen sind auch unglücklich.

Könnte try einen Inferenz-Fallback von Result wie ganzzahlige Literale verwenden? Würde das den ersten Versuch von @shepmaster auf Result<&str, NoneError> ? Welche verbleibenden Probleme würde es wahrscheinlich geben, einen häufigen Fehlertyp für ? zu finden, in den konvertiert werden soll? (Habe ich irgendwo die Diskussion darüber verpasst?)

@shepmaster Ich stimme der Typinferenz zu. Lustigerweise habe ich Ihren genauen Code mit einer etwas naiven try_ -Implementierung ausprobiert und es funktioniert einwandfrei: https://github.com/norcalli/koption_macros/blob/4362fba8fa9b6c62fdaef4df30060234381141e7/src/lib.rs#L23

    let x = try_! { config.log?.level? }.unwrap_or("foo".to_owned());
    assert_eq!(x, "debug");

funktioniert gut.

eine etwas naive try_ Implementierung

Ja, aber Ihr Makroaufruf gibt ein String , kein &str , für das Besitz erforderlich ist. Sie zeigen den umgebenden Code nicht an, aber dies schlägt fehl, da wir nicht Eigentümer der Config :

fn example(config: &Config) {
    let x = try_! { config.log?.level? }.unwrap_or_else(|| String::from("foo"));
}
error[E0507]: cannot move out of captured variable in an `Fn` closure
  --> src/lib.rs:20:21
   |
19 | fn example(config: &Config) {
   |            ------ captured outer variable
20 |     let x = try_! { config.log?.level? }.unwrap_or_else(|| String::from("foo"));
   |                     ^^^^^^^^^^ cannot move out of captured variable in an `Fn` closure

Es weist auch bedingungslos ein String ; Ich habe in diesem Beispiel unwrap_or_else , um diese Ineffizienz zu vermeiden.

Es ist eine Schande, dass diese Funktion nicht vor async/await Blöcken stabilisiert wurde. IMO wäre es konsequenter gewesen

let fut = async try {
    fut1().await?;
    fut2().await?;
    Ok(())
};

anstatt zuzulassen, dass es ohne try funktioniert. Aber ich nehme an, das Schiff ist längst gesegelt.

Betreff: Auto-Wrapping, ich glaube nicht, dass es jetzt möglich sein wird, mit async Blöcken konsistent zu sein. async Blöcke führen eine Art "Auto-Wrapping" in einen anonymen Typ durch, der Future implementiert. Dies gilt jedoch auch für frühe Renditen, die mit try Blöcken nicht möglich wären.

Es könnte doppelt verwirrend werden, wenn wir jemals einen hypothetischen async try Block haben. Sollte das Auto-wickeln Sie das Ergebnis?

Ich glaube nicht, dass wir Chancen auf Konsistenz im Sinne der automatischen Umhüllung verloren haben. Beide async -Blöcke und -Funktionen können ? , und beide müssen ihre eigene manuelle Ok -Verpackung durchführen, sowohl für die frühe als auch für die endgültige Rückgabe.

Ein try -Block könnte andererseits ? mit automatischem Ok -Wrapping verwenden, auch für "frühe Rückgaben" unter der Annahme einer Funktion für frühe Rückgaben - möglicherweise Label-Break- Wert . Eine hypothetische Try- Funktion könnte leicht eine automatische Ok -Verpackung sowohl für frühe als auch für endgültige Renditen durchführen.

Ein hypothetischer async try -Block könnte einfach die Funktionalität des Zwei-Auto- Ok -Wraps und dann des Auto- Future -Wraps kombinieren. (Der umgekehrte Weg ist unmöglich zu implementieren und würde wohl sowieso try async werden.)

Die Inkonsistenz, die ich sehe, ist, dass wir async Blöcke mit Funktionen zusammengeführt haben. (Dies geschah in letzter Minute im Gegensatz zum RFC, nicht weniger.) Dies bedeutet, dass return in async Blöcken den Block verlassen, während return in try -Blöcke verlassen die enthaltende Funktion. Diese sind jedoch zumindest isoliert sinnvoll, und async -Blöcke ohne Early-Return- oder Label-Break-Wert wären viel schwieriger zu verwenden.

Hat irgendetwas die Stabilisierung gestoppt, oder hat sich noch niemand die Zeit dafür genommen? Ich bin ansonsten daran interessiert, die notwendigen PRs zu erstellen 🙂

Am 18. November 2019, 02:03:36 Uhr PST, schrieb Kampfkarren [email protected] :

Alles, was die Stabilisierung davon aufhält, oder einfach niemand hat das genommen
Zeit es noch zu tun? Ich bin daran interessiert, die notwendigen PRs zu erstellen
sonst 🙂>
>
->
Sie erhalten dies, weil Sie erwähnt wurden.>
Antworte direkt auf diese E-Mail oder sieh sie dir auf GitHub an:>
https://github.com/rust-lang/rust/issues/31436#issuecomment -554944079

Ja, der Blocker für die Stabilisierung arbeitet an der Entscheidung über das Ok-Wrapping. Dies sollte nicht stabilisiert werden, bis wir uns einig sind, wie es sich verhalten soll.

Ich persönlich bin gegen Ok-Wrapping, aber ich frage mich, wie schwer es wäre, nachträglich etwas hinzuzufügen. Ist No-Ok-Wrapping Forward mit Ok-Wrapping kompatibel?

Ich kann mir knifflige Fälle wie die Mehrdeutigkeit von Result<Result<T,E>> vorstellen, aber in solchen mehrdeutigen Fällen könnten wir einfach auf No-Wrapping zurückgreifen. Der Benutzer könnte dann explizit Ok-Wrap verwenden, um zu unterscheiden. Das scheint nicht schlecht zu sein, da ich nicht erwarte, dass diese Art von Zweideutigkeit zu oft auftritt ...

Es sollte überhaupt keine Mehrdeutigkeit geben, da es sich nicht um Ok -Zwang handelt, sondern um Ok -Verpackung. try { ...; x } würde Ok(x) genauso eindeutig ergeben wie Ok({ ...; x }) .

@joshtriplett Ist das ungelöst? Bei dem Tracking-Problem ist resolve whether catch blocks should "wrap" result value deaktiviert, unter Berufung auf https://github.com/rust-lang/rust/issues/41414

@rpjohnst Sorry, ich hätte klarer sein sollen. Was ich damit meine ist, dass wenn wir try jetzt ohne Ok-Wrapping stabilisieren würden, ich glaube, dass es später abwärtskompatibel hinzugefügt werden könnte.

Das heißt, ich denke, die meisten Leute sind sich einig, dass wir try Blöcke haben sollten, aber nicht alle sind sich einig über catch oder Ok-Wrapping. Aber ich denke nicht, dass diese Diskussionen try blockieren müssen ...

@Kampfkarren Ja. Das obige Gespräch beschreibt den Fortschritt dieser Angelegenheit. Es wurde vorzeitig abgehakt, ohne alle zu konsultieren. Insbesondere @joshtriplett hatte Bedenken, die mehrere andere (einschließlich ich) teilten.

@ mark-im Wie genau sehen Sie, dass in Zukunft Ok-Wrapping hinzugefügt wird? Ich versuche herauszufinden, wie das gemacht werden könnte, und ich kann es nicht ganz sehen.

Ich werde dies vorwegnehmen, indem ich sage, ich weiß nicht, ob es eine gute Idee ist oder nicht ...

Wir würden den Block try stabilisieren, ohne ihn in Ordnung zu bringen. Zum Beispiel:

let x: Result<usize, E> = try { 3 }; // Error: expected Result, found usize
let x: Result<usize, E> = try { Ok(3) }; // Ok (no pun intended)

Nehmen wir später an, wir waren uns einig, dass wir Ok-Wrapping haben sollten, dann könnten wir einige Fälle zulassen, die vorher nicht funktionierten:

let x: Result<usize, E> = try { 3 }; // Ok
let x: Result<usize, E> = try { Ok(3) }; // Also Ok for backwards compat
let x: Result<Result<usize, E1>, E2> = try { Ok(3) }; // Ok(Ok(3))
let x: Result<Result<usize, E1>, E2> = try { Ok(Ok(3)) }; // Ok(Ok(3))

Die Frage ist, ob dies dazu führen kann, dass etwas mehrdeutig wird, was vorher nicht war. Zum Beispiel:

let x = try { Err(3) }; // If x: Result<Result<T1, usize>, usize>, then it is not clear if user meant Ok(Err(3)) or Err(3)...

Obwohl, vielleicht muss sich Ok-Wrapping schon mit diesem Problem auseinandersetzen?

Wie auch immer, meine Intuition ist, dass solche seltsamen Fälle nicht so oft auftauchen, also ist es vielleicht nicht so wichtig.

Was ist mit Ok-Wrapping, außer wenn der zurückgegebene Typ Result oder Option ? Dies würde in den meisten Fällen einen einfacheren Code ermöglichen, aber bei Bedarf den genauen Wert angeben.

// Ok-wrapped
let v: Result<i32, _> = try { 1 };

// not Ok-wrapped since the returned type is Result
let v: Result<i32, _> = try { Ok(1) };

// not Ok-wrapped since the returned type is Result
let v: Result<i32, _> = try { Err("error") };

// Ok-wrapped
let v: Option<i32> = try { 1 };

// not Ok-wrapped since the returned type is Option
let v: Option<i32> = try { Some(1) };

// not Ok-wrapped since the returned type is Option
let v: Option<i32> = try { None };

Das Hinzufügen von Ok -Zwang oder einer syntaxabhängigen Ok -Verpackung (was erforderlich wäre, um die Stabilisierung ohne sie zu unterstützen und sie später einzuführen) wäre für die Lesbarkeit sehr schlecht wurde mehrfach mehrfach gegen i.rl.o argumentiert (häufig von Leuten, die das einfache Ok -Wrapping, das implementiert wird, falsch verstehen).

Ich persönlich bin stark für das implementierte Ok -Wrapping, wäre aber noch stärker gegen jede Form von Zwang oder Syntaxabhängigkeit, die das Verständnis der Situationen erschwert, die sich abwickeln lassen (ich würde es für nutzlos halten, schreiben zu müssen) Ok(...) überall damit beschäftigt, herauszufinden, ob es erzwungen hat oder nicht).

let x = try { Err(3) }; // If x: Result<Result<T1, usize>, usize>, then it is not clear if user meant Ok(Err(3)) or Err(3)...

Obwohl, vielleicht muss sich Ok-Wrapping schon mit diesem Problem auseinandersetzen?

Nein, das ist eindeutig Ok(Err(3)) , Ok -Wrapping ist unabhängig von Syntax oder Typ, es umschließt nur die Ausgabe des Blocks in der Variante Try::Ok .

@ mark-im Ich glaube nicht, dass wir nach der Stabilisierung vernünftigerweise von einem zum anderen wechseln können. So schlecht Ok-Wrapping auch für mich ist, inkonsistentes Ok-Wrapping, das zu erraten versucht, ob Sie es wollen oder nicht, wäre noch schlimmer.

In meiner Codebasis habe ich es mit vielen optionalen Werten zu tun, deshalb habe ich vor langer Zeit meinen eigenen Try-Block wie ein Makro eingeführt. Und als ich es einführte, hatte ich verschiedene Varianten mit und ohne Ok-Wrapping, und die Ok-Wrapping-Version erwies sich als so viel ergonomischer, dass es das einzige Makro ist, das ich letztendlich verwendet habe.

Ich habe eine Menge optionaler Werte, mit denen ich arbeiten muss, und sie sind meistens numerisch, also habe ich eine Menge solcher Situationen:

let c = try { 2 * a? + b? };

Ohne Ok-Wrapping wäre dies viel weniger ergonomisch bis zu dem Punkt, an dem ich wahrscheinlich auf meinem eigenen Makro bleiben würde, als wenn ich die echten Try-Blöcke verwenden würde.

Angesichts der ehrwürdigen Geschichte dieses Tracking-Problems, seiner ursprünglichen und bedauerlichen Verschmelzung mit dem ? -Operator und der Straßensperre für das Ok -Verpackungsproblem würde ich empfehlen, dieses Problem sofort zu schließen und try senden

Ohne Ok-Wrapping wäre dies viel weniger ergonomisch

Könnten Sie bitte näher erläutern, welche nicht ergonomischen Aspekte genau eingeführt werden sollen?

Ohne Ok-Wrapping würde Ihr Beispiel folgendermaßen aussehen:

let c = try { Ok(2 * a? + b?) };

Das ist meiner Meinung nach ziemlich gut.

Ich meine, mit einem kleinen Beispiel wie diesem sieht es vielleicht wie ein Overkill aus, aber je mehr Code der try -Block enthält, desto weniger Auswirkungen hat dieser Ok(...) -Wrapper.

Neben dem Kommentar von Ok -Wrapping emuliert, wenn der Block try dies nicht tut (und jemand wird es sicherlich erstellen eine Standardkiste für dieses winzige Makro), aber das Gegenteil ist nicht der Fall.

Dieses Makro ist nicht möglich, solange das Merkmal Try instabil ist.

Warum? Wie auch immer, wenn es sich stabilisiert (hypothetisch nicht zu weit in der Zukunft), wird es sehr gut möglich sein.

@ Nemo157 try Blöcke sind momentan auch nur nachts Try herauszureißen. Dies bedeutet, dass sie wahrscheinlich nicht vor Try stabilisiert werden. Es macht also keinen Sinn zu sagen, dass das Makro nicht möglich ist.

@KrishnaSannasi Ich bin gespannt, warum Try könnte.

@ mark-im Ich glaube nicht, dass es so sein wird. Ich erkläre nur, warum es kein realistisches Problem ist, sich Sorgen darüber zu machen, dass Try jeden Abend geschrieben wird, um Blöcke auszuprobieren. Ich freue mich auf Try im Stall.

Angesichts der Tatsache, dass ? bereits stabilisiert wurde und try Blöcke ein klares Design haben, das sowohl Result als auch Option auf die gleiche Weise umfasst wie ? tut, es gibt keinen Grund, warum ich die Stabilisierung blockieren kann, wenn ich Try stabilisiere. Ich habe es nicht genau beobachtet, aber meine Eindrücke waren, dass es viel weniger Konsens über das Design von Try als für try Blöcke, so dass ich try blockiert die Stabilisierung Jahre vor dem Merkmal Try (wie es bei ? geschehen ist). Und selbst wenn das Merkmal Try aufgegeben wird, sehe ich keinen Grund, der die Stabilisierung von try -Blöcken blockieren sollte, da sie nur mit Result und Option wie ? wäre dann.

(Für _why_ konnten Sie dieses Makro nicht schreiben, wenn stabilisierte try Blöcke und ein instabiles Try Merkmal vorhanden waren. Das Makro hätte sich auf try { Try::from_ok($expr) } . Sie könnten Makros pro Typ erstellen für nur Result und Option , aber IMO, die den Punkt "sehr einfach zu [...] emulieren" nicht erfüllen würde).

Angesichts der Tatsache, dass ? bereits ein Stall mit Spezialgehäuse ist, obwohl das Merkmal Try nicht für einen Stall verwendet werden kann, verstehe ich nicht, warum ein instabiles Try das Vorhandensein von Versuchsblöcken blockieren würde implementiert auf Stable, denn wenn das Try-Merkmal entfernt wird, haben wir immer noch Option und Ergebnis, die ? auf Stable unterstützen, nur ohne Ergonomie.

Ich würde das folgende Konzept vorschlagen, um zu versuchen, die Semantik zu fangen ...

Betrachten Sie den folgenden Code:

union SomeFunctionMultipleError {
    err0: Error1,
    err1: Error2,
}

struct SomeFunctionFnError {
    index: u32,
    errors: SomeFunctionMultipleError,
}

fn some_function() -> Result<i32, SomeFunctionFnError> {
    if 0 == 0 {
        Ok(2)
    } else {
        Err(SomeFunctionFnError{ index: 0, errors: SomeFunctionMultipleError {err1: Error2 {id0: 0, id1: 0, id3: 0}}})
    }
}

union OtherFunctionMultipleError {
    err0: Error1,
    err1: Error2,
    err2: Error3,
}

struct OtherFunctionFnError {
    id: u32,
    errors: OtherFunctionMultipleError,
}

fn other_function() -> Result<i32, OtherFunctionFnError> {
    if 0 == 0 {
        Ok(2)
    } else {
        Err(OtherFunctionFnError {id: 0, errors: OtherFunctionMultipleError {err0: Error1 {id0: 0, id1: 0}}})
    }
}

Dies ist der Code, der durch Zero-Overhead-Ausnahmen in Rust mit der folgenden Syntaxfunktion generiert werden kann:

fn some_function() -> i32 throws Error1, Error2 {
    if 0 == 0 {
        2
    } else {
        Error2 {id0: 0, id1: 0, id3: 0}.throw
    }
}

fn other_function() -> i32 throws Error1, Error2, Error3 {
    if 0 == 0 {
        2
    } else {
        Error1{id0: 0, id1: 0}.throw
    }
}

oder sogar diese Fehler könnten vom Compiler implizit abgeleitet werden:

fn some_function(i: i32) -> i32 throws { // Implicitly throws Error1, Error2
    if i == 0 {
        2
    } else if i == 1 {
        Error1 {id0: 0, id1: 0, id3: 0}.throw
    } else {
        Error2 {id0: 0, id1: 0, id3: 0}.throw
    }
}

fn other_function(i: i32) -> i32 throws { // Implicitly throws Error1
    if i == 0 {
        2
    } else {
        Error1{id0: 0, id1: 0}.throw
    }
}

Dies ist nichts anderes syntaktischer Zucker !! Verhalten ist das gleiche !!

Hallo zusammen,

Hat jemand meinen Vorschlag bezüglich der oben genannten Zero-Overhead-Ausnahmen angesehen?

@redradist Einer der Hauptpunkte des Blocks try ist, dass wir ihn innerhalb einer Funktion verwenden können , ohne dass für jeden Block eine Funktion erstellt werden muss. Ihr Vorschlag hat hier meines Erachtens nichts damit zu tun.

Erst heute hatte ich das Bedürfnis nach try Blöcken. Ich habe eine große Funktion, die viele ? Operationen hat. Ich wollte den Fehlern Kontext hinzufügen, aber dies für jedes ? tun, hätte eine riesige Menge Boilerplate benötigt. Das Abfangen der Fehler mit try und das Hinzufügen des Kontexts an einer Stelle hätte dies verhindert.

Übrigens. Das Umschließen der Operationen in eine innere Funktion wäre in diesem Fall schwierig gewesen, da der Kontext keine offensichtliche Lebensdauer hat und das Teilen von Dingen in mehrere Funktionen die NLL unterbricht.

Ich möchte erwähnen, dass in der aktuellen Implementierung ein try -Block kein Ausdruck ist. Denken Sie, dass dies ein Versehen ist.

Ich möchte erwähnen, dass in der aktuellen Implementierung ein try-Block kein Ausdruck ist. Denken Sie, dass dies ein Versehen ist.

Könnten Sie den Code posten, der für Sie nicht funktioniert? Ich kann es hier in einem Ausdruckskontext verwenden: ( Rust Playground )

#![feature(try_blocks)]

fn main() {
    let s: Result<(), ()> = try { () };
}

Klar, hier ist es .

Und hier ist eine andere , die zeigt, dass die Typinferenz auf Try-Blöcken noch nicht vollständig ist. Dies ist besonders ärgerlich, da Typzuweisungen in if let -Blöcken nicht unterstützt werden.

@ Nokel81 Das Problem mit Ihrem früheren Beispiel ist, dass der Ausdruck in if let $pat = $expr kein Kontext für reguläre Ausdrücke ist, sondern ein spezieller Kontext für Ausdrücke ohne Klammern. Ein Beispiel dafür, wie dies mit Strukturausdrücken funktioniert, finden Sie in diesem Beispiel, in dem syntaktisch klar ist, dass dort ein Strukturausdruck vorhanden ist, und in diesem Beispiel, in dem dies nicht der Fall ist. Der Fehler ist also nicht, dass try kein Ausdruck ist, sondern dass der Fehler falsch ist und sagen sollte " try Ausdruck ist hier nicht erlaubt; versuchen Sie, ihn mit Klammern zu umgeben" (und die falsche Warnung vor unnötiger Klammer unterdrückt).

Ihr letztes Beispiel ist für die Typinferenz tatsächlich mehrdeutig. Der Typ von e ist in diesem Fall _: From<usize> , was nicht ausreicht, um ihm einen konkreten Typ zu geben. Sie müssten es auf irgendeine Weise verwenden, um ihm einen konkreten Typ zu geben, damit die Typinferenz erfolgreich sein kann. Dies ist kein spezifisches Problem für try ; So funktioniert die Typinferenz in Rust.

Wenn Sie nun sofort versuchen, eine Übereinstimmung als Ok herzustellen und den Fall Err verwerfen , haben Sie einen Fall für eine suboptimale Fehlermeldung , für die es keine wirklich

Ah, vielen Dank für die ausführliche Erklärung. Ich denke, ich bin immer noch verwirrt, warum die später für Typinferenz mehrdeutig ist. Warum ist der Typ des Ausdrucks nicht Result<isize, usize> ?

Der Operator ? kann mithilfe des Merkmals From Typkonvertierungen zwischen verschiedenen Fehlertypen durchführen. Es wird ungefähr auf den folgenden Rostcode erweitert (wobei das Merkmal Try ignoriert wird):

match expr {
    Ok(v) => v,
    Err(e) => return From::from(e),
}

Der Aufruf From verwendet den Typ des endgültig zurückgegebenen Ausdrucks, um zu bestimmen, welche Fehlertypkonvertierungen durchgeführt werden sollen, und verwendet nicht automatisch den Typ des übergebenen Werts.

Entschuldigung, wenn dies bereits angesprochen wurde, aber es scheint mir seltsam, dass:

#![feature(try_blocks)]

fn main() -> Result<(), ()> {
    let result = try { // no type annotation
        Err(())?;
    };
    result.map_err(|err| err)
}

Kompiliert nicht mit:

error[E0282]: type annotations needed

aber:

#![feature(try_blocks)]

fn main() -> Result<(), ()> {
    let result : Result<_, _> = try { // partial type annotation
        Err(())?;
    };
    result.map_err(|err| err)
}

in Ordnung.

Wenn dies ein Problem wäre, weil die Typargumente von Result nicht abgeleitet werden könnten, würde ich verstehen, aber wie oben gezeigt, ist dies nicht der Fall und rustc kann eine Inferenz durchführen, sobald bekannt ist, dass das Ergebnis von Ein try Ausdruck ist eine Art Result , die er aus core::ops::Try::into_result ableiten sollte.

Gedanken?

@nwsharp , weil try / ? generisch über Try Typen ist. Wenn Sie einen anderen Typ hatten, der impl Try<Ok=_, Error=()> , konnte der try-Block diesen Typ sowie Result auswerten. Desugared, Ihr Beispiel ist grob

#![feature(try_trait, label_break_value)]

use std::ops::Try;

fn main() -> Result<(), ()> {
    let result /*: Result<_, _>*/ = 'block: {
        match Try::into_result(Err(())) {
            Ok(ok) => Try::from_ok(ok),
            Err(err) => {
                break 'block Try::from_error(From::from(err));
            }
        }
    };
    result.map_err(|err| err)
}

@ CAD97 Danke für die Erklärung.

Trotzdem habe ich nicht erwartet, dass try tatsächlich eine Art Konvertierung zwischen verschiedenen Try Impls bewirken kann.

Ich würde ein De-Surgaring erwarten, bei dem das gleiche Try Impl für into_result , from_ok und from_error .

Meiner Meinung nach überwiegt der ergonomische Verlust, die Typinferenz nicht durchführen zu können (insbesondere wenn man bedenkt, dass es überhaupt keine alternativen impl Try gibt), nicht den Vorteil, diese Konvertierung zuzulassen.

Wir könnten Rückschlüsse zulassen, indem wir die Mehrdeutigkeit beseitigen und die Möglichkeit behalten, uns für die Konvertierung über Folgendes zu entscheiden:

try { ... }.into()

Mit der entsprechenden Decke impl:

impl<T: Try, E: Into<T::Err>> From<Result<T::Ok, E>> for T {
    fn from(result: Result<T::Ok, E>) -> Self {
        match result {
            Ok(ok) => T::from_ok(ok),
            Err(err) => T::from_err(err.into()),
        }
    }
}

(Was ich ehrlich gesagt sinnvoll finde, obwohl ich persönlich die automatische Konvertierung von Fehlertypen hier bezweifle. Wenn es gewünscht wird, sollte der Benutzer .map_err() auf den Result .)

Im Allgemeinen denke ich, dass dieses Entzuckern "zu klug" ist. Es verbirgt sich zu viel und seine aktuelle Semantik kann Menschen verwirren. (Besonders wenn man bedenkt, dass die aktuelle Implementierung Typanmerkungen für etwas verlangt, das sie nicht direkt unterstützt!)

Oder ich gehe noch weiter mit dem Deckengerät.

impl <T: Try, U: Try> From<U> for T 
    where U::Ok : Into<T::Ok>, U::Err : Into<T::Err>
{
    fn from(other: U) -> Self {
        match other.into_result() {
            Ok(ok) => Self::from_ok(ok.into()),
            Err(err) => Self::from_err(err.into()),
        }
    }
}

Oder Wasauchimmer...

Trotzdem hatte ich nicht erwartet, dass der Versuch tatsächlich eine Art Konvertierung zwischen verschiedenen Try Impls bewirken könnte.

Ich würde ein De-Surgaring erwarten, bei dem das gleiche Try impl für into_result , from_ok und from_error .

Meiner Meinung nach überwiegt der ergonomische Verlust, die Typinferenz nicht durchführen zu können (insbesondere wenn man bedenkt, dass es überhaupt keine alternativen impl Try gibt), nicht den Vorteil, diese Konvertierung zuzulassen.

Es gibt vier stabile Try -Typen: Option<T> , Result<T, E> , Poll<Result<T, E>> und Poll<Option<Result<T, E>> .

NoneError ist instabil, so dass Option<T> in Option<T> versucht, während NoneError instabil ist. (Beachten Sie jedoch, dass die Dokumente From<NoneError> explizit als "Aktivieren Sie option? für Ihren Fehlertyp" aufrufen.)

Die Poll Impls setzen jedoch ihren Fehlertyp auf E . Aus diesem Grund ist das "Typ-Morphing" von Try stabil, da Sie ? a Poll<Result<T, E>> in einem -> Result<_, E> ein Poll<T> und geben Sie den Fall E frühzeitig zurück.

In der Tat treibt dies

fn lift_err<T, E>(x: Poll<Result<T, E>>) -> Result<Poll<T>, E> { Ok(x?) }

@ CAD97 Danke, dass du mich humorisiert hast. Dies wird eine schwierige Sache für Neulinge sein und erfordert einiges an Liebe in Bezug auf Fehlermeldungen.

Wurde darüber nachgedacht, die Angabe des gewünschten impl Try zuzulassen, um das unintuitive Verhalten hier zu lindern?

Zum Beispiel Bikeshedding für ein bisschen, try<T> { ... } . Oder gibt es noch etwas, worüber man stolpern kann?

Um hier vielleicht ein bisschen mehr Farbe hinzuzufügen, ist die Tatsache, dass try { } über ein Bündel von Result nicht "nur" ein Result unerwartet und macht mich traurig . Ich verstehe warum , aber ich mag es nicht.

Ja, es wurde über die Kombination von "generalisierter Typzuordnung" (es gibt Ihren Begriff, nach dem gesucht werden muss) und try diskutiert. Ich denke, zuletzt habe ich gehört, dass try: Result<_, _> { .. } irgendwann funktionieren sollte.

Aber ich stimme Ihnen zu: try -Blöcke verdienen eine gezielte Diagnose, um sicherzustellen, dass ihr Ausgabetyp angegeben ist.

In dieser separaten Ausgabe finden Sie eine spezifische, enge Frage, um den Konsens des Sprachteams in Bezug auf das Ok -Verpacken zu lösen.

Bitte lesen Sie den Eröffnungskommentar dieses Threads, bevor Sie ihn kommentieren, und beachten Sie insbesondere, dass es sich bei diesem Thread nur um diese eine Frage handelt, nicht um ein anderes Problem im Zusammenhang mit try oder ? oder Try .

Ich verstehe nicht, warum der Block try benötigt wird. Diese Syntax

fn main() -> Result<(), ()> {
    try {
        if foo() {
            Err(())?
        }
        ()
    }
}

kann dadurch ersetzt werden:

fn main() -> Result<(), ()> {
    Ok({
        if foo() {
            Err(())?
        }
        ()
    })
}

Beide verwenden die gleiche Anzahl von Zeichen, aber das zweite ist bereits stabil.

Wenn Sie das Ergebnis einer Variablen zuweisen, bedeutet dies möglicherweise, dass eine Hilfsfunktion erstellt werden sollte, um das Ergebnis zurückzugeben. Ist dies nicht möglich, kann stattdessen ein Verschluss verwendet werden.

@dylni try Blöcke sind besonders nützlich, wenn sie nicht den gesamten Körper einer Funktion enthalten. Der ? -Operator bei einem Fehler bewirkt, dass die Flusskontrolle bis zum Ende des innersten try -Blocks reicht, ohne von der Funktion zurückzukehren.

`` `Rost
fn main () / * hier kein Ergebnis * / {
let result = try {
foo ()?. bar ()?. baz ()?
};
Spielergebnis {
//…
}}
}}

@ SimonSapin Kommt das so oft vor? Ich hatte selten eine Situation, für die es Sinn machen würde, und es gibt normalerweise einen guten Weg, um sie zu umgehen. In Ihrem Beispiel:

fn main() /* no result here */ {
    let result  = foo()
        .and_then(|x| x.bar())
        .and_then(|x| x.baz());
    match result {
        // …
    }
}

Das ist ausführlicher, aber ich denke, eine einfachere Lösung wäre eine Syntax zum Schließen von Methoden:

fn main() /* no result here */ {
    let result  = foo()
        .and_then(::bar)
        .and_then(::baz);
    match result {
        // …
    }
}

Der Typ wird auch korrekt mit and_then , wobei Sie Typanmerkungen für try benötigen. Ich habe dies selten genug erfahren, dass ich nicht glaube, dass eine Terser-Syntax den Lesbarkeitsschaden wert wäre.

Der akzeptierte RFC hat weitere Gründe: https://rust-lang.github.io/rfcs/0243-trait-based-exception-handling.html

Wie auch immer, die Argumente für Sprachkonstrukte für den Kontrollfluss mit dem Operator ? (und .await ) über Verkettungsmethoden wie and_then wurden bereits ausführlich diskutiert.

Wie auch immer, die Argumente für Sprachkonstrukte für den Kontrollfluss mit dem Operator ? (und .await ) über Verkettungsmethoden wie and_then wurden bereits ausführlich diskutiert.

@ SimonSapin Danke. Dies und das erneute Lesen des RFC haben mich überzeugt, dass dies nützlich sein kann.

Ich dachte, ich könnte try-Blöcke verwenden, um Fehlern leicht Kontext hinzuzufügen, aber bisher kein Glück.

Ich habe eine kleine Funktion geschrieben, die gut funktioniert. Beachten Sie, dass File::open()? mit einem std::io::Error fehlschlägt, während die folgende Zeile mit einem anyhow::Error fehlschlägt. Trotz der unterschiedlichen Typen findet der Compiler heraus, wie beide in Result<_, anyhow::Error> konvertiert werden können.

fn tls_add_cert(config: &ClientConfig, path: impl AsRef<Path>) -> Result<(usize, usize), anyhow::Error> {
    let path = path.as_ref();
    let mut file = BufReader::new(File::open(path)?);
    Ok(config.root_store.add_pem_file(&mut file)
        .map_err(|_| anyhow!("Bad PEM file"))?)
}

Ich wollte einen Fehlerkontext hinzufügen, also habe ich versucht, einen Try-Block zu verwenden und trotzdem with_context() :

fn tls_add_cert(config: &ClientConfig, path: impl AsRef<Path>) -> anyhow::Result<(usize, usize)> {
    let path = path.as_ref();
    try {
        let mut file = BufReader::new(File::open(path)?);
        Ok(config.root_store.add_pem_file(&mut file)
            .map_err(|_| anyhow!("Bad PEM file"))?)
    }
    .with_context(|| format!("Error adding certificate {}", path.display()))
}

Aber jetzt schlägt die Typinferenz fehl:

error[E0282]: type annotations needed
  --> src/net.rs:29:5
   |
29 | /     try {
30 | |         let mut file = BufReader::new(File::open(path)?);
31 | |         Ok(config.root_store.add_pem_file(&mut file)
32 | |             .map_err(|_| anyhow!("Bad PEM file"))?)
33 | |     }
   | |_____^ cannot infer type
   |
   = note: type must be known at this point
   ```

I don't understand why a type annotation is needed here but not in the first case. Nor do I see any easy way to add one, as opposed to using an [IIFE](https://en.wikipedia.org/wiki/Immediately_invoked_function_expression) which does let me add an annotation:

```rust
(|| -> Result<_, anyhow::Error> {
    let domain = DNSNameRef::try_from_ascii_str(host)?;
    let tcp = TcpStream::connect(&(host, port)).await?;

    Ok(tls.connect(domain, tcp).await?)
})()
.with_context(|| format!("Error connecting to {}:{}", host, port))

@jkugelman

Nochmal,

Das liegt daran, dass try / ? generisch über Try Typen ist. Wenn Sie einen anderen Typ hatten, der [ impl Try<Ok=_, Error=anyhow::Error> ] war, konnte der try-Block diesen Typ sowie Result auswerten.

(Außerdem müssen Sie Ihren nachgestellten Ausdruck nicht in einem try -Block (# 70941) Ok angeben.)

Ich denke, dass die Tatsache, dass dies weiterhin auftaucht, dies bedeutet

  • Vor der Stabilisierung muss try eine Typzuweisung unterstützen ( try: Result<_,_> { oder was auch immer) oder dieses Problem auf andere Weise abmildern.
  • Dies erfordert definitiv eine gezielte Diagnose, wenn die Typinferenz eines try -Blocks fehlschlägt, und
  • Wir sollten nachdrücklich in Betracht ziehen, try einen Typ-Fallback auf Result<_,_> wenn dies nicht anderweitig eingeschränkt ist. Ja, das ist schwierig, unterbestimmt und möglicherweise problematisch, aber es würde den 80% -Fall von try Blöcken lösen, für die eine Typanmerkung erforderlich ist, da $12: Try<Ok=$5, Error=$8> nicht spezifisch genug ist.

Angesichts der Tatsache, dass # 70941 sich in Richtung "Ja, wir wollen (irgendeine Form von) ' Try::from_ok wrapping'" aufzulösen scheint, wollen wir wahrscheinlich auch eine gezielte Diagnose für den Fall, dass der Endausdruck eines try block gibt Ok(x) wenn x funktionieren würde.

Ich vermute, dass das richtige Verhalten für den Versuch ist

  • Erweitern Sie die Syntax, um eine manuelle Zuordnung wie try: Result<_, _> { .. } , try as Result<> oder was auch immer zu ermöglichen (ich denke, try: Result ist wahrscheinlich in Ordnung? Es scheint die bevorzugte Syntax zu sein).
  • Untersuchen Sie den "erwarteten Typ", der aus dem Kontext stammt. Wenn einer vorhanden ist, bevorzugen Sie diesen als Ergebnistyp eines try
  • Andernfalls wird standardmäßig Result<_, _> Dies ist kein Fallback für Typinferenz wie bei i32 Dies würde früher passieren, aber das würde bedeuten, dass Dinge wie try { }.with_context(...) kompiliert werden.

Ich bin jedoch besorgt, dass wir Fehler um ? und den into Zwang bekommen könnten, zumindest solange der Fehlertyp nicht angegeben ist. Insbesondere, wenn Sie Code schreiben, in dem Sie ? das Ergebnis eines try -Blocks erhalten, wie folgt:

#![feature(try_blocks)]

use std::error::Error;
fn foo() -> Result<(), Box<dyn Error>> {
    let x: Result<_, _> = try {
        std::fs::File::open("foo")?;
    };

    x?;

    Ok(())
}

fn main() { 
}

Sie erhalten immer noch Fehler ( Spielplatz ) und das zu Recht, da nicht klar ist, bei welchem ? der "In" -Zwang ausgelöst werden soll.

Ich bin mir nicht sicher, was hier die beste Lösung ist, aber es handelt sich wahrscheinlich um einen Typ-Inferenz-Fallback, der mich nervös macht.

Wahrscheinlich handelt es sich um einen Fallback, der mich nervös macht.

Einfachste: Wenn alle Verwendungen von ? in einem bestimmten Try -Block denselben Try::Error -Typ enthalten, verwenden Sie diesen Fehlertyp für den enthaltenden try -Block (es sei denn sonst gebunden).

Das "(sofern nicht anders gebunden)" ist natürlich der subtile beängstigende Teil.

Ich hoffe, dass ich mit diesem Beitrag nicht zu unkonstruktiv bin. Allerdings wollte ich @nikomatsakis Beispiel mit einem von einer parallelen Welt , in der, um den Kontrast ? keine Zwangsumwandlung tun, und es gibt keine automatischen Umwickeln des try Block - Ergebnisses:

use std::error::Error;
fn foo() -> Result<(), Box<dyn Error>> {
    let x = try {
        std::fs::File::open("foo").err_convert()?;
        Ok(())
    };

    x?;

    Ok(())
}

In dieser Welt:

  • Es ist leicht zu erkennen, dass sowohl der Bereich try als auch der Bereich fn selbst zu einem Erfolg ohne Wert führen. Es ist auch leicht zu sehen, ohne zu versuchen, dass sie Result s produzieren.
  • Es ist offensichtlich, wo die Fehlerkonvertierung stattfindet.
  • Die Fehlerkonvertierung könnte in den Ausdruck x? verschoben werden, wodurch der Bereich try für die Operationen std::fs::File spezifisch wird.
  • Alle Typhinweise ketten fließend aus der Typensignatur. Sowohl für die Maschine als auch für uns Menschen.
  • Vom Benutzer angegebene Tipphinweise sind nur in Fällen erforderlich, in denen Fehler tatsächlich in einen anderen, unabhängigen Fehler zusammengefasst werden sollen.

Ich würde mich in diesem Paralleluniversum sehr freuen.

@phaylon Während ich die sorgfältige Art und Weise schätzen Sie diesen Kommentar geschrieben habe, ich Angst habe ist es eher nicht konstruktiv. Die Fehlerkonvertierung ist Teil von ? und das wird sich nicht ändern. In Anbetracht dessen ist das Ok-Wrapping im Grunde genommen orthogonal zum Rest dieser Diskussion.

Wenn try -Funktionen (mit Return- und Throw-Typen) jemals in Betracht gezogen werden sollen, wäre es möglicherweise sinnvoll, auch die Syntax für das Zuweisen von try-Blöcken als ähnlich zu betrachten.

z.B

try fn foo() -> u32 throw String {
  let result = try: u32 throw String {
    123
  };
  result?
}

Entschuldigung, wenn dies besprochen wurde, aber was sind die Vorteile der Verwendung

try fn foo() -> u32 throw String { ... }

oder ähnlich im Gegensatz zu

fn foo() -> Result<u32, String> { ... }

?
Scheint nur wie doppelte Syntax.

@gorilskij Soweit ich weiß, besteht der Hauptvorteil darin, Ok -Verpackung zu erhalten. Ansonsten muss man schreiben:

fn foo() -> Result<u32, String> {
    try {
        // function body
    }
}

Einige Leute bevorzugen auch throws da sie die Ausnahmeterminologie für zuordenbar halten.

Persönlich möchte ich mich so weit wie möglich vom Auftreten von Ausnahmen fernhalten.

Dies ist nicht der Thread, um try fn zu diskutieren. Bitte nehmen Sie diese Tangente nicht weiter. Dieser Thread ist für die akzeptierte Funktion von try -Blöcken vorgesehen, nicht für die potenzielle (und noch nicht RFCd) Funktion try fn .

Mir ist gerade aufgefallen, dass der ursprüngliche RFC für ? die Verwendung von Into , nicht von From . Es sagt aus:

Der vorliegende RFC verwendet zu diesem Zweck das Merkmal std::convert::Into (das eine pauschale Impl-Weiterleitung von From ).

Die genaue Upcasting-Methode bleibt jedoch eine ungelöste Frage . Into wurde (vermutlich) aufgrund der Anleitung von From bevorzugt:

Verwenden Sie lieber Into als From wenn Sie Merkmalsgrenzen für eine generische Funktion angeben. Auf diese Weise können auch Typen, die Into direkt implementieren, als Argumente verwendet werden.

Im RFC-Merkmal Try wird jedoch Into nicht mehr erwähnt, und die Konvertierung erfolgt stattdessen mit From . Dies ist auch das, was der Code jetzt verwendet, selbst für ?
https://github.com/rust-lang/rust/blob/b613c989594f1cbf0d4af1a7a153786cca7792c8/src/librustc_ast_lowering/expr.rs#L1232

Dies scheint unglücklich zu sein, da es keine pauschale Implementierung gibt, die von Into auf From . Dies bedeutet, dass Fehler, die Into (im Gegensatz zu From ) entweder aus alten Gründen oder aus anderen Gründen irrtümlich implementieren, nicht mit ? (oder) verwendet werden können Try ). Dies bedeutet auch, dass jede Implementierung, die der Empfehlung in der Standardbibliothek folgt, Into in Grenzen zu verwenden, ? nicht verwenden kann. In einem Beispiel empfiehlt die Standardbibliothek, Folgendes zu schreiben:

fn with_user_err<E>(op: impl Fn() -> Result<(), E>) -> Result<(), MyError>
where E: Into<MyError>

In diesem Fall kann ich ? im Funktionskörper verwenden. Wenn ich das machen will, muss ich schreiben

fn with_user_err<E>(op: impl Fn() -> Result<(), E>) -> Result<(), MyError>
where MyError: From<E>

In diesem Fall können Benutzer mit Fehlertypen, die Into anstelle von From implementieren, diese Funktion nicht verwenden. Beachten Sie, dass die Umkehrung aufgrund der pauschalen Implikation von Into basierend auf From nicht wahr ist.

Es ist wahrscheinlich (?) Zu spät, um ? jetzt zu reparieren (was sehr unglücklich ist - vielleicht in der nächsten Ausgabe?), Aber wir sollten zumindest sicherstellen, dass wir diesen Weg in den Try nicht tiefer gehen

@jonhoo @cuviper hat versucht, das Desugaring in From 60796 von Into zu ändern, um # 38751 zu überprüfen, und es hat genau wegen der From zu einer großen Menge an Inferenzbruch geführt -> Into blanket impl machte es rustc schwerer, den allgemeinen Fall der Identitätskonvertierung zu behandeln. Es wurde entschieden, dass es die kostenbrechende Schlussfolgerung nicht so sehr wert war.

@jonhoo finden Sie diesen Kommentar von niko möglicherweise auch informativ:

Es gibt auch eine fest codierte Grenze im Merkmalssystem. Wenn Sie ein Ziel wie ?X: Into<ReturnType> lösen müssen, können wir es nicht lösen. Wenn Sie jedoch ein Ziel wie ReturnType: From<?X> lösen müssen, werden wir möglicherweise erfolgreich sein und einen Wert für ?X ableiten

Bearbeiten: Hier bezieht sich ?X auf eine unbekannte Inferenzvariable. Die fest codierte Grenze im heutigen Merkmalssystem besteht darin, dass der Typ Self zumindest teilweise abgeleitet werden muss, damit wir diese Option untersuchen können.

Die TL; DR ist, dass das Ableiten mit Into schwieriger ist und in der Art und Weise, wie der Trait Solver funktioniert, inhärent ist.

@KrishnaSannasi @ CAD97 Danke, das ist hilfreich! Ich mache mir immer noch Sorgen, dass wir uns zu sehr stabilisieren, was auf From basiert, da wir Implementierer von Into permanent auslassen. Ist die Erwartung, dass die Schlussfolgerung hier irgendwann besser wird? Sollte die Anleitung, Into in Grenzen zu bevorzugen, geändert werden? Erwarten wir, dass es mit den neuen Regeln für die Kohärenz von Merkmalen in 1.41 (glaube ich) keinen Grund mehr gibt, nur Into zu implementieren und all diese Impls-Fehler zu berücksichtigen?

Wenn die Inferenz gut genug ist, sollte sie vorwärtskompatibel sein, um später zu Into zu wechseln. Das Schlimmste, was wir brechen könnten, ist die Schlussfolgerung, dass From Into impliziert

Deckt dies (das Merkmal Try ) ab, dass ? mit den Merkmalen Into / From mit generischen Grenzen für Typen arbeiten kann, die Try implementieren Result selbst)?

dh ? in einem Abschluss oder einer Funktion, die zB impl Into<Result> zurückgibt

(Es scheint nicht, wenn ich das jeden Abend anprobiere?)

Ich bin gespannt darauf, dass sich dies stabilisiert. Nachdem ich diesen Thread und # 70941 gelesen habe, denke ich, dass die Zusammenfassung wie folgt aktualisiert werden sollte:

  1. "Auflösen, ob Catch-Blöcke den Ergebniswert" umbrechen "sollen" sollte angekreuzt werden, "Als Ja aufgelöst "

  2. Neue Besorgnis über diese Inferenzprobleme hinzugefügt. Vielleicht so etwas wie:

    • [] Ergonomische Schwierigkeiten aufgrund von Problemen mit der Typinferenz.

ISTM, dass dieses letzte Problem unter anderem angegangen werden könnte:

  • Fügen Sie try vor der Stabilisierung eine maßgeschneiderte Verschlüsselungssyntax hinzu
  • Entscheiden Sie, dass https://github.com/rust-lang/rfcs/pull/803 # 23416 (Typzuweisung) dies behebt und zuerst stabilisiert werden sollte
  • Fügen Sie eine Art automatisches Fallback hinzu (z. B. zu Result , wie in https://github.com/rust-lang/rust/issues/31436#issuecomment -614735806 vorgeschlagen)
  • Entscheiden Sie sich dafür, try unverändert zu stabilisieren und lassen Sie syntaktischen / semantischen Raum für zukünftige Verbesserungen

(Einige dieser Optionen schließen sich nicht gegenseitig aus.)

Vielen Dank für Ihre Aufmerksamkeit und ich hoffe, Sie finden diese Nachricht hilfreich.

Die Typzuweisung weist eine Reihe von Problemen auf (syntaktisch und anderweitig) und wird wahrscheinlich nicht bald implementiert, geschweige denn stabilisiert. Das Blockieren von try -Blöcken in der Typzuschreibungssyntax erscheint nicht angemessen.

Ein Fallback auf Result könnte helfen, löst aber nicht die Typinferenzprobleme mit Fehlertypen: try { expr? }? (oder in der Praxis komplexere Äquivalente) haben effektiv zwei Aufrufe an .into() Dies gibt dem Compiler zu viel Flexibilität beim Zwischentyp.

@ijackson danke, dass Sie die Initiative ergriffen haben, um den aktuellen Status zusammenzufassen. Ich denke, Sie haben Recht, dass es verschiedene Möglichkeiten gibt, wie wir Try-Blöcke verbessern können, aber eines der Probleme ist, dass wir uns nicht sicher sind, welche wir tun sollen, auch weil jede Lösung ihre eigenen Nachteile hat.

In Bezug auf die Typzuweisung habe ich jedoch das Gefühl, dass die Implementierungsherausforderungen dort nicht so schwierig sind. Das könnte ein guter Kandidat sein, um etwas Aufmerksamkeit zu erregen und zu versuchen, es trotzdem über die Ziellinie zu schieben. Ich kann mich nicht erinnern, ob die Syntax oder ähnliches kontrovers diskutiert wurde.

Am Mittwoch, den 05. August 2020 um 14:29:06 Uhr -0700 schrieb Niko Matsakis:

In Bezug auf die Typzuweisung habe ich jedoch das Gefühl, dass die Implementierungsherausforderungen dort nicht so schwierig sind. Das könnte ein guter Kandidat sein, um etwas Aufmerksamkeit zu erregen und zu versuchen, es trotzdem über die Ziellinie zu schieben. Ich kann mich nicht erinnern, ob die Syntax oder ähnliches kontrovers diskutiert wurde.

Soweit ich mich erinnere, war das Hauptanliegen, die Typzuweisung zuzulassen
Überall wäre eine wesentliche Grammatikänderung und möglicherweise eine
Begrenzung eines. Ich erinnere mich nicht an alle Details, nur dass die Sorge war
angehoben.

Persönlich denke ich, dass die ergonomischen Probleme nicht so schlimm sind, dass es sich nicht lohnt, diese Funktion jetzt zu stabilisieren. Selbst ohne die Zuweisung eines Ausdruckstyps ist die Einführung einer Let-Bindung keine so hässliche Problemumgehung.

Außerdem können try -Blöcke in Makros nützlich sein. Insbesondere frage ich mich, ob die exzellente Fehlerbibliothek @withoutboats unter weniger Problemen mit Mängeln in unserem Makrosystem leiden würde, wenn sie die Prozesskörper in try einwickeln könnte.

Ich stoße auf Orte, an denen ich gerne Try-Blöcke verwenden würde. Es wäre gut, dies über die Linie zu bringen. Persönlich würde ich absolut 100% Opfer Typ Zuschreibung, wenn es erforderlich wäre, Try-Blöcke über die Linie zu bekommen. Ich habe mich noch nicht in einer Situation befunden, in der ich sagte "Verdammt, ich würde gerne hier eine Typzuordnung haben", aber am Ende mache ich ein IIFE, um Versuchsblöcke viel zu simulieren. Eine langfristige, nützliche Funktion instabil zu lassen, weil sie mit einer anderen langfristigen, instabilen Funktion in Konflikt steht, ist eine wirklich unglückliche Situation.

Um etwas genauer zu sein, mache ich dies, wenn ich mich in einer Funktion befinde, die Ergebnis zurückgibt, aber ich möchte eine Art Verarbeitung für Dinge durchführen, die eine Option zurückgeben. Das heißt, wenn Try im Allgemeinen stabil wäre, würde ich wahrscheinlich immer noch Try-Blöcke bevorzugen, da ich nicht wirklich von der Hauptfunktion zurückkehren möchte, sondern stattdessen einen Standardwert angeben möchte, wenn irgendetwas in der Kette ist Keiner. Dies passiert bei mir im Serialisierungsstil.

Persönlich wollte ich viel häufiger eine Typzuweisung als einen Versuchsblock (obwohl ich manchmal beides wollte). Insbesondere habe ich oft mit "Type Debugging" zu kämpfen, bei dem der Compiler einen anderen Typ als erwartet ableitet. Normalerweise müssen Sie irgendwo eine neue Let-Bindung hinzufügen, was sehr störend ist und dazu führt, dass rustfmt den Rückgängig-Verlauf unterbricht. Darüber hinaus gibt es viele Orte, an denen eine Typzuweisung einen zusätzlichen Turbofisch vermeiden würde.

Im Gegensatz dazu kann ich einfach and_then oder andere Kombinatoren verwenden, um vorzeitig zu beenden, ohne zu beenden. Vielleicht nicht so sauber wie ein Versuch, aber auch nicht so schlimm.

@steveklabnik @ mark-im Try-Blöcke und Typzuweisung stehen in keiner Weise in Konflikt, es handelt sich nicht um die eine oder andere Funktion. Es sind nur try Blöcke, die unergonomische Typinferenzfehler aufweisen, und eine verallgemeinerte Typzuweisung könnte eine Möglichkeit sein, dieses Problem zu lösen, aber da eine verallgemeinerte Typzuweisung kein kurzfristiges Merkmal oder gar eine sichere Sache ist, @joshtriplett (und ich) zustimmen) möchte nicht, dass diese Funktion die allgemeine Typzuweisung blockiert.

Dies bedeutet nicht einmal, dass wir die allgemeine Typzuweisung nicht zur Lösung des Problems machen würden. Eine Option, die untersucht werden sollte, ist "den Versuch so zu stabilisieren, wie er ist, in der Erwartung, dass eines Tages eine allgemeine Typzuweisung dieses Problem lösen wird". Alles, was gesagt wurde, ist, die Stabilisierung bei der Typzuweisung nicht zu blockieren.


@ rust-lang / lang Ich muss zugeben, dass es aufgrund der Einschränkungen von GitHub und der vielen anderen Themen, die hier behandelt werden, etwas schwierig ist, die Nuance des Typinferenzfehlers in diesem Thread zu verstehen. Da eine Entscheidung über den Umgang mit Inferenzfehlern das einzige ist, was die Stabilisierung von Versuchsblöcken verhindert, wäre es meiner Meinung nach von Vorteil, wenn wir ein Treffen hätten, um dies zu diskutieren, und jemand könnte darauf hinweisen, ein tiefes Verständnis für das Inferenzproblem zu erlangen .

Eine Frage, die mir zum Beispiel einfällt: Liegt dieses Inferenzproblem speziell an der Konvertierungsflexibilität, die wir in Try ? Ich weiß, dass die Entscheidung zu Tode diskutiert wurde, aber wenn dem so ist, scheint dies eine relevante neue Information zu sein, die eine Änderung der Definition des Merkmals Try rechtfertigen könnte.

@withoutboats Ich stimme der Notwendigkeit zu, alle Informationen an einem Ort zu sammeln, und dem Wunsch, diese Funktion über die Ziellinie zu bringen. Das heißt, ich denke, als wir das letzte Mal hier nachforschten, wurde auch klar, dass Änderungen an Try aufgrund der Abwärtskompatibilität schwierig sein könnten - @cramertj erwähnte einige spezifische Pin Impls, IIRC.

War diese Seite hilfreich?
0 / 5 - 0 Bewertungen