Rust: Verfolgungsproblem für RFC 1937: „?“ in „main“.

Erstellt am 18. Juli 2017  ·  183Kommentare  ·  Quelle: rust-lang/rust

Dies ist ein Verfolgungsproblem für den RFC " ? in main " (rust-lang/rfcs#1937).

Schritte:

Stabilisierungen:

  • [x] main mit Nicht-()-Rückgabetypen stabilisieren (https://github.com/rust-lang/rust/issues/48453) Zusammengeführt in https://github.com/rust-lang/ rost/ziehen/49162
  • [x] Einheitentests mit Nicht-()-Rückgabetypen stabilisieren (https://github.com/rust-lang/rust/issues/48854)

Verwandte Themen:

  • [x] Fehlermeldung für Unit-Tests ist nicht toll (https://github.com/rust-lang/rust/issues/50291)

Ungelöste Fragen:

B-RFC-approved C-tracking-issue E-mentor T-compiler T-lang WG-compiler-middle

Hilfreichster Kommentar

Entschuldigen Sie, dass ich mich an einem Punkt einschalte, an dem es wahrscheinlich zu spät ist, etwas dagegen zu unternehmen, aber ich wollte hier mein Feedback hinterlassen, falls dies der Fall ist. Ich habe den größten Teil dieses Threads gelesen, also spreche ich mit diesem Kontext im Hinterkopf. Dieser Thread ist jedoch lang. Wenn es also so aussieht, als hätte ich etwas übersehen, dann habe ich es wahrscheinlich getan, und ich würde es begrüßen, wenn ich darauf hingewiesen würde. :-)

TL;DR - Ich denke, das Anzeigen der Debug -Meldung eines Fehlers war ein Fehler, und es wäre eine bessere Wahl, die Display -Meldung eines Fehlers zu verwenden.

Der Kern meiner Überzeugung ist, dass ich mich als jemand, der _routinemäßig CLI-Programme in Rust_ erstellt, nicht erinnern kann, dass ich mich jemals groß darum gekümmert habe, was die Debug -Nachricht eines Error ist. Die Debug eines Fehlers sind nämlich per Design für Entwickler, nicht für Endbenutzer. Wenn Sie ein CLI-Programm erstellen, ist seine Schnittstelle grundsätzlich dazu bestimmt, von Endbenutzern gelesen zu werden, daher hat eine Debug -Nachricht hier sehr wenig Nutzen. Das heißt, wenn ein CLI-Programm, das ich an Endbenutzer auslieferte, im normalen Betrieb die Debug-Darstellung eines Rust-Werts zeigte, dann würde ich dies als einen zu behebenden Fehler betrachten. Ich denke im Allgemeinen, dass dies für jedes in Rust geschriebene CLI-Programm gelten sollte, obwohl ich verstehe, dass dies ein Punkt sein kann, in dem vernünftige Leute anderer Meinung sein können. Abgesehen davon ist meiner Meinung nach eine etwas verblüffende Implikation, dass wir ein Feature effektiv stabilisiert haben, dessen Standardbetriebsmodus Sie mit einem Fehler (wiederum, meiner Meinung nach) beginnt, der behoben werden sollte.

Indem standardmäßig die Debug -Darstellung eines Fehlers angezeigt wird, glaube ich auch, dass wir schlechte Vorgehensweisen fördern. Insbesondere beim Schreiben eines Rust-CLI-Programms wird häufig beobachtet, dass selbst die Display Impl eines Fehlers nicht gut genug sind, um von Endbenutzern verarbeitet zu werden, und dass echte Arbeit geleistet werden muss repariere es. Ein konkretes Beispiel dafür ist io::Error . Das Anzeigen io::Error ohne entsprechenden Dateipfad (vorausgesetzt, es stammt vom Lesen/Schreiben/Öffnen/Erstellen einer Datei) ist im Grunde ein Fehler, da es für einen Endbenutzer schwierig ist, etwas damit zu tun. Indem wir uns dafür entschieden haben, standardmäßig die Debug -Darstellung eines Fehlers anzuzeigen, haben wir es schwieriger gemacht, diese Art von Fehlern von Leuten aufzudecken, die CLI-Programme erstellen. (Außerdem ist das Debug eines io::Error viel weniger nützlich als das Display , aber das allein ist meiner Erfahrung nach kein großer Schmerzpunkt. )

Um mein Argument abzurunden, fällt es mir schließlich auch schwer, mir die Umstände vorzustellen, unter denen ich ?-in-main selbst in Beispielen verwenden würde. Ich habe nämlich versucht, Beispiele zu schreiben, die realen Programmen so genau wie möglich entsprechen, und dazu gehörte im Allgemeinen das Schreiben von Dingen wie diesen:

use std::error::Error;
use std::process;

fn try_main() -> Result<(), Box<Error>> {
    // do stuff with `?`
}

fn main() {
    if let Err(err) = try_main() {
        eprintln!("{}", err);
        process::exit(1);
    }
}

Auf den ersten Blick wäre es _schön_, dies durch ?-in-main zu ersetzen, aber ich kann nicht, weil es die Display eines Fehlers nicht anzeigt. Das heißt, wenn ich ein echtes CLI-Programm schreibe, werde ich tatsächlich den obigen Ansatz verwenden. Wenn ich also möchte, dass meine Beispiele die Realität widerspiegeln, dann sollte ich meiner Meinung nach zeigen, was ich in echten Programmen mache, und keine Abkürzungen nehmen (in einem vernünftigen Umfang). ). Ich denke, dass so etwas wirklich wichtig ist, und ein historischer Nebeneffekt davon war, dass es den Leuten gezeigt hat, wie man idiomatischen Rust-Code schreibt, ohne überall unwrap zu verstreuen. Aber wenn ich in meinen Beispielen wieder ?-in-main verwende, dann habe ich gerade mein Ziel verfehlt: Ich richte jetzt Leute ein, die es vielleicht nicht besser wissen, einfach Programme zu schreiben, die standardmäßig sehr emittieren nicht hilfreiche Fehlermeldungen.

Das Muster "Fehlermeldung ausgeben und mit entsprechendem Fehlercode beenden" wird tatsächlich in ausgefeilten Programmen verwendet. Wenn beispielsweise ?-in-main Display verwendet, dann könnte ich heute die Funktionen main und run in ripgrep zusammenführen:

https://github.com/BurntSushi/ripgrep/blob/64317bda9f497d66bbeffa71ae6328601167a5bd/src/main.rs#L56 -L86

Natürlich könnte ich in Zukunft ?-in-main verwenden, indem ich mein eigenes Impl für das Merkmal Termination bereitstelle, sobald sich das stabilisiert, aber warum sollte ich mir die Mühe machen, wenn ich einfach die main schreiben könnte impl in die Beispiele einfügen, damit sie der Realität entsprechen, und an diesem Punkt könnte ich genauso gut bei den Beispielen bleiben, die ich heute habe (mit main und ein try_main ).

So wie es aussieht , wäre es eine bahnbrechende Änderung, dies zu beheben. Das heißt, dieser Code wird heute auf Rust Stable kompiliert:

#[derive(Debug)]
struct OnlyDebug;

fn main() -> Result<(), OnlyDebug> {
    Err(OnlyDebug)
}

Ich denke, ein Wechsel zu Display würde diesen Code brechen. Aber ich weiß es nicht genau! Wenn dies wirklich ein Problem ist, das ein Schiff gesegelt hat, dann verstehe ich, und es hat keinen allzu großen Sinn, den Punkt zu vertiefen, aber ich fühle mich stark genug, um zumindest etwas zu sagen und zu sehen, ob ich andere nicht überzeugen und sehen kann wenn es etwas gibt, was getan werden kann, um es zu beheben. (Es ist auch gut möglich, dass ich hier überreagiere, aber ich wurde bisher von ein paar Leuten gefragt: "Warum verwendest du nicht ?-in-main ?" in meinen CSV-Beispielen, und meine Antwort war im Grunde: „Ich sehe nicht, wie es jemals machbar wäre, das zu tun.“ Vielleicht war das kein Problem, das von ?-in-main gelöst werden sollte, aber einige Leute hatten sicherlich diesen Eindruck , ich könnte sehen, dass es in Dokumenttests und Komponententests nützlich ist, aber es fällt mir schwer, an andere Situationen zu denken, in denen ich es verwenden würde.)

Alle 183 Kommentare

Wie wird mit Austrittsstatus umgegangen?

Dieser Kommentar von @Screwtapello scheint zu kurz vor dem Ende von FCP gemacht worden zu sein, als dass Änderungen am RFC als Reaktion darauf vorgenommen werden könnten.

Kurz gesagt: Der RFC schlägt vor, 2 bei Fehlschlagen aus Gründen zurückzugeben, die zwar gut begründet, aber obskur sind und zu einem etwas ungewöhnlichen Ergebnis führen; Am wenigsten überraschend ist es, 1 zurückzugeben, wenn das Programm keinen Hinweis darauf hat, dass es mehr Details als nur Erfolg oder Misserfolg haben möchte. Ist dies ausreichend bikesheddy, dass es diskutiert werden kann, ohne dass es sich anfühlt, als würden wir den RFC-Prozess pervertieren, oder sind wir jetzt in diesem spezifischen Implementierungsdetail gefangen?

Es ist aber kein Implementierungsdetail, oder?

Einige Skripte verwenden Exit-Codes, um Informationen von einem Unterprozess abzurufen.

Dies ist insbesondere der Fall, wenn ein Teilprozess (in Rust implementiert) keine Informationen zu geben hat, die über ein binäres "alles in Ordnung" / "etwas ist schief gelaufen" hinausgehen.

Einige Skripte verwenden Exit-Codes, um Informationen von einem Unterprozess abzurufen.

Dieses Verhalten hängt immer stark davon ab, ob das Programm _außer_ aufgerufen wird, da Nicht-Null Fehler bedeutet. Angesichts der Tatsache, dass std::process::exit mit einem Hauptfunktions-Wrapper und einer Nachschlagetabelle die beste Option für diejenigen bleiben wird, die einen deutlicheren Exit-Status wünschen, egal was getan wird, scheint dies ein größtenteils unbedeutendes Detail zu sein.

Ich glaube jedoch nicht, dass SemVer eine Ausnahme für "meist unbedeutende Details" hat.

Ich denke, der Exit-Code sollte der Liste der ungelösten Fragen hinzugefügt werden. @zackw hat auch einen verwandten internen Thread geöffnet.

Viele Leute sind sich einig, dass der Exit-Code bei einem Fehler 1 sein sollte (statt 2 ):
https://www.reddit.com/r/rust/comments/6nxg6t/the_rfc_using_in_main_just_got_merged/

@arielb1 wirst du diesen RFC implementieren?

@bkchr

Nein, nur um es zu betreuen. Ich habe zugeteilt, damit ich nicht vergesse, die Mentoring-Notizen zu schreiben.

Ahh schön, das würde mich interessieren :)
Aber ich habe keine Ahnung, wo ich anfangen soll :D

@bkchr

Darum bin ich hier :-). Ich sollte die Mentoring-Anweisungen früh genug schreiben.

Okay, dann warte ich auf deine Anweisungen.

Mentoring-Anweisungen

Dies ist ein [WG-compiler-middle]-Problem. Wenn Sie Hilfe suchen möchten, können Sie #rustc auf irc.mozilla.org (ich bin arielby) oder https://gitter.im/rust-impl-period/WG-compiler-middle (ich bin @arielb1 dort).

Es gibt eine WIP-Compiler-Readme unter #44505 – sie beschreibt einige Dinge im Compiler.

Arbeitsplan für diesen RFC:

  • [ ] - füge das Termination lang-Element zu libcore hinzu
  • [ ] - erlaubt die Verwendung Termination in main
  • [ ] - erlaubt die Verwendung Termination in Dokumententests
  • [ ] - erlaubt die Verwendung Termination in #[test]

füge das Termination lang-Element zu libcore hinzu

Zuerst müssen Sie das Merkmal $#$ Termination $#$ zusammen mit etwas Dokumentation zu libcore/ops/termination.rs hinzufügen. Sie müssen es auch mit einem #[unstable(feature = "termination_trait", issue = "0")] -Attribut als instabil markieren - dies verhindert, dass Benutzer es verwenden, bevor es stabilisiert ist.

Dann müssen Sie es als lang-item in src/librustc/middle/lang_items.rs markieren. Das bedeutet, dass der Compiler es bei der Typprüfung main herausfinden kann (zB siehe 0c3ac648f85cca1e8dd89dfff727a422bc1897a6).
Das bedeutet:

  1. Hinzufügen zur Langartikelliste (in librustc/middle/lang_items.rs )
  2. Hinzufügen eines #[cfg_attr(not(stage0), lang = "termination")] zum Merkmal Termination . Der Grund, warum Sie nicht einfach ein #[lang = "termination"] -Attribut hinzufügen können, liegt darin, dass der „stage0“-Compiler (während des Bootstrappings) nicht weiß, dass termination existiert, also wird er es nicht können libstd kompilieren. Wir werden cfg_attr manuell entfernen, wenn wir den Stage0-Compiler aktualisieren.
    Weitere Informationen finden Sie in der Bootstrapping-Dokumentation unter XXX.

erlaubt die Verwendung Termination in main

Das ist der interessante Teil, mit dem ich umzugehen weiß. Dies bedeutet, dass ein main erstellt wird, das () ohne Typprüfung zurückgibt (derzeit erhalten Sie einen main function has wrong type -Fehler) und funktioniert.

Um eine Typprüfung durchzuführen, müssen Sie zuerst den vorhandenen Fehler entfernen in:
https://github.com/rust-lang/rust/blob/9a00f3cc306f2f79bfbd54f1986d8ca7a74f6661/src/librustc_typeck/lib.rs#L171 -L218

Dann müssen Sie eine Überprüfung hinzufügen, dass der Rückgabetyp das Merkmal Termination implementiert (Sie fügen eine Merkmalsverpflichtung mit register_predicate_obligation hinzu – suchen Sie nach Verwendungen davon). Das kann man hier machen:
https://github.com/rust-lang/rust/blob/9a00f3cc306f2f79bfbd54f1986d8ca7a74f6661/src/librustc_typeck/check/mod.rs#L1100 -L1108

Der andere Teil sorgt dafür, dass es funktioniert. Das sollte ziemlich einfach sein. Wie der RFC sagt, möchten Sie lang_start über den Rückgabetyp generisch machen.

lang_start ist derzeit hier definiert:
https://github.com/rust-lang/rust/blob/9a00f3cc306f2f79bfbd54f1986d8ca7a74f6661/src/libstd/rt.rs#L32

Sie müssen es also so ändern, dass es generisch ist und mit dem RFC übereinstimmt:

#[lang = "start"]
fn lang_start<T: Termination>
    (main: fn() -> T, argc: isize, argv: *const *const u8) -> !
{
    use panic;
    use sys;
    use sys_common;
    use sys_common::thread_info;
    use thread::Thread;

    sys::init();

    sys::process::exit(unsafe {
        let main_guard = sys::thread::guard::init();
        sys::stack_overflow::init();

        // Next, set up the current Thread with the guard information we just
        // created. Note that this isn't necessary in general for new threads,
        // but we just do this to name the main thread and to give it correct
        // info about the stack bounds.
        let thread = Thread::new(Some("main".to_owned()));
        thread_info::set(main_guard, thread);

        // Store our args if necessary in a squirreled away location
        sys::args::init(argc, argv);

        // Let's run some code!
        let exitcode = panic::catch_unwind(|| main().report())
            .unwrap_or(101);

        sys_common::cleanup();
        exitcode
    });
}

Und dann müssen Sie es von create_entry_fn aufrufen. Derzeit instanziiert es ein monomorphes lang_start mit Instance::mono , und Sie müssen es ändern, um monomorphize::resolve mit den richtigen Substs zu verwenden.

https://github.com/rust-lang/rust/blob/9a00f3cc306f2f79bfbd54f1986d8ca7a74f6661/src/librustc_trans/base.rs#L697

erlaubt die Verwendung Termination in Dokumententests

Ich verstehe nicht wirklich, wie Doctests funktionieren. Vielleicht fragen Sie @alexcrichton (das würde ich tun)?

erlaubt die Verwendung Termination in #[test]

Ich verstehe nicht wirklich, wie libtest funktioniert. Vielleicht fragen Sie @alexcrichton (das würde ich tun)? Komponententests werden grundsätzlich von einem Makro generiert, daher müssen Sie dieses Makro oder seinen Aufrufer ändern, um Rückgabetypen zu verarbeiten, die nicht () sind.

@bkchr

Kannst du wenigstens dem IRC/gitter beitreten?

@bkchr checkt gerade ein -- ich habe gesehen, dass du und @arielb1 vor einiger Zeit über gitter gesprochen habt, irgendwelche Fortschritte? Irgendwo saugen?

Nein, tut mir leid, bis jetzt kein Fortschritt. Momentan habe ich viel zu tun, aber ich hoffe, dass ich diese Woche etwas Zeit finde, um damit anzufangen.

@bkchr Wenn du Hilfe brauchst, lass es mich wissen!

Ich stecke gerade etwas fest, ich möchte die Obligation erstellen. Um die Obligation zu erstellen benötige ich eine TraifRef, für eine TraitRef benötige ich eine DefId. Kann mir jemand einen Code zeigen, wie man eine DefId aus dem Termination Trait erstellt?

@bkchr Das Merkmal sollte der Lang-Items-Liste hinzugefügt werden, z
und mit #[termination_trait] markiert werden, z

Ja das ist nicht das Problem, das habe ich schon gemacht. Ich muss in der Funktion check_fn nach dem Beendigungsmerkmal suchen. Ich möchte register_predicate_obligation verwenden und dafür brauche ich das defid des Terminierungsmerkmals.

Oh, dann brauchst du nur tcx.require_lang_item(TerminationTraitLangItem) .

@bkchr wie geht das? Einfach nochmal reinschauen. =) Keine Sorge, wenn Sie beschäftigt sind, möchten Sie nur sicherstellen, dass Sie alle Hilfe bekommen, die Sie brauchen.

Tut mir leid, ich bin gerade beschäftigt :/ Bis jetzt habe ich alle Hilfe bekommen, die ich brauchte :)

Dies ist der Code, um das TerminationTrait zu überprüfen: https://github.com/bkchr/rust/blob/f185e355d8970c3350269ddbc6dfe3b8f678dc44/src/librustc_typeck/check/mod.rs#L1108

Ich denke, dass ich den Rückgabetyp der Funktion nicht überprüfe? Ich bekomme folgenden Fehler:

error[E0277]: the trait bound `Self: std::ops::Termination` is not satisfied
  --> src/rustc/rustc.rs:15:11
   |
15 | fn main() { rustc_driver::main() }
   |           ^^^^^^^^^^^^^^^^^^^^^^^^ the trait `std::ops::Termination` is not implemented for `Self`
   |
   = help: consider adding a `where Self: std::ops::Termination` bound

Was muss ich ändern, um den Rückgabetyp der Funktion zu überprüfen?

@bkchr Ich würde empfehlen, der Compiler-Middle-Arbeitsgruppe Gitter unter https://gitter.im/rust-impl-period/WG-compiler-middle beizutreten, um Feedback zu erhalten, sowie den IRC-Kanal #rust-internals unter https auszuprobieren ://chat.mibbit.com/?server=irc.mozilla.org%3A%2B6697&channel=%23rust-internals . :)

@bstrie ja danke, ich bin schon im gitter-chat dabei und konnte mein problem lösen. :)

@bkchr Ihr Problem liegt in dieser Zeile . Die Trait-Referenz, die Sie dort erstellen möchten, ist etwa R: Termination , wobei R der Rückgabetyp der Funktion ist. Dies wird durch den Aufbau geeigneter "substs" angegeben, bei denen es sich um den Satz von Werten handelt, die die Typparameter des Merkmals ersetzen (in diesem Fall Self ).

Sie rufen jedoch die Methode Substs::identity_for_item für die Eigenschaft auf. Dadurch erhalten Sie die Ersetzungen zurück, die man innerhalb der Eigenschaftsdefinition selbst verwenden würde. dh in diesem Fall ordnen Sie den Parameter Self , der für die Eigenschaft Termination deklariert wurde, Self zu. Dies wäre angemessen, wenn Sie die Definition einer Funktion innerhalb des Merkmals Terminator überprüfen würden, aber nicht so sehr hier.

Was Sie stattdessen wollen, ist, den Rückgabetyp der Eintragsfunktion zu erhalten. Dies ist nur eine der Variablen ret_ty oder actual_ret_ty . Beides ist in Ordnung, aber ich denke, ret_ty ist besser - das entspricht dem Rückgabetyp, den der Benutzer deklariert hat (wobei actual_ret_ty der Typ ist, den der tatsächliche Code zurückgegeben hat).

Sie können die gewünschten Substs erstellen, indem Sie einfach die Methode mk_substs() aus dem tcx aufrufen. In diesem Fall gibt es nur einen Parameter, den Typ, also würde etwas wie let substs = fcx.tcx.mk_substs(&[ret_ty]); funktionieren, denke ich.

Ich glaube, das zu verwendende Ding ist tcx.mk_substs_trait(ret_ty, &[]) .

@bkchr checkt gerade ein - hattest du die Möglichkeit, diesen Rat zu verwenden? (Außerdem kann es für schnellere Antworten ratsam sein , auf gitter zu fragen .)

Ja, ich konnte das Problem mit Gitter lösen :)

@bkchr wie geht das? Einfach einchecken.

Alles okay, ich werde wohl diese Woche etwas Zeit finden, um in den Code zu schauen.

Gibt es Platz für eine weitere Person, die dabei hilft? Ich möchte noch vor Jahresende einen Beitrag zur Rust-Community leisten und würde gerne bei dieser Funktion helfen. Hoffentlich wäre es nicht zu verwirrend, wenn zwei Leute daran zusammenarbeiten.

@U007D

Dies ist ein kleines Feature und @bkchr ist fast fertig damit.

Ah, ok, das ist gut zu wissen, danke. Ich werde nach etwas anderem Ausschau halten, bei dem ich helfen kann.

@U007D Hast du https://www.rustaceans.org/findwork gesehen?

@lnicola Ja, das habe ich! Ich versuche, etwas an der Schnittstelle von etwas zu finden, an dem ich zuversichtlich bin, daran arbeiten zu können (z. B. ein Nettopositiv zu sein) und für das ich mich leidenschaftlich interessiere. Um ehrlich zu sein, obwohl ich Rust seit ungefähr einem Jahr lerne, ist es immer noch ein wenig einschüchternd, sich freiwillig für etwas zu melden. FWIW, das ist keineswegs die Schuld der Rust-Community – die Rust-Community hat sich nach hinten gebeugt, um dies zu einer offenen, einladenden und integrativen Kultur zu machen – das Beste, was ich je erleben durfte. (Ich vermute, es hat mehr mit alten Kampfnarben aus jahrelanger Erfahrung in der Technologiebranche zu tun, in der Teams eher wettbewerbsfähig als kooperativ sind.)

Mein Ziel ist es jedenfalls, dieses Jahr etwas herauszupicken und zumindest ansatzweise einen positiven Beitrag zu leisten. Es ist Zeit für mich, mich zu engagieren! :)

Danke für den Vorschlag, @lnicola. Das ist eine gute Ressource.

@bkchr irgendwelche Updates?

Ich bin dabei (https://github.com/rust-lang/rust/pull/46479). Jetzt habe ich Urlaub und Zeit zum Arbeiten in den Kommentaren im Pull Request. Sorry für die ganzen Verzögerungen :/

Oh, tut mir leid, ich habe nicht bemerkt, dass Sie eine Pull-Anforderung hatten. Habe es vernetzt.

Hallo, ähm. Also dachte ich, ich beginne meine Karriere als potenzieller Rust-Mitarbeiter mit Bikeshedding, wie es Tradition ist. Konkret zu diesem hier:

  • [ ] Der Name des eingeführten Merkmals

Wie wäre es mit Exit ? Es ist kurz und prägnant und passt zum bestehenden Rust-Vokabular. Exit-as-a-Substantiv ist ein natürliches Gegenstück zu exit-as-a-Verb, das für die meisten das vertraute Wort ist, um einen Prozess "von innen" auf kontrollierte Weise zu beenden.

Speziell für einen C++-Programmierer erinnert "Beendigung" an std::terminate , das standardmäßig eine abnormale Beendigung (Aufruf abort ) vornimmt und im Grunde das C++-Äquivalent zu einer Panik ist (aber im Gegensatz zu einer Panik niemals die Stapel).

Warten Sie, ignorieren Sie diesen Kommentar, es sieht so aus, als ob der RFC das ausdrücklich offen für Diskussionen hinterlassen hat.

Ich mag Exit als Eigenschaftsnamen.

Ich gehe davon aus, dass das Feature weit vor dem Trait stabilisiert wird, wie es bei Carrier passiert ist.

FWIW, das ist ein weiterer Fall, wo ich wirklich froh bin, dass der vorläufige Name vor der Stabilisierung geändert wurde :D

Als Autor des RFC habe ich nichts dagegen, den Namen des Merkmals in Exit oder etwas anderes zu ändern. Ich bin nicht besonders gut darin, Dinge zu benennen und freue mich, wenn jemand anderes eine bessere Idee hat.

https://github.com/rust-lang/rust/blob/5f7aeaf6e2b90e247a2d194d7bc0b642b287fc16/src/libstd/lib.rs#L507

Soll die Eigenschaft sein

  1. platziert in libstd anstelle von libcore und
  2. gerade std::Termination genannt, nicht std::ops::Termination ?

Die Eigenschaft konnte nicht in libcore platziert werden, da die Implementierung für Result erfordert, dass in stderr gedruckt wird und dies in libcore nicht möglich ist.

@bkchr Dass impl in libstd ist, bedeutet nicht, dass die Eigenschaft auch in libstd sein muss.

@kennytm Ich weiß, aber Result ist auch in libcore definiert, daher kann Termination für Result in libstd nicht implementiert werden.

@zackw +1 weitere Stimme für Exit als Eigenschaftsname.

@U007D : Könntest du bitte die Reaktionsschaltfläche (z. B. 👍) verwenden, anstatt eine solche Nachricht zu posten? Auf diese Weise können Sie vermeiden, Abonnenten mit Problemen zu nerven, indem Sie sie unnötig anpingen.

Kann ich libtest / libsyntax einchecken, wenn ein language_feature aktiviert ist (in einer Kiste)? @arielb1 @nikomatsakis @alexcrichton

@bkchr in libsyntax müssen Sie es möglicherweise übergeben, aber es ist theoretisch möglich, aber in libtest selbst zur Laufzeit glaube ich nicht, dass Sie es überprüfen können.

@bkchr wie geht das hier?

Ich arbeite noch daran, aber momentan habe ich keine weiteren Fragen :)

Ich denke, diese Impl ist zu streng:

#[unstable(feature = "termination_trait", issue = "43301")]
impl<T: Termination, E: Error> Termination for Result<T, E> {
    fn report(self) -> i32 {
        match self {
            Ok(val) => val.report(),
            Err(err) => {
                print_error(err);
                exit::FAILURE
            }
        }
    }
}


#[unstable(feature = "termination_trait", issue = "43301")]
fn print_error<E: Error>(err: E) {
    eprintln!("Error: {}", err.description());

    if let Some(ref err) = err.cause() {
        eprintln!("Caused by: {}", err.description());
    }
}

Es gibt mehrere häufig verwendete Fehler, die Error nicht implementieren, vor allem Box<::std::error::Error> und failure::Error . Ich denke auch, dass es ein Fehler ist, die Methode description anstelle der Anzeige impl dieses Fehlers zu verwenden.

Ich würde vorschlagen, dieses Impl durch dieses breitere Impl zu ersetzen:

#[unstable(feature = "termination_trait", issue = "43301")]
impl<T: Termination, E: Display> Termination for Result<T, E> {
    fn report(self) -> i32 {
        match self {
            Ok(val) => val.report(),
            Err(err) => {
                eprintln!("Error: {}", err)
                exit::FAILURE
            }
        }
    }
}

Das verliert die Ursachenkette, was ein Mist ist.

Es ist jedoch definitiv besser, die Anzeige Impl anstelle der Beschreibung zu verwenden.

Die Ursachenkette ist ein interessantes Problem. Insbesondere gibt diese Implementierung nur die ersten beiden Mitglieder der Ursachenkette aus.

Fehler musste sich mit der Behandlung der Ursachenkette auseinandersetzen und sich standardmäßig auf dieses Verhalten festlegen (z. B. wenn Sie nur Fehler mit .context aufbauen:

  • {} gibt nur diesen Fehler aus
  • {:?} gibt diesen Fehler sowie seine Ursache aus (rekursiv)

Wir könnten uns entscheiden, hier :? zu verwenden und es Debug statt Display zu binden. Unsicher.

Ja, ich weiß bereits, dass ich das Impl verbessern muss, um es zu unterstützen. Ich bin offen für das, was wir tun könnten. Eine Bindung an Debug könnte eine gute Idee sein.

Hmm, das ist eine knifflige Frage. Ich schätze, es hängt davon ab, ob wir glauben, dass ein "ausgefeiltes" Programm von dieser Eigenschaft impl Gebrauch machen wird. Ich neige dazu zu sagen, dass es in Ordnung ist zu sagen, dass "nein, das werden sie nicht" - im Grunde genommen wird ein ausgefeiltes Programm entweder (a) die Ausgabe abfangen und auf andere Weise damit umgehen oder (b) einen neuen Typ oder etwas verwenden, das Debug implementiert der richtige Weg. Das würde bedeuten, dass wir das impl für das Ablegen nützlicher Informationen optimieren können, aber notwendigerweise in der schönsten Form (was wie die Rolle von Debug aussieht).

Es könnte die richtige Wahl sein, dies mit Debug ganz klar auf das Prototyping auszurichten, da ich nicht glaube, dass wir Fehler jemals automatisch auf eine Weise behandeln könnten, die für die meisten Produktionsanwendungsfälle korrekt ist.

@ohneBoote Ich stimme zu.

@nikomatsakis Ich nehme an, du meintest " nicht unbedingt in der schönsten Form"? Wenn ja, ja, ich stimme zu.

Update: Nachdem ich ein paar Tage daran gearbeitet habe, habe ich das umgedreht. Siehe unten.

:+1: auf Debug , hier; Ich mag @nikomatsakis „Art of Analogous to an Uncaught Exception“ von https://github.com/rust-lang/rfcs/pull/1937#issuecomment -284509933. Ein Kommentar von Diggsey, der ebenfalls Debug vorschlägt: https://github.com/rust-lang/rfcs/pull/1937#issuecomment -289248751

Zu Ihrer Information, ich habe das Problem „vollständiger“ vs. „benutzerfreundlicher“ (d. h. Debug vs. Display Merkmalsbindung) umgedreht.

Die TL;DR ist jetzt glaube ich, dass wir die Grenze auf Display setzen sollten (wie im ursprünglichen Beitrag von @withoutboats ), um eine sauberere, zusammenfassende Ausgabe im Fall von "Nichts tun" bereitzustellen.

Hier ist meine Begründung:

Zum RFC-Problem des termination - Traits macht @zackw den zwingenden Punkt , dass Rust das duale panic / Result -System hat, weil panic s für Bugs und Result sind

Natürlich gibt es keinen Standard, der alle zufriedenstellt, also frage ich mich nach dem Prinzip der geringsten Überraschung, welcher Standard besser geeignet ist?

  • Ein Fehler wird oft nicht vom Design behandelt, indem dem Benutzer mitgeteilt werden soll, dass etwas (möglicherweise behebbares) schief gelaufen ist (Datei nicht gefunden usw.). Als solches existiert der Anwendungsfall und könnte vernünftigerweise als üblich angesehen werden, dass der Benutzer die beabsichtigte Zielgruppe ist.

  • Wie @nikomatsakis betonte, kann jeder Entwickler, der das Verhalten ändern möchte, unabhängig von der von uns gewählten Standardeinstellung entweder das Newtype-Muster verwenden oder eine benutzerdefinierte Implementierung in main() entwickeln.

Und schließlich auf der eher subjektiven Seite: Als ich in den letzten Tagen mit dieser Funktion gearbeitet habe, habe ich festgestellt, dass die Ausgabe von Debug bei mir das Gefühl hinterlassen hat, dass sich meine "Rust-App" ungeschliffener anfühlt :

$ foo
Error: Custom { kind: Other, error: StringError("returned Box<Error> from main()") }
$

vs

$ foo
Error: returned Box<Error> from main()
$

Das Merkmal Dispay scheint einfach ein viel zivilisierterer Standard für einen Fehler zu sein (im Gegensatz zu einem Bug), nicht wahr?

@U007D warte, welche dieser beiden Ausgaben bevorzugst du?

(a) Error: Custom { kind: Other, error: StringError("returned Box<Error> from main()") }

oder

(b) Error: returned Box<Error> from main()

Er bevorzugt Option (b).

@nikomatsakis Ursprünglich war ich mit a) Debug als Konzept in meinem Kopf einverstanden, aber nachdem ich ein paar Tage damit gearbeitet und tatsächlich die Ausgabe gesehen habe, bevorzuge ich jetzt b) Display als eine Vorgabe. Ich denke, meine Präferenz für b) würde noch ausgeprägter, wenn ich einen verketteten Fehler modellieren würde.

Ich glaube jedoch nicht, dass "poliert" oder "zivilisiert" das Ziel ist, da ich den Thread so verstanden habe, dass dies bereits hauptsächlich als Beispiel akzeptiert wurde, wobei von den Leuten erwartet wird, dass sie mit zunehmender Reife des Programms eine benutzerdefinierte Handhabung hinzufügen.

In diesen Fällen ist für mich die "wenigste Überraschung" eine entwicklerorientierte Ausgabe, genau wie unwrap .

Würde es sich lohnen, hier über {:#?} zu diskutieren, wenn Bedenken wegen eines langen Fehlers bestehen?

Die Fehlerberichterstattung für Endbenutzer wird für jedes Tool und jeden Anwendungsfall unterschiedlich sein, aber die Fehlerberichterstattung für Entwickler sollte dem ähneln, was Rust in anderen Situationen wie .unwrap() tut. Da es nur einen Standard geben kann und ausgefeilte Software die Ausgabe sowieso überschreiben muss, stimme ich mit Debug dafür, dass der Standard entwicklerorientiert ist.

Ich denke, der Kern dieser Diskussion ist wirklich "Wer ist die Zielgruppe für die Standardnachricht?"

Nehmen wir für einen Moment an, dass wir uns alle einig waren, dass die Standardzielgruppe Entwickler sind. Ich denke, die Standardgrenze Debug wäre eine einfache Wahl.

Lassen Sie uns jetzt für einen Moment sagen, dass wir uns darauf geeinigt haben, dass die Standardzielgruppe der Benutzer ist, dann bin ich hier, respektive, anderer Meinung als einige andere und denke, dass subjektive Qualitäten wie "polnische" und "zivilisierte" Ausgabe eine wichtige Rolle spielen abspielen. Für einige mag die „polnische“ Endbenutzerpräsentation der beste Grund sein, Display zu vermeiden. (Ich teile diese Ansicht nicht zufällig, aber ich verstehe und respektiere sie.)

Also ja, ich kann sicherlich vernünftige Argumente für beide Gruppen als Standardziel sehen. Ich denke, wenn sich ein starker Konsens darüber entwickelt, welches Publikum das Standardziel sein sollte, dann wird die Wahl für die Merkmalsgrenze klar sein (ähm) ... :)

(Ich bin mit diesem ganzen Thema nicht ganz vertraut, aber) ist es nicht vorstellbar, dass es kleine Dienstprogramme geben könnte, für die die Standardfehlerausgabe mit Termination vollkommen ausreichend wäre, vorausgesetzt , sie ist in einem vom Benutzer darstellbaren Format wie Display ? In diesem Fall wäre der einzige Grund, warum Autoren zur „benutzerdefinierten Handhabung“ greifen müssten, wenn wir sie erstellen.

Kann jemand Beispiele dafür geben, wie die Ausgabe in jedem Fall aussieht (ich nehme an, es hängt auch vom jeweiligen E -Typ ab?) und welche Schritte Autoren tatsächlich unternehmen müssen, wenn sie eine "benutzerdefinierte Behandlung" wünschen. stattdessen? Ich gehe oben nur von Hypothesen aus.

(Sieht die Ausgabe buchstäblich so aus wie das, was @U007D oben eingefügt hat? Warum würde es "zurückgegebene Box" drucken?Feld <Fehler>?)

Wie oft ist sogar das Display der Fehlermeldung benutzerfreundlich genug? Zum Beispiel das folgende Programm:

fn main() {
    if let Err(e) = std::fs::File::open("foo") {
        println!("{}", e)
    }
}

gibt folgende Meldung aus:

No such file or directory (os error 2)

Was, würde ich sagen, keine großartige UX ist, insbesondere wenn der Dateiname nicht erwähnt wird. Zumindest nicht, es sei denn, das Programm nimmt buchstäblich einen einzelnen Dateinamen als Eingabe. Andererseits ist es auch keine großartige Entwicklererfahrung , wenn die Quelldatei/Zeilennummer/Stack-Trace fehlt. Die Ausgabe Debug ist offensichtlich eine noch schlechtere Benutzererfahrung und fügt auch keine nützlichen Informationen für den Deceoper hinzu.

Ich versuche also zu sagen, dass weder Debug noch Display großartig sind, ohne den Informationsgehalt der Bibliotheksfehler selbst zu verbessern.

Sieht die Ausgabe buchstäblich so aus wie das, was @U007D oben eingefügt hat? Warum sollte es "Returned Boxfrom main()" anstelle von ... dem tatsächlichen Inhalt dieser Box?

@glaebhoerl Sie haben Recht - in diesem Fall war der "tatsächliche Inhalt dieses Box<Error> " ein benutzerdefiniertes message -Feld, das ich erstellt hatte, um das termination_trait zu testen, das wörtlich angezeigt wurde . Ich hätte stattdessen "foo bar baz" oder etwas anderes hineinschreiben können (aber das wäre für einen Benutzer, der die Compiler-Tests ausführt, möglicherweise nicht so nützlich gewesen).

Unter Verwendung des Beispiels von @jdahlstrom ist hier die tatsächliche Ausgabe für eine Box ed Standardbibliothek "Datei nicht gefunden" Error (beachten Sie, wie Sie richtig darauf hinweisen, dass Boxen nirgendwo erwähnt wird):
Debug :

$ foo
Error { repr: Os { code: 2, message: "No such file or directory" } }
$

und Display :

$ foo
No such file or directory (os error 2)
$

@jdahlstrom Ich denke, Sie machen einen guten Punkt. Ich stimme zu, dass, obwohl beide Nachrichten ihre Zielgruppe möglicherweise nicht erreichen, und ich möchte hervorheben, dass es noch schlimmer ist, die falsche bereitzustellen (wie Sie meiner Meinung nach angedeutet haben):

Die Bereitstellung Display für einen Entwickler hat alle Nachteile von Debug und verfehlt außerdem die Spezifität, welcher Fehlertyp überhaupt angezeigt wird.

Die Bereitstellung Debug für einen Benutzer hat alle Nachteile von Display und fügt noch mehr technische Informationen hinzu , die der Benutzer nicht benötigt und möglicherweise nicht verstehen kann.

Also ja, ich stimme zu, dass die Botschaften oft nicht gezielt genug an beide Zielgruppen gerichtet sind. Ich denke, dies unterstreicht einen weiteren wichtigen Grund für uns, klar zu machen, auf wen wir abzielen, damit wir dieser Gruppe das bestmögliche Erlebnis bieten (ungeachtet aller Mängel).

Ich benötige Hilfe bei der Implementierung der Unterstützung für ? in #[test] . Meine aktuelle Implementierung finden Sie hier: https://github.com/rust-lang/rust/compare/master...bkchr :termination_trait_in_tests

Das Kompilieren eines Tests mit meinen Änderungen führt zu folgendem Fehler:

error: use of unstable library feature 'test' (see issue #27812)
  |
  = help: add #![feature(test)] to the crate attributes to enable

@eddyb sagte, ich sollte quote_item!/expr! nicht mehr verwenden, weil sie veraltet sind.
Was soll ich jetzt tun, auf das neue quote! Makro umstellen oder alles auf den manuellen Astaufbau umstellen?

Ich freue mich über jede Hilfe :)

Ich denke, dass das Generieren eines Makroaufrufs für ein in libtest definiertes Makro sehr gut funktionieren könnte.

@eddyb Ich bin mir nicht sicher, ob ich deinen Vorschlag hier verstehe:

Ich denke, dass das Generieren eines Makroaufrufs für ein in libtest definiertes Makro sehr gut funktionieren könnte.

Oh, ich schätze, vielleicht tue ich das. Sie sagen -- ein Makro in libtest definieren und dann Code generieren, der es aufruft? Interessante Idee. Würde dieser Makroname nicht irgendwie "durchsickern"? (d. h. es wird Teil der öffentlichen Schnittstelle von libtest?)


@bkchr

Das Kompilieren eines Tests mit meinen Änderungen führt zu folgendem Fehler:

Haben Sie eine Ahnung, warum dieser Fehler generiert wird? Nur vom Lesen des Diffs verstehe ich es nicht, aber ich kann versuchen, es lokal zu bauen und es herauszufinden.

Ich sollte quote_item!/expr! nicht mehr verwenden, weil sie veraltet sind.

Ich habe hier keine starke Meinung. Ich stimme @eddyb zu, dass sie veraltet sind, aber ich bin mir nicht sicher, ob es sich um die Art von Erbe handelt, bei der das Hinzufügen einiger weiterer Verwendungszwecke das Entfernen erschwert - dh, wenn wir kürzlich einen Ersatz erhalten, wäre es einfach @eddyb , um von einem zum anderen zu wechseln?

Das manuelle Erstellen von AST ist sicherlich mühsam, obwohl ich denke, dass wir dafür einige Helfer haben.

Meistens müssen wir nur eine winzige Änderung vornehmen, oder? dh vom Aufrufen der Funktion zum Testen des Ergebnisses von report() wechseln?

PS, wir möchten wahrscheinlich etwas wie Termination::report(...) generieren, anstatt eine .report() -Notation zu verwenden, um zu vermeiden, dass das Termination -Merkmal im Geltungsbereich liegt?

Nein, ich habe keine Ahnung, woher dieser Fehler kommt :(

Der RFC schlug vor, eine Wrapper-Funktion zu generieren, die die ursprüngliche Testfunktion aufruft. Das ist auch meine jetzige Vorgehensweise.
Ich denke, wir könnten die Wrapper-Funktion auch löschen, aber dann müssten wir den Funktionszeiger boxen, da jede Testfunktion einen anderen Typ zurückgeben kann.

Hmm, da der Test bereits andere Dinge importiert, ist es nicht so kompliziert, auch das Termination-Merkmal zu importieren.

@alexcrichton hast du vielleicht eine idee woher dieser fehler kommt?

@nikomatsakis libtest ist instabil und können wir nicht auch Makros als instabil markieren, selbst wenn dies nicht der Fall wäre?

@eddyb oh, guter Punkt.

Der RFC schlug vor, eine Wrapper-Funktion zu generieren, die die ursprüngliche Testfunktion aufruft. Das ist auch meine jetzige Vorgehensweise.

Eine Wrapper-Funktion scheint mir in Ordnung zu sein.

@eddyb mit dem Makro meinst du so etwas wie create_test das in libtest definiert ist? Aber was ich nicht verstehe ist "Makro als instabil markieren". Was beabsichtigen Sie damit? Können Sie mir ein Beispiel geben?

@bkchr Setzen eines #[unstable(...)] -Attributs auf die Makrodefinition, zB: https://github.com/rust-lang/rust/blob/3a39b2aa5a68dd07aacab2106db3927f666a485a/src/libstd/thread/local.rs#L159 -L165

Also, sollte dieses erste Kontrollkästchen...

Implementieren Sie den RFC

... jetzt überprüft werden, ob der verlinkte PR zusammengeführt wurde?

@ErichDonGubler Fertig :)

Hmm, es ist derzeit nur der halbe RFC, der mit dem gemergten pr implementiert wird ^^

Getrennt in 3 Kontrollkästchen :)

Ich habe versucht, diese Funktion in main zu verwenden, und ich fand es ziemlich frustrierend. Die vorhandenen Impls des Beendigungsmerkmals erlauben es mir einfach nicht, mehrere Arten von Fehlern bequem zu "akkumulieren" - zum Beispiel kann ich failure::Fail nicht verwenden, weil es Error nicht implementiert Box<Error> aus demselben Grund nicht verwenden. Ich denke, wir sollten den Wechsel zu Debug priorisieren. =)

Hallo @nikomatsakis ,

Ich war genauso frustriert wie Sie, als ich versuchte, termination_trait zu verwenden.

Das, kombiniert mit Ihren Beiträgen zum Hacken des Compilers, hat mich dazu inspiriert, dieses Problem Anfang dieses Monats anzugehen. Ich habe das Impl für Display (und für Debug im vorherigen Commit) zusammen mit Tests hier gepostet: https://github.com/rust-lang/rust/pull/47544. (Es ist sehr klein, aber immer noch mein erster Rust-Compiler-PR! :tada:) :)

Soweit ich weiß, wird das Lang-Team entscheiden, welche Eigenschaft verwendet werden soll, aber so oder so ist die Implementierung bereit.

Eine Frage, die mich immer noch interessiert: Angenommen, Sie möchten sich nicht auf die Standardausgabe der Fehlermeldung verlassen (ob Debug oder Display ) und stattdessen Ihre eigene möchten, wie machen Sie das? das? (Entschuldigung, falls das schon irgendwo aufgeschrieben war und ich es übersehen habe.) Sie müssten doch nicht ganz auf ? -in- main verzichten, oder? Es ist so etwas wie das Schreiben Ihres eigenen Ergebnisses und/oder Fehlertyps und/oder impl ? (Es erscheint mir bedauerlich, wenn ? -in- main nur ein Spielzeug wäre, und sobald Sie "ernst werden" wollten, müssten Sie auf weniger ergonomische Wege zurückgreifen.)

@glaebhörl Es ist ganz einfach:

  1. Erstellen Sie einen neuen Typ.
  2. Implementieren Sie From Ihren alten Fehlertyp.
  3. Implementieren Sie Debug (oder Display ).
  4. Ersetzen Sie den Typ in der Signatur von main .

Danke!

Es erscheint mir etwas seltsam, benutzerdefinierte Implementierungen von Debug zu schreiben, die nicht debuggingorientiert sind, aber ich denke, es ist nicht das Ende der Welt.

@glaebhörl Deshalb sollte das Result impl Display statt Debug IMO verwenden.

Kann die Eigenschaft Termination nicht eine zusätzliche Methode namens message oder error_message oder so ähnlich haben, die eine Standardimplementierung hat, die die Debug / Display um die Nachricht anzuzeigen? Dann müssen Sie nur eine einzelne Methode implementieren, anstatt einen neuen Typ zu erstellen.

@glaebhoerl @Thomasdezeeuw So etwas wie eine error_message -Methode war im ursprünglichen Entwurf des RFC enthalten, wurde aber mangels Konsens fallen gelassen. Mein damaliges Gefühl war, dass es am besten wäre, das grundlegende Feature zu landen (nicht unbedingt zu stabilisieren) und dann zu iterieren.

@zackw Einverstanden, ich persönlich wäre ohne Nachrichten in Ordnung, nur eine Nummer, sagen wir 0 und 1 für Fehlercodes. Aber wenn wir die Nachrichten in der ersten Iteration erhalten wollen, wäre ich wohl eher für Termination::message als für irgendetwas bei Debug oder Display .

Würde diese Methode message ein String zurückgeben? Wäre das nicht unvereinbar mit Termination in libcore?

@SimonSapin Termination ist derzeit in libstd definiert.

Hmm, aber ich glaube nicht, dass das Hinzufügen einer Methode message zur Eigenschaft die beste Implementierung wäre. Was würde die Methode für Typen wie i32 zurückgeben? Wie würde entscheiden, wann diese Nachricht gedruckt werden soll? Derzeit gibt die Implementierung von Termination für Result den Fehler in der Funktion report aus. Das funktioniert, weil Result weiß, dass es sich um ein Err handelt. Wir könnten irgendwo einen Scheck report() != 0 integrieren und dann drucken, aber das fühlt sich nicht richtig an.
Das nächste Problem wäre, dass wir eine Standardimplementierung für Result bereitstellen wollen, aber was muss der Typ Error implementieren, um wahrscheinlich gedruckt zu werden? Dies würde uns zurück zur aktuellen Frage Debug oder Display bringen.

Ein weiteres Problem, das auftritt, wenn Sie ? in main in einem "seriösen" Programm verwenden möchten, ist, dass Befehlszeilenprogramme unter bestimmten Umständen mit einem Nicht-Null-Status beenden möchten, aber ohne _irgendetwas_ zu drucken (beachten Sie grep -q ). Jetzt brauchen Sie also ein Termination -Impl für etwas, das _kein_ Error ist, das nichts druckt, mit dem Sie den Exit-Status steuern können ... und Sie müssen entscheiden, ob Sie ' Geben Sie das Ding _nach_ dem Analysieren der Befehlszeilenargumente zurück.

Das ist was ich denke:

Die Rückgabe Result<T, E> sollte das Debug-Impl für E verwenden. Dies ist die praktischste Eigenschaft – sie ist weit verbreitet und erfüllt den Anwendungsfall „Quick and Dirty Output“ sowie den Anwendungsfall Unit-Tests. Ich würde es vorziehen, Display nicht zu verwenden, sowohl weil es weniger weit verbreitet ist, als auch weil es den Eindruck erweckt, dass dies eine ausgefeilte Ausgabe ist, was ich für sehr unwahrscheinlich halte.

Aber es sollte auch eine Möglichkeit geben, professionell aussehende Ergebnisse zu erzielen. Glücklicherweise gibt es bereits zwei solcher Möglichkeiten. Erstens können die Leute, wie @withoutboats sagte, eine "Display-from-Debug"-Brücke erstellen, wenn sie E: Display verwenden oder auf andere Weise eine Ausgabe im professionellen Stil liefern möchten. Aber wenn sich das zu hacky anfühlt, dann können Sie auch einfach Ihren eigenen Typ definieren, um das Ergebnis zu ersetzen. z. B. könnten Sie Folgendes tun:

fn main() -> ProfessionalLookingResult {
    ...
}

und implementieren Sie dann Try für ProfessionalLookingResult . Dann können Sie auch Terminate implementieren, was auch immer:

impl Terminate for ProfessionalLookingResult {
    fn report(self) -> i32 {
        ...
        eprintln!("Something very professional here.");
        return 1;
        ...
    }
}

Ich stimme @nikomatsakis zu, dass dies Debug verwenden sollte.

Ich denke auch, dass es für eine ausgefeilte Ausgabe wahrscheinlich besser ist, Code in main zu schreiben, als einen neuen Typ zu erstellen, um Try und Terminate zu implementieren. Der Punkt von Terminate scheint mir für Dinge zu sein, für die Bibliotheken leicht einen guten Standard machen können, was einfach nie der Fall ist für eine Situation, in der es für Endbenutzer wichtig ist, wie das Programm beendet wird (z. B. professionelle CLIs). .

Natürlich können andere Leute anderer Meinung sein, und es gibt mehrere Möglichkeiten, die beteiligten Eigenschaften zum Einfügen von Code zu verwenden, anstatt ihn direkt in main zu schreiben. Das Tolle ist, dass wir mehrere Möglichkeiten haben und uns nicht immer einen einzigen gesegneten Weg ausdenken müssen, um mit Fehlern umzugehen.

Lassen Sie mich nur einige Gedanken niederschreiben, obwohl es ein paar Probleme damit gibt.

Folgendes würde ich gerne sehen

fn main() -> i32 {
    1
}

Was allgemeiner geschrieben werden könnte als:

fn main() -> impl Display {
    1
}

Diese beiden Hauptfunktionen sollten den Exit-Code 0 und println! den Display von 1 zurückgeben.

Dies sollte so einfach sein wie das Folgende (glaube ich).

impl<T> Termination for T where T: Display {
    fn report(self) -> i32 {
        println!("{}", self);
        EXIT_SUCCESS
    }
}

Dann können wir für Fehler haben:

impl<T: Termination, E: Debug> Termination for Result<T, E> { ... }

wobei die Implementierung die gleiche ist wie im RFC nur mit "{:?}" zu verwenden
ein Debug -Format.

Wie bereits erwähnt, können Personen, die mehr Kontrolle über die Ausgabe benötigen, einfach
schreiben:

fn main() -> Result<i32, MyError> { ... }
impl Termination for Result<i32, MyError> { ... }

Obwohl dies mit unserem aktuellen Compiler unentscheidbar wäre, denke ich, da es
würde widersprüchliche Implementierungen sehen ... Also tun wir, was @nikomatsakis vorschlägt
und schreibe:

fn main() -> MyResult { ... }
impl Termination for MyResult { ... }
or, if you want something more general.
impl<T, E> Termination for MyResult<T, E> { ... }

Ich weiß, dass dies teilweise das Gesagte wiederholt, aber ich dachte, ich würde meine Vision insgesamt präsentieren und zeigen, dass eine allgemeinere Lösung zum Anzeigen von Rückgabewerten und nicht nur von Ergebnissen vorhanden ist. Scheint, als würde in vielen Kommentaren darüber diskutiert, welche Implementierungen von Termination wir standardmäßig ausliefern. Auch dieser Kommentar steht im Widerspruch zu einer Implementierung wie impl Termination for bool , wie im RFC beschrieben. Ich persönlich denke, dass Exit-Codes ungleich Null ausschließlich von Results oder benutzerdefinierten Typen behandelt werden sollten, die Termination implementieren.

Es ist eine interessante Frage, wie man dann ? auf Option -Typen in main behandelt, da sie keine Implementierung für Display haben.

TL;DR: Ich bin mit Debug .

Genauere Erklärung:
Ich habe gestern und heute einige Zeit damit verbracht, darüber nachzudenken, mit dem Ziel, implizite Annahmen aufzudecken, die ich habe oder mache, um zu helfen, die bestmögliche Antwort zu finden.

Schlüsselannahme: Von all den verschiedenen Arten von Anwendungen, die man in Rust schreiben kann, denke ich, dass die Konsolen-App am meisten von dieser Entscheidung profitieren wird/am stärksten betroffen sein wird. Mein Gedanke ist, dass man beim Schreiben einer Bibliothek, eines AAA-Spieltitels, einer IDE oder eines proprietären Steuerungssystems wahrscheinlich nicht erwarten würde, dass ein standardmäßiges Hauptabschlussmerkmal die eigenen Anforderungen sofort erfüllt (tatsächlich hat man möglicherweise nicht einmal eine hauptsächlich). Meine Vorliebe für Display als Standard ergibt sich also aus dem, was wir erwarten, wenn wir eine kleine Befehlszeilenanwendung verwenden - zum Beispiel:

$ cd foo
bash: cd: foo: No such file or directory

Die meisten von uns erwarten keine Debugging-Hilfen, sondern nur einen prägnanten Hinweis darauf, was schief gelaufen ist. Ich plädiere lediglich dafür als Standardposition.

Wenn ich daran denke, ein Terminate -Impl zu schreiben, um eine einfache Ausgabe wie diese zu erhalten, wird mir klar, dass sich das ? $-Hauptfeature nicht so sehr von Stable Rust heute unterscheidet (in Bezug auf die Menge des geschriebenen Codes). ), wo oft ein Result -bewusstes "inner_main()" erstellt wird, um E zu handhaben.

Mit dieser Annahme im Hinterkopf versuchte ich als Denkübung festzustellen, ob ich das starke Gefühl hatte, dass die heute existierenden Implementierungen im Stil von " inner_main() " eher lässigen Display -Flair waren (über einen eher technischen Debug -Geschmack). Mein Gedanke war, dass dies ein Hinweis darauf wäre, wie die Funktion wahrscheinlich tatsächlich verwendet wird.

Ich konnte mich nicht davon überzeugen, dass dies der Fall ist. (Das heißt, ich glaube nicht, dass es derzeit eine starke Tendenz zu Display in bestehenden Implementierungen gibt). Als ich meine eigenen Repositories durchgesehen habe, die ich in den letzten 16 Monaten geschrieben habe, habe ich auch genug von beiden Fällen gefunden, sodass ich nicht sagen kann, dass die standardmäßige Implementierung Display zu einer Nettoeinsparung geführt hätte.

Gemäß der Annahme „Hauptnutzen ist die CLI-Anwendung“, gibt es eine große Anzahl von Konsolen-Apps, die Hilfe und Nutzungsinformationen bereitstellen. Zum Beispiel:

$ git foo
git: 'foo' is not a git command. See 'git --help'.

The most similar command is
    log

Selbst bei Konsolen-Apps fällt es mir schwer, eine "geschädigte Gruppe" zu identifizieren, indem ich Debug verwende.

Und schließlich wäre ich mit einem Debug Impl glücklicher als mit dem Feature, das auch noch weitere 6 Monate aufgehalten wird, also, egoistisch, da ist das :).

Da ist also mein Denkprozess öffentlich dargestellt. Zusammenfassend denke ich, dass Debug nicht besser und nicht schlechter als Display wird und daher als Standardimplementierung in Ordnung sein sollte.

Ich bin mir sicher, wie viele von Ihnen, ich wünschte, es gäbe eine Implementierung, auf die ich mich mehr freue – wie „JA, DAS IST ES!!!“, TBH. Aber vielleicht sind das nur meine Erwartungen, die unrealistisch sind ... Vielleicht, sobald wir eine Lösung haben, die mit failure funktioniert, um Boilerplates in meinen Projekten zu reduzieren, wird es mir wachsen. :)

Beachten Sie, dass ich eine PR geöffnet habe, um das Terminatio-Merkmal in Tests zu unterstützen: #48143 (aufbauend auf der Arbeit von @bkchr ).

Ich habe mir erlaubt, das Merkmal Termination um eine Methode zur Verarbeitung des Testergebnisses zu erweitern. Dies vereinfachte die Implementierung, ist aber auch sinnvoll, da Testfehler möglicherweise eine ausführlichere Ausgabe erfordern als ausführbare Fehler.

Termination sollte in Terminate umbenannt werden, entsprechend unserer allgemeinen Vorliebe für Verben für Eigenschaften in libstd.

@ohneBoote Ich glaube, irgendwann gab es eine Diskussion darüber, dass Verbmerkmale meistens solche sind, die eine einzige Methode mit demselben Namen wie das Merkmal haben. Wie auch immer, könnte ich meinen eigenen Fahrradschuppen-Vorschlag noch einmal vorstellen, Exit ?

Gratuitous bikeshedding: Dies ist eine Eigenschaft mit nur einer Methode. Wenn wir ihnen den gleichen Namen geben wollen, vielleicht ToExitCode / to_exit_code ?

Die Stabilisierung der Rückgabe Result kann unabhängig von der Stabilisierung des Merkmals erfolgen, richtig?

Für mich fühlt sich das wie ? an, wo der größte Wert aus der Sprachfunktion stammt und wir die Bestimmung des Merkmals verzögern können. Die RFC-Diskussion ließ mich sogar fragen, ob das Trait _ever_ stabilisiert werden muss, da das Einfügen des Codes in ein inner_main einfacher schien als ein Trait impl dafür ...

Ja, wir müssen das Merkmal nicht stabilisieren - obwohl es auf Stable für Dinge wie Frameworks nützlich ist, die sich nicht unbedingt so sehr auf inner_main verlassen können.

@SimonSapin Ich denke, dass sich To auf eine Typkonvertierung bezieht, und dies nicht. Aber wir könnten die Methode terminate nennen (auch glaube ich nicht, dass diese Einschränkung, wann Traits Verben benannt werden sollen, Bestand hat. Try ist ein offensichtliches Gegenbeispiel.)

Ich habe vorgeschlagen, dass wir fn main() -> T stabilisieren, wobei T keine Einheit ist. Dadurch bleiben viele Details – insbesondere Name/Ort/Details des Merkmals – unstabilisiert, aber einige Dinge sind behoben. Details hier:

https://github.com/rust-lang/rust/issues/48453

Bitte geben Sie Ihr Feedback!

terminate scheint aussagekräftiger zu sein als report . Wir verwenden immer terminate , können aber report weglassen.

Aber anders als zum Beispiel std::process::exit beendet diese Methode nichts. Es wandelt nur den Rückgabewert von main() in einen Exit-Code um (nach optionaler Ausgabe Result::Err an stderr).

Noch eine Stimme für Exit . Mir gefällt, dass es kurz, recht beschreibend und mit dem traditionellen Konzept der Ausgangscodes/Ausgangsstatus übereinstimmt.

Ich würde auf jeden Fall Exit gegenüber Terminate vorziehen, da wir elegant aussteigen, indem wir vom Main zurückkehren, anstatt plötzlich hart zu beenden, wo wir sind, weil etwas wirklich schief gelaufen ist.

Ich habe https://github.com/rust-lang/rust/issues/48854 hinzugefügt, um stabilisierende Komponententests vorzuschlagen, die Ergebnisse zurückgeben.

Oh hey, ich habe den richtigen Ort gefunden, um darüber zu sprechen.

Verwendung ? in Dokumententests

Die Art und Weise, wie Doctests derzeit funktionieren, sieht ungefähr so ​​aus:

  • rustdoc scannt das doctest darauf, ob es ein fn main deklariert

    • (derzeit führt es nur eine zeilenweise Textsuche nach "fn main" durch, die nicht nach einem // steht)

  • Wenn fn main gefunden wurde, berührt es nicht das, was bereits vorhanden ist
  • Wenn fn main nicht gefunden wurde, wird der Großteil* des Dokumenttests in ein einfaches fn main() { } eingeschlossen

    • *Vollversion: Es extrahiert #![inner_attributes] - und extern crate -Deklarationen und platziert sie außerhalb der generierten Hauptfunktion, aber alles andere kommt hinein.

  • Wenn doctest keine externen Crate-Anweisungen enthält (und die dokumentierte Crate nicht std heißt), fügt rustdoc auch eine extern crate my_crate; -Anweisung direkt vor der generierten Hauptfunktion ein.
  • rustdoc kompiliert dann das Endergebnis und führt es als eigenständige Binärdatei als Teil der Testumgebung aus.

(Ich habe ein paar Details ausgelassen, aber ich habe praktischerweise hier eine vollständige Beschreibung erstellt.)

Um also ? nahtlos in einem Doctest zu verwenden, muss der Teil geändert werden, in dem fn main() { your_code_here(); } hinzugefügt wird. Das Deklarieren Ihrer eigenen fn main() -> Result<(), Error> wird funktionieren, sobald Sie dies tun können dass in normalem Code - rustdoc dort nicht einmal geändert werden muss. Damit es funktioniert, ohne main manuell zu deklarieren, ist jedoch eine kleine Änderung erforderlich. Da ich diese Funktion nicht genau verfolgt habe, bin ich mir nicht sicher, ob es eine Einheitslösung gibt. Ist fn main() -> impl Termination möglich?

Ist fn main() -> impl Termination möglich?

Im oberflächlichen Sinn ja: https://play.rust-lang.org/?gist=8e353379f77a546d152c9113414a88f7&version=nightly

Leider denke ich, dass -> impl Trait #$ aufgrund der eingebauten Fehlerkonvertierung, die den Inferenzkontext benötigt, um zu sagen, welcher Typ verwendet werden soll, grundsätzlich problematisch mit ? ist: https://play.rust- lang.org/?gist=23410fa4fa684710bc75e16f0714ec4b&version=nightly

Persönlich stellte ich mir ? -in-doctests vor, das über so etwas wie https://github.com/rust-lang/rfcs/pull/2107 als fn main() -> Result<(), Box<Debug>> catch { your_code_here(); } funktioniert (unter Verwendung der Syntax von https:// github.com/rust-lang/rust/issues/41414#issuecomment-373985777).

Die impl Trait -Version ist jedoch cool und könnte funktionieren, wenn es eine Art Unterstützung "bevorzuge den gleichen Typ, wenn nicht eingeschränkt" gäbe, die rustc intern im ? -Desugar verwenden könnte, aber meiner Erinnerung nach ist es so dass die Idee eines solchen Features dazu neigt, die Leute, die verstehen, wie die Dinge funktionieren, entsetzt zurückschrecken zu lassen :sweat_smile: Aber vielleicht wäre es machbar, dass es eine interne Sache ist, die nur gilt, wenn die Ausgabe impl Trait ist ...

Oh, das sind sehr reale Sorgen. Ich hatte vergessen, wie das die Typinferenz durcheinander bringen würde. Wenn der Catch-Block wie dieses verknüpfte Problem mit dem Ok-Wrapping begonnen hat, dann scheint dies ein viel einfacherer (und viel schnellerer, stabilisierungstechnischer) Weg nach vorne zu sein.

Ich frage mich nur, wie sich der Editionswechsel darauf auswirken wird. Ändert sich die catch -Syntax nicht in der Epoche 2018? Rustdoc wird wahrscheinlich Doctests in derselben Edition wie die Bibliothek, die es ausführt, kompilieren wollen, also müsste es zwischen den Syntaxen basierend auf dem übergebenen Epochen-Flag unterscheiden.

Ich mache mir Sorgen, dass dies jetzt stabilisiert ist, aber einfache Fälle anscheinend immer noch ICE: https://github.com/rust-lang/rust/issues/48890#issuecomment -375952342

fn main() -> Result<(), &'static str> {
    Err("An error message for you")
}
assertion failed: !substs.has_erasable_regions(), librustc_trans_utils/symbol_names.rs:169:9

https://play.rust-lang.org/?gist=fe6ae28c67e7d3195a3731839d4aac84&version=nightly

An welchem ​​Punkt sagen wir "das ist zu fehlerhaft und wir sollten destabilisieren"? Es scheint, als ob dies in seiner aktuellen Form den stabilen Kanal erreichen würde, es würde viel Verwirrung stiften.

@frewsxcv Ich denke, die Probleme sind jetzt behoben, oder?

@nikomatsakis das Problem, das ich in https://github.com/rust-lang/rust/issues/48389 angesprochen habe, ist in 1.26-beta gelöst, also ja aus meiner Sicht.

Ja, der ICE, um den ich mir Sorgen gemacht habe, ist jetzt repariert!

Entschuldigen Sie, dass ich mich an einem Punkt einschalte, an dem es wahrscheinlich zu spät ist, etwas dagegen zu unternehmen, aber ich wollte hier mein Feedback hinterlassen, falls dies der Fall ist. Ich habe den größten Teil dieses Threads gelesen, also spreche ich mit diesem Kontext im Hinterkopf. Dieser Thread ist jedoch lang. Wenn es also so aussieht, als hätte ich etwas übersehen, dann habe ich es wahrscheinlich getan, und ich würde es begrüßen, wenn ich darauf hingewiesen würde. :-)

TL;DR - Ich denke, das Anzeigen der Debug -Meldung eines Fehlers war ein Fehler, und es wäre eine bessere Wahl, die Display -Meldung eines Fehlers zu verwenden.

Der Kern meiner Überzeugung ist, dass ich mich als jemand, der _routinemäßig CLI-Programme in Rust_ erstellt, nicht erinnern kann, dass ich mich jemals groß darum gekümmert habe, was die Debug -Nachricht eines Error ist. Die Debug eines Fehlers sind nämlich per Design für Entwickler, nicht für Endbenutzer. Wenn Sie ein CLI-Programm erstellen, ist seine Schnittstelle grundsätzlich dazu bestimmt, von Endbenutzern gelesen zu werden, daher hat eine Debug -Nachricht hier sehr wenig Nutzen. Das heißt, wenn ein CLI-Programm, das ich an Endbenutzer auslieferte, im normalen Betrieb die Debug-Darstellung eines Rust-Werts zeigte, dann würde ich dies als einen zu behebenden Fehler betrachten. Ich denke im Allgemeinen, dass dies für jedes in Rust geschriebene CLI-Programm gelten sollte, obwohl ich verstehe, dass dies ein Punkt sein kann, in dem vernünftige Leute anderer Meinung sein können. Abgesehen davon ist meiner Meinung nach eine etwas verblüffende Implikation, dass wir ein Feature effektiv stabilisiert haben, dessen Standardbetriebsmodus Sie mit einem Fehler (wiederum, meiner Meinung nach) beginnt, der behoben werden sollte.

Indem standardmäßig die Debug -Darstellung eines Fehlers angezeigt wird, glaube ich auch, dass wir schlechte Vorgehensweisen fördern. Insbesondere beim Schreiben eines Rust-CLI-Programms wird häufig beobachtet, dass selbst die Display Impl eines Fehlers nicht gut genug sind, um von Endbenutzern verarbeitet zu werden, und dass echte Arbeit geleistet werden muss repariere es. Ein konkretes Beispiel dafür ist io::Error . Das Anzeigen io::Error ohne entsprechenden Dateipfad (vorausgesetzt, es stammt vom Lesen/Schreiben/Öffnen/Erstellen einer Datei) ist im Grunde ein Fehler, da es für einen Endbenutzer schwierig ist, etwas damit zu tun. Indem wir uns dafür entschieden haben, standardmäßig die Debug -Darstellung eines Fehlers anzuzeigen, haben wir es schwieriger gemacht, diese Art von Fehlern von Leuten aufzudecken, die CLI-Programme erstellen. (Außerdem ist das Debug eines io::Error viel weniger nützlich als das Display , aber das allein ist meiner Erfahrung nach kein großer Schmerzpunkt. )

Um mein Argument abzurunden, fällt es mir schließlich auch schwer, mir die Umstände vorzustellen, unter denen ich ?-in-main selbst in Beispielen verwenden würde. Ich habe nämlich versucht, Beispiele zu schreiben, die realen Programmen so genau wie möglich entsprechen, und dazu gehörte im Allgemeinen das Schreiben von Dingen wie diesen:

use std::error::Error;
use std::process;

fn try_main() -> Result<(), Box<Error>> {
    // do stuff with `?`
}

fn main() {
    if let Err(err) = try_main() {
        eprintln!("{}", err);
        process::exit(1);
    }
}

Auf den ersten Blick wäre es _schön_, dies durch ?-in-main zu ersetzen, aber ich kann nicht, weil es die Display eines Fehlers nicht anzeigt. Das heißt, wenn ich ein echtes CLI-Programm schreibe, werde ich tatsächlich den obigen Ansatz verwenden. Wenn ich also möchte, dass meine Beispiele die Realität widerspiegeln, dann sollte ich meiner Meinung nach zeigen, was ich in echten Programmen mache, und keine Abkürzungen nehmen (in einem vernünftigen Umfang). ). Ich denke, dass so etwas wirklich wichtig ist, und ein historischer Nebeneffekt davon war, dass es den Leuten gezeigt hat, wie man idiomatischen Rust-Code schreibt, ohne überall unwrap zu verstreuen. Aber wenn ich in meinen Beispielen wieder ?-in-main verwende, dann habe ich gerade mein Ziel verfehlt: Ich richte jetzt Leute ein, die es vielleicht nicht besser wissen, einfach Programme zu schreiben, die standardmäßig sehr emittieren nicht hilfreiche Fehlermeldungen.

Das Muster "Fehlermeldung ausgeben und mit entsprechendem Fehlercode beenden" wird tatsächlich in ausgefeilten Programmen verwendet. Wenn beispielsweise ?-in-main Display verwendet, dann könnte ich heute die Funktionen main und run in ripgrep zusammenführen:

https://github.com/BurntSushi/ripgrep/blob/64317bda9f497d66bbeffa71ae6328601167a5bd/src/main.rs#L56 -L86

Natürlich könnte ich in Zukunft ?-in-main verwenden, indem ich mein eigenes Impl für das Merkmal Termination bereitstelle, sobald sich das stabilisiert, aber warum sollte ich mir die Mühe machen, wenn ich einfach die main schreiben könnte impl in die Beispiele einfügen, damit sie der Realität entsprechen, und an diesem Punkt könnte ich genauso gut bei den Beispielen bleiben, die ich heute habe (mit main und ein try_main ).

So wie es aussieht , wäre es eine bahnbrechende Änderung, dies zu beheben. Das heißt, dieser Code wird heute auf Rust Stable kompiliert:

#[derive(Debug)]
struct OnlyDebug;

fn main() -> Result<(), OnlyDebug> {
    Err(OnlyDebug)
}

Ich denke, ein Wechsel zu Display würde diesen Code brechen. Aber ich weiß es nicht genau! Wenn dies wirklich ein Problem ist, das ein Schiff gesegelt hat, dann verstehe ich, und es hat keinen allzu großen Sinn, den Punkt zu vertiefen, aber ich fühle mich stark genug, um zumindest etwas zu sagen und zu sehen, ob ich andere nicht überzeugen und sehen kann wenn es etwas gibt, was getan werden kann, um es zu beheben. (Es ist auch gut möglich, dass ich hier überreagiere, aber ich wurde bisher von ein paar Leuten gefragt: "Warum verwendest du nicht ?-in-main ?" in meinen CSV-Beispielen, und meine Antwort war im Grunde: „Ich sehe nicht, wie es jemals machbar wäre, das zu tun.“ Vielleicht war das kein Problem, das von ?-in-main gelöst werden sollte, aber einige Leute hatten sicherlich diesen Eindruck , ich könnte sehen, dass es in Dokumenttests und Komponententests nützlich ist, aber es fällt mir schwer, an andere Situationen zu denken, in denen ich es verwenden würde.)

Ich stimme zu, dass die Anzeige von Display über Debug für CLI-Anwendungen besser ist. Ich stimme nicht zu, dass es Display anstelle von Debug sein sollte, da dies drastisch einschränkt, welche Fehler tatsächlich ? -ed werden können, und den Zweck von ?-in-main zunichte macht Display zu verwenden, wenn dies verfügbar ist, und auf Debug zurückzugreifen, wenn dies nicht der Fall ist.

#[unstable(feature = "termination_trait_lib", issue = "43301")]
impl<E: fmt::Display> Termination for Result<!, E> {
    fn report(self) -> i32 {
        let Err(err) = self;
        eprintln!("Error: {}", err);
        ExitCode::FAILURE.report()
    }
}

Schließlich, um mein Argument abzurunden, fällt es mir auch schwer, mir die Umstände vorzustellen, unter denen ich ?-in-main selbst in Beispielen verwenden würde.

Ich muss @BurntSushi im Allgemeinen für echte CLI-Programme zustimmen, aber für zufällige Skripte und interne Tools, die nur ich verwenden möchte, ist dies wirklich praktisch. Außerdem ist es sehr praktisch für Prototypen und Spielzeug. Wir könnten immer davon abraten, es tatsächlich im Produktionscode zu verwenden, oder?

Ein Teil des Ziels von ?-in-main besteht darin, unidiomatischen Code in Codebeispielen zu vermeiden, nämlich Unwraps. Aber wenn ?-in-main selbst unidiomatisch wird, untergräbt das die gesamte Funktion. Ich möchte unbedingt jedes Ergebnis vermeiden, das dazu führen würde, dass wir davon abraten müssen, es im Produktionscode oder auf andere Weise zu verwenden.

Ich habe versucht, diese Funktion hauptsächlich zu verwenden, und ich fand es ziemlich frustrierend. Die vorhandenen Impls des Terminierungsmerkmals erlauben es mir einfach nicht, mehrere Arten von Fehlern bequem zu "akkumulieren" -- zum Beispiel kann ich fail::Fail nicht verwenden, weil es Error nicht implementiert; Ich kann Box nicht verwenden, gleicher Grund. Ich denke, wir sollten den Wechsel zu Debug priorisieren. =)

Wenn wir Display verwenden, können wir einen schnellen und schmutzigen Typ wie MainError einführen, um mehrere Arten von Fehlern zu akkumulieren:

pub struct MainError {
    s: String,
}

impl std::fmt::Display for MainError {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        self.s.fmt(f)
    }
}

impl<T> From<T> for MainError where T: std::error::Error {
    fn from(t: T) -> Self {
        MainError {
            s: t.to_string(),
        }
    }
}

Dies ermöglicht Folgendes, ähnlich wie Box<Error> :

fn main() -> Result<(), MainError> {
    let _ = std::fs::File::open("foo")?;
    Ok(())
}

Die vorherige Diskussion von Display vs. Debug befindet sich hier im versteckten Teil und beginnt um https://github.com/rust-lang/rust/issues/43301#issuecomment -362020946.

@ BurntSushi Ich stimme dir zu. Wenn dies nicht behoben werden kann, gibt es möglicherweise eine Problemumgehung:

use std::fmt;
struct DisplayAsDebug<T: fmt::Display>(pub T);

impl<T: fmt::Display> Debug for DisplayAsDebug {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        fmt::Display::fmt(&self.0, f)
    }
}

impl<T: fmt::Display> From<T> for DisplayAsDebug {
    fn from(val: T) -> Self {
        DisplayAsDebug(val)
    }
}

Wenn dies in std::fmt wäre, könnten wir so etwas verwenden:

use std::{fmt, io};

fn main() -> Result<(), fmt::DisplayAsDebug<io::Error>> {
    let mut file = File::open("/some/file")?;
    // do something with file
}

Ich habe immer noch keine Zeit, auf absehbare Zeit mehr dazu beizutragen, aber ich stimme auch zu, dass ? im Wesentlichen in ernsthaften CLI-Programmen verwendbar sein sollte, und es gab Funktionen im ursprünglichen Entwurf des RFC, die dies erleichtern sollten. Sie wurden im Namen des Inkrementalismus fallen gelassen, aber vielleicht sollten sie noch einmal aufgegriffen werden.

Ich denke, es ist in Ordnung, dies in abwärtskompatibler Weise zu beheben, wenn dies bald geschehen ist, bevor viel Code da draußen es auf Stable verwendet.

Als jemand, der viele CLI-Programme in Rust schreibt und bei der Arbeit für den Rust-Stil verantwortlich ist, stimme ich @burntsushi hier ziemlich zu. Ich hätte gerne die im RFC angegebene Version dieser Funktion verwendet.

Aber ich halte es für einen Fehler, einem Benutzer eine Debug -Implementierung zu zeigen, und nicht viel besser, als einfach überall in einem CLI-Programm unwrap aufzurufen. Selbst im Beispielcode kann ich mir also nicht vorstellen, diese Version der Funktion jemals zu verwenden. Das ist schade, denn das Entfernen der Boilerplate aus main hätte das Lernen für neue Rust-Entwickler bei der Arbeit vereinfacht, und wir hätten quick_main! oder ähnliches nicht mehr erklären müssen.

So ziemlich die einzigen Umstände, unter denen ich mir vorstellen kann, dies zu verwenden, wären, unwrap aus Doctests zu entfernen. Aber ich weiß nicht, ob das noch unterstützt wird.

Wenn wir Display verwenden, können wir einen schnellen und schmutzigen Typ wie MainError einführen, um mehrere Arten von Fehlern zu akkumulieren:

Persönlich würde diese Art der Problemumgehung so viel Komplexität hinzufügen, dass es einfach einfacher ist, ? -in- main vollständig zu vermeiden.

@Aronepower :

Soweit ich das beurteilen kann (könnte völlig falsch sein, da ich nicht die Zeit habe, stdlib zu kompilieren, und ich nicht weiß, ob die Spezialisierung dies abdeckt), gibt es keinen Grund, warum wir das folgende Impl nicht zu Termination hinzufügen können. Dies würde eine unterbrechungsfreie Möglichkeit bieten, Display zu verwenden, wenn dies verfügbar ist, und auf Debug zurückzugreifen, wenn dies nicht der Fall ist.

Wenn dies mir ermöglichen würde, fn main() -> Result<(), failure::Error> zu schreiben und mit Display einen netten, für Menschen lesbaren Fehler zu erhalten, würde dies definitiv meine Hauptbedenken befriedigen. Ich nehme jedoch an, dass dies immer noch die Frage offen lässt, ob der Backtrace optional in failure::Error angezeigt werden kann, wenn RUST_BACKTRACE=1 gesetzt ist – aber das könnte sowieso außerhalb des Geltungsbereichs liegen oder zumindest ein Problem sein, das sollte mit failure aufgenommen werden.

Dies wurde vor einiger Zeit stabilisiert, und ich glaube nicht, dass wir das Verhalten von Result wirklich ändern können. Ich persönlich bin nach wie vor ziemlich zufrieden mit dem aktuellen Verhalten für die Anwendungsfälle, die ich tendenziell mache – schnelle und schmutzige Skripte und dergleichen –, aber ich stimme zu, dass komplexere Skripte dies nicht wollen. Zum Zeitpunkt der Diskussion haben wir jedoch auch eine Reihe von Möglichkeiten besprochen, wie Sie dieses Verhalten kontrollieren können (ich kann diese Kommentare jetzt nicht finden, da Github Dinge vor mir verbirgt).

Beispielsweise könnten Sie Ihren eigenen "Wrapper" für den Fehlertyp definieren und dafür From implementieren:

struct PrettyPrintedError { ... }
impl<E: Display> From<E> for PrettyPrintedError { }

impl Debug { /* .. invoke Display .. */ }

Jetzt können Sie so etwas schreiben, was bedeutet, dass Sie ? in main verwenden können:

fn main() -> Result<(), PrettyPrintedError> { ... }

Vielleicht sollte ein solcher Typ Teil von Quick-Cli oder so sein?

@nikomatsakis Ja, ich verstehe diese Problemumgehung total, aber ich habe das Gefühl, dass dies den Zweck der Prägnanz der Verwendung von ?-in-main zunichte macht. Ich habe das Gefühl, dass "es ist möglich, ?-in-main mit dieser Problemumgehung zu verwenden" ?-in-main leider selbst untergräbt. Zum Beispiel werde ich Ihre Problemumgehung nicht in prägnanten Beispielen aufschreiben und ich werde auch nicht für jedes Beispiel, das ich schreibe, eine Abhängigkeit von quicli auferlegen. Ich werde es sicherlich auch nicht für schnelle Programme verwenden, weil ich die Display Ausgabe eines Fehlers in praktisch jedem CLI-Programm haben möchte, das ich schreibe. Meiner Meinung nach ist die Debug-Ausgabe eines Fehlers ein Fehler in CLI-Programmen, den Sie den Benutzern vorführen.

Die Alternative zu ?-in-main ist eine zusätzliche ~4-zeilige Funktion. Wenn also die Problemumgehung, ?-in-main für die Verwendung Display zu reparieren, viel mehr als das ist, sehe ich persönlich keinen Grund, es überhaupt zu verwenden (außerhalb von Dokumenttests oder Komponententests, da oben erwähnt).

Kann man das in der Edition ändern?

@BurntSushi Wie würden Sie sich über die Problemumgehung in std fühlen, sodass Sie nur eine etwas längere Deklaration des Rückgabetyps für main() schreiben müssten?

@Kixunil Mein erstes Gefühl ist, dass es schmackhaft sein könnte. Hab mir aber noch nicht so viele Gedanken gemacht.

@BurntSushi

Die Alternative zu ?-in-main ist eine zusätzliche ~4-zeilige Funktion.

Letztendlich ist das Verhalten stabil, und ich glaube nicht, dass wir es zum jetzigen Zeitpunkt realistisch ändern können. (Ich meine, es ist keine Verletzung der Solidität oder so etwas.)

Das heißt, ich finde das aktuelle Setup immer noch ziemlich nett und besser als Display , aber ich denke, es ist eine Frage dessen, was Sie als "Standard" haben möchten - das heißt, jedes Setup kann so gemacht werden, dass es wie das funktioniert andere über verschiedene neue Typen. Also bevorzugen wir entweder standardmäßig "quick-n-dirty"-Skripte, die Debug wollen, oder ausgefeilte Endprodukte. Ich neige dazu zu denken, dass ein ausgefeiltes Endprodukt dort ist, wo ich mir einen zusätzlichen Import leicht genug leisten kann und wo ich auch aus vielen verschiedenen möglichen Formaten auswählen möchte (z. B. möchte ich nur den Fehler oder möchte ich argv [0] usw.).

Konkret stelle ich mir das so vor:

use failure::format::JustError;

fn main() -> Result<(), JustError> { .. }

oder um ein anderes Format zu erhalten, mache ich vielleicht Folgendes:

use failure::format::ProgramNameAndError;

fn main() -> Result<(), ProgramNameAndError> { .. }

Beides fühlt sich im Vergleich zu der 4-zeiligen Funktion, die Sie zuvor schreiben mussten, ziemlich gut an:

use std::sys;

fn main() {
  match inner_main() {
    Ok(()) => { }
    Err(error) => {
      println!("{}", error);
      sys::exit(1);
    }
}

fn inner_main() -> Result<(), Error> {
  ...
}

Ich komme zu diesem Thema, da für die Produktionsqualität das Hinzufügen von Beträgen zu im Wesentlichen einem benutzerdefinierten Fehlertyp für die Ausgabe ohnehin eine gute Richtung zu sein scheint, um die Leute zu pushen. Dies scheint parallel dazu zu sein, wie beispielsweise panic! standardmäßig Debug-Qualitätsmeldungen ausgibt.

@nikomatsakis Das ist vielleicht eine bessere langfristige Sichtweise, und ich finde Ihr Endergebnis appetitlich. Es wäre schön, wenn diese Fehlertypen eines Tages in std landen würden, damit sie mit möglichst wenig Overhead in Beispielen verwendet werden können. :-)

Prozesshinweis: Ich habe versucht, mein anfängliches Feedback hier mit „Gibt es irgendetwas, was getan werden kann“ zu formulieren, anstatt „Lasst uns etwas ändern, das sich bereits stabilisiert hat“, um mich auf das übergeordnete Ziel zu konzentrieren, anstatt nach etwas zu fragen, das wir haben kann das sicher nicht. :-)

Na das hat nicht lange gedauert: https://crates.io/crates/exitfailure 😆

Ich frage mich, wie viele Leute überrascht sind, wenn sie das Debug -Merkmal im Vergleich zum Display -Merkmal verwenden. Sowohl der ursprüngliche Diskussionsentwurf als auch der endgültige RFC verwenden Display . Dies ähnelt dem universellen Impl-Trait-Vorfall, bei dem die Leute dachten, sie würden eine Sache bekommen, aber erst wissen, dass sie eine andere bekommen, nachdem sie sich stabilisiert hat. Auch wenn man bedenkt, dass viele Leute, die überrascht sind, diese Funktion nicht verwenden werden, denke ich, dass es keinen großen Backslash geben wird, wenn wir sie in der nächsten Ausgabe ändern.

@WiSaGaN Die Details eines RFC können und werden sich von Zeit zu Zeit ändern. Das ist zu erwarten und gesund. RFCs sind Designdokumente und keine perfekte Darstellung dessen, was ist und immer sein wird. Das Auffinden von Informationen ist schwierig, und es ist meine Schuld, dass ich nicht mehr aufpasse. Ich hoffe, dass wir es vermeiden können, die Stabilität dieses Features erneut in Frage zu stellen, und uns stattdessen auf die übergeordneten Ziele konzentrieren können, auf die wir angemessen reagieren können.

@nikomatsakis Das Hauptproblem, das ich bei der Bevorzugung von Quick-n-Dirty-Fällen sehe, ist, dass es bereits eine Möglichkeit gibt, sie anzugehen: .unwrap() . Aus dieser Perspektive ist ? also nur eine andere Möglichkeit, schnelle und schmutzige Dinge zu schreiben, aber es gibt keine ähnlich einfache Möglichkeit, ausgefeilte Dinge zu schreiben.

Natürlich ist das Schiff gesegelt, also sind wir meistens am Arsch. Ich würde gerne sehen, dass dies in der nächsten Ausgabe geändert wird, wenn möglich.

@Kixunil , unwrap ist wirklich kein guter Weg, um Dinge anzusprechen, da der Titel dieses Threads besagt, dass wir gerne in der Lage sein würden, den syntaktischen Zucker ? in main zu verwenden . Ich kann verstehen, woher Sie kommen (tatsächlich war es Teil meines anfänglichen Zögerns bei der Verwendung von ? für die Handhabung von Optionen), aber mit der Einbeziehung von ? in die Sprache ist dieses Problem a ziemlich nette Usability gewinnen IMHO. Wenn Sie eine schönere Ausgabe benötigen, gibt es Optionen für Sie. Sie geben keine Dinge an Benutzer frei, ohne sie vorher zu testen, und die Entdeckung, dass Sie einen benutzerdefinierten Typ für die Ausgabe von main benötigen, wird eine ziemlich schnelle Erkenntnis sein.

Was das "Warum" angeht, wir möchten ? in main , bedenken Sie, wie seltsam es jetzt ist. Sie können es praktisch überall sonst verwenden (da Sie die Kontrolle über den Rückgabetyp haben). Die main -Funktion fühlt sich dann irgendwie besonders an, was sie nicht sollte.

.unwrap() ist eine Quick-and-Dirty-Lösung, die nicht komponiert wird, sodass Sie beim Skizzieren eines Programms keinen Code in main() hinein- und herausrechnen können.

Im Vergleich dazu ist ? eine Quick-and-Dirty-Lösung, die komponiert, und um es nicht -quick-and-dirty zu machen, müssen Sie den richtigen Rückgabetyp auf main() setzen, nicht den Code selbst zu ändern.

@Screwtapello ah, jetzt ergibt es für mich Sinn. Danke!

Ich möchte nur zum Ausdruck bringen, dass ich dachte, das Ziel davon wäre, ? in main für alle Anwendungen zu bringen, ohne auf zusätzliche Wrapper zurückgreifen zu müssen ... Wenn es nur zum Testen ist, tue ich es nicht sehe viel Nutzen darin und werde leider bei .unwrap() bleiben.

@oblitum Es ist nicht nur zum Testen. Es wird einfach (standardmäßig) nicht das Format Display verwendet. Dies kann die gesuchte Ausgabe sein oder auch nicht, aber sie ist mit ziemlicher Sicherheit besser als die Ausgabe von .unwrap . Vor diesem Hintergrund könnte es Ihren Code "schöner" machen, ? zu verwenden. In jedem Fall ist es immer noch möglich, die Ausgabe von ? -Fehlern in main anzupassen, wie @nikomatsakis oben beschrieben hat.

Wäre es möglich, einen neuen Typ in stdlib für die Vitrine einzuführen? Etwas wie:

#[derive(Clone, Copy, PartialEq, PartialOrd, Eq, Ord, Debug, Hash)]
#[must_use]
struct DisplayResult<E: fmt::Display>(Result<(), E>)

impl<E> Termination for DisplayResult<E> {
    // ... same as Result, but use "{}" instead of "{:?}"
}

impl<E> From<Result<(), E>> for DisplayResult<E> {}
impl<E> Deref<Result<(), E>> for DisplayResult<E> {}
impl<E> Try for DisplayResult<E> {}
// ...

Dies sollte es Benutzern ermöglichen, Folgendes zu schreiben:

fn main() -> DisplayResult<MyError> {
    // Ordinary code; conversions happen automatically via From, Try, etc.
}

Die wichtigsten Beweggründe sind hier:

  • Anzeige wird verwendet, wenn ein Fehler zurückgegeben wird
  • Benutzer müssen ihre Fehler- oder Ergebnistypen nicht extra umschließen; Sie können einfach ? in ihrer Hauptfunktion verwenden.

Nachtrag: Mir kommt in den Sinn, dass die Semantik von ? und From dies möglicherweise nicht so implizit funktionieren lässt, wie ich es gerne hätte. Ich weiß, dass ? über From zwischen Fehlertypen konvertiert, aber ich weiß nicht, ob es für verschiedene Implementierer von Try dasselbe tut. Das heißt, es wird von Result<?, io::Error> in Result<?, FromIoError> konvertiert, aber nicht unbedingt von Result<?, Err> in DisplayResult<Result<?, Err>> . Grundsätzlich suche ich nach so etwas, das funktioniert:

fn main() -> DisplayResult<io::Error> {
    let f = io::File::open("path_to_file")?;
    let result = String::new();
    f.read_to_string(result)?
}

Unter der Annahme, dass dies nicht funktioniert, könnte dies vielleicht stattdessen zu einem einfachen bedingten Kompilierungs-Flag in Cargo.toml gemacht werden?

@Lucretiel : Sie scheinen anzunehmen, dass Programmierer hauptsächlich nur eine Exit-Nachricht auf der Konsole drucken möchten. Dies gilt nicht für nicht-technische Desktop-Anwendungen, Daemons mit Protokollierung usw. Ich ziehe es vor, dass wir der Standardbibliothek keine „allgemein nützlichen“ Dinge hinzufügen, ohne zwingende Beweise (z. B. Zahlen), dass sie dort enthalten sein sollten.

Ich gehe davon aus, dass Programmierer, die ein Result<(), E> where E: Error von main zurückgeben, daran interessiert sind, eine Fehlermeldung auf der Konsole auszugeben, ja. Sogar das aktuelle Verhalten, das über Debug gedruckt wird, ist "Ausgabe einer Exit-Nachricht an die Konsole".

Es scheint in diesem Thread breite Zustimmung zu geben, dass etwas auf stderr gedruckt werden sollte; Die wichtigsten Entscheidungen, die getroffen werden müssen, sind "Sollte es mit "{}" oder "{:?}" oder etwas anderem gedruckt werden, und wie konfigurierbar sollte es sein, wenn überhaupt".

@Lucretiel , für mich steuert der Wert der Rückgabe eines Result den Exit-Status gut. Ob man etwas drucken sollte, sollte man nicht am besten dem Panik-Handler überlassen? Es stellt sich nicht nur die Frage, ob etwas auf stderr gedruckt werden soll, sondern auch, was gedruckt werden soll (Backtrace, Fehlermeldung usw.?). Siehe https://github.com/rust-lang/rust/issues/43301#issuecomment -389099936, insb. der letzte Teil.

Ich habe dies kürzlich verwendet und wünschte, es würde Display heißen. Ziemlich sicher, dass wir hier falsch angerufen haben.

Ein Prozessfehler, der mir aufgefallen ist, ist, dass die Entscheidung eher vom lang-Team (hauptsächlich Niko und ich) als vom libs-Team getroffen wurde, aber der fragliche Code lebt vollständig in std. Möglicherweise hätten Bibliotheken eine bessere Entscheidung getroffen.

@ohneBoote ist es zu spät, um sich zu ändern? Mir ist aufgefallen, dass die Funktion in den Dokumenten immer noch als nightly/experimental gekennzeichnet ist, aber es ist möglich, dass ich einen Teil der Granularität hinter dem Stabilisierungsprozess nicht verstehe.

Auch ich würde diese Änderung gerne sehen und bedauere, dass ich mich nicht stärker dafür eingesetzt habe, als das Team mich bat, die Implementierung von Display in Debug zu ändern.

@Lucretiel Termination Merkmal ist nächtlich, aber das Feature selbst (das Termination Implementierungen von main /tests zurückgibt) ist bereits stabil.

Gibt es ein Ticket oder einen Zeitplan für die Stabilisierung des Namens des Merkmals?

@xfix Das würde bedeuten, dass es möglich ist, die Implementierung zu ändern, oder? Das Verhalten ändert sich nicht ( Termination zurückgegeben von main ), aber die Eigenschaft selbst würde sich ändern von:

impl<E: Debug> Termination for Result<(), E> ...

zu:

impl<E: Display> Termination for Result<(), E> ...

Es ist möglich, die Implementierung zu ändern, oder?

Nicht ohne Abwärtskompatibilität zu brechen. Dieser Code wird im heutigen stabilen Rust kompiliert:

#[derive(Debug)]
struct X;

fn main() -> Result<(), X> {
    Ok(())
}

Mit der vorgeschlagenen Änderung, Display zu erfordern, würde es die Kompilierung stoppen.

Wie bereits erwähnt , ist es möglich, dass so etwas wie Spezialisierung hilfreich sein könnte, aber mein derzeitiges Verständnis von Spezialisierung ist, dass dies nur für die Typen nützlich wäre, die sowohl Display als auch Debug implementieren (dh so, dass es eine speziellere Implementierung).

trait ResultTerm {
    fn which(&self);
}
impl<T: Debug> ResultTerm for T {
    default fn which(&self) {
        println!("{:?}", self)
    }
}
impl<T: Debug + Display> ResultTerm for T {
    fn which(&self) {
        println!("{}", self)
    }
}

enum MyResult<T, E> {
    Ok(T),
    Err(E),
}

impl<T, E> Termination for MyResult<T, E>
where
    E: ResultTerm,
{
    fn report(self) -> i32 {
        match self {
            MyResult::Err(e) => {
                e.which();
                1
            }
            _ => 0,
        }
    }
}

Spielplatz

Dies mag ausreichen, um alle gängigen Fälle abzudecken, aber es legt die Spezialisierung öffentlich offen.

Ah, ich verstehe, was du mit stabilem Rost meinst. Aus irgendeinem Grund war mir nicht bewusst, dass dies in Stable verfügbar war.

Ist dies etwas, das zur Kompilierzeit mit einem Cargo-Flag umgeschaltet werden könnte? Genauso wie andere Dinge schaltbar sind, wie panic = "abort" .

Vielleicht? Ich könnte mir vorstellen, dass es möglich ist, in einer anderen Rust-Edition ein anderes Verhalten für ? in main zu haben. Wahrscheinlich nicht 2018, aber vielleicht 2021?

Verschiedene Crates im selben Programm können in verschiedenen Editionen sein, aber dasselbe std verwenden, also müssen die in std verfügbaren Eigenschaften und ihre Impls in allen Editionen gleich sein. Was wir theoretisch tun könnten (ich befürworte das nicht) ist, zwei Merkmale zu haben, Termination und Termination2 , und den Rückgabetyp von main() zu benötigen, um eines oder das zu implementieren andere abhängig von der Ausgabe der Kiste, die main() definiert.

Ich glaube nicht, dass wir irgendetwas verwerfen müssen. Ich werde @Aaronepowers Ansicht wiederholen , dass die Beschränkung auf nur Anzeigetypen dazu dienen könnte, Quick-Script-Benutzer (das Ableiten von Debug ist trivial, das Implementieren von Display nicht) genauso unzufrieden zu machen, wie die aktuelle Situation Produktionsbenutzer unzufrieden macht. Selbst wenn wir könnten, glaube ich nicht, dass es letztendlich ein Vorteil für uns wäre. Ich denke, der von @shepmaster dargelegte Spezialisierungsansatz ist vielversprechend, da er das folgende Verhalten erzeugen würde:

  1. Bei einem Typ, der über Debug, aber nicht über Display verfügt, wird die Debug-Ausgabe standardmäßig gedruckt
  2. Bei einem Typ, der sowohl Debug als auch Display hat, wird seine Display-Ausgabe standardmäßig gedruckt
  3. Ein Typ, der über Display, aber nicht über Debug verfügt, führt zu einem Compilerfehler

Jemand könnte Punkt 2 ablehnen, falls Sie einen Typ mit Display haben, aber aus irgendeinem Grund Debug drucken möchten ; Ich denke, dieser Fall würde durch Nikos Vorschlag verschiedener vorgefertigter Ausgabeformate für Fehlertypen angegangen (die es wahrscheinlich wert sind, untersucht zu werden, selbst wenn wir uns für die Spezialisierungsroute entscheiden).

Was Punkt 3 angeht, ist er ein wenig ruckelig (vielleicht hätten wir in 1.0 trait Display: Debug machen sollen?), aber wenn sich jemand die Mühe gemacht hat, ein Display-Impl zu schreiben, dann ist es nicht viel zu verlangen, dass er darauf klatscht eine einzelne Ableitung (die sie, wie ich vermute, wahrscheinlich sowieso haben ... gibt es jemanden da draußen, der grundsätzlich etwas dagegen hat, Debug auf seinem Display-Typ zu haben?).

Die Beschränkung auf nur Display-Typen könnte dazu dienen, Quick-Script-Benutzer nur unzufrieden zu machen (das Ableiten von Debug ist trivial, das Implementieren von Display ist es nicht).

Ein Fall, in dem Display Debug vorzuziehen ist, ist die Verwendung &'static str oder String als Fehlertyp. Wenn Sie überhaupt einen benutzerdefinierten Fehlertyp haben, auf den Sie #[derive(Debug)] klatschen können, verbrauchen Sie bereits einige nicht triviale Energie für die Fehlerbehandlung.

@SimonSapin Ich würde nicht sagen, dass es in einer schnellen Skriptumgebung vorzuziehen ist, oder eher nicht vorzuziehen genug, dass ich in einer Position wäre, in der ich wollte, dass der String im Format Display gedruckt wird, ohne sich die Mühe machen zu wollen Um richtig formatierte Fehler zu haben, wird für mich Debug bevorzugt, weil das Fehlerformat, wenn es String ist, nicht gut geformt ist, sodass ich mit Err("…") den Fehler leicht visuell von jedem anderen unterscheiden kann Ausgabe, die möglicherweise ausgegeben wurde.

FWIW formatiert ist nicht der Result<_, E> -Wert, sondern nur der darin enthaltene E -Wert. In der Ausgabe sehen Sie also nicht Err( . Der aktuelle Code fügt jedoch ein Error: -Präfix hinzu, das vermutlich erhalten bleiben würde, wenn Display verwendet wird. Abgesehen davon, dass Display keine Anführungszeichen druckt, würde Display auch den Inhalt der Zeichenfolge nicht mit einem Backslash versehen, was meiner Meinung nach wünschenswert ist, wenn es darum geht, Benutzern eine (Fehler-)Meldung anzuzeigen.

https://github.com/rust-lang/rust/blob/cb6eeddd4dcefa4b71bb4b6bb087d05ad8e82145/src/libstd/process.rs#L1527 -L1533

Ich denke, angesichts der Situation ist das Beste, was wir tun können, die Spezialisierung auf Typen, die Display + Debug implementieren, um die Display -Implementierung zu drucken. Es ist bedauerlich, dass Typen, die Debug nicht implementieren, nicht als Fehler verwendet werden können (bis wir Schnittmengen-Impls unterstützen), aber es ist das Beste, was wir tun konnten.

Die Hauptfrage ist, ob dieses Impl unter unsere derzeitige Richtlinie für spezialisierte Impls in Std. fällt oder nicht. Wir haben im Allgemeinen eine Regel, dass wir die Spezialisierung nur in std verwenden, wo wir keine stabile Garantie dafür bieten, dass sich das Verhalten später nicht ändert (weil die Spezialisierung selbst eine instabile Funktion ist). Fühlen wir uns wohl dabei, dieses Impl jetzt bereitzustellen, da wir uns bewusst sind, dass es möglich ist, dass diese Typen eines Tages wieder ihre Debug -Ausgabe drucken, wenn wir schließlich die Spezialisierung als Feature entfernen?

Eigentlich denke ich, dass wir diese Spezialisierung im Moment nicht zulassen können, weil sie dazu verwendet werden könnte, Ungesundheit einzuführen. Diese impl ist eigentlich genau die Art von Spezialisierung, die uns so viele Probleme bereitet!

use std::cell::Cell;
use std::fmt::*;

struct Foo<'a, 'b> {
     a: &'a Cell<&'a i32>,
     b: &'b i32,
}

impl<'a, 'b> Debug for Foo<'a, 'b> {
    fn fmt(&self, _: &mut Formatter) -> Result {
        Ok(())
    }
}

impl<'a> Display for Foo<'a, 'a> {
    fn fmt(&self, _: &mut Formatter) -> Result {
        self.a.set(self.b);
        Ok(())
    }
}

Aufgrund der Art und Weise, wie die Spezialisierung derzeit funktioniert, könnte ich report auf einem Foo<'a, 'b> aufrufen, wobei 'b 'a nicht überdauert, und es ist derzeit durchaus möglich, dass der Compiler wählt das Impl aus, das die Display -Instanz aufruft, obwohl die Lebensdaueranforderungen nicht erfüllt sind, wodurch der Benutzer die Lebensdauer einer Referenz ungültig verlängern kann.

Ich weiß nicht, wie es euch geht, aber wenn ich schnelle „Skripte“ schreibe, verdränge ich einfach unwrap() alles. Es ist 100 Mal besser, weil es Backtraces hat, die mir kontextbezogene Informationen liefern. Ohne sie kann ich, falls ich let ... = File::open() mehr als einmal in meinem Code habe, bereits nicht erkennen, welches fehlgeschlagen ist.

Ich weiß nicht, wie es euch geht, aber wenn ich schnelle „Skripte“ schreibe, verdränge ich einfach unwrap() alles. Es ist 100 Mal besser, weil es Backtraces hat, die mir kontextbezogene Informationen liefern. Ohne sie kann ich, falls ich let ... = File::open() mehr als einmal in meinem Code habe, bereits nicht erkennen, welches fehlgeschlagen ist.

Das ist eigentlich der Grund, warum ich mich so stark für Display fühle, da ich in diesem Fall im Allgemeinen ein map_err mache, das den Dateinamen und andere relevante Kontextinformationen anhängt. Im Allgemeinen ist die Debug-Ausgabe meiner Erfahrung nach weniger hilfreich, um herauszufinden, was tatsächlich schief gelaufen ist.

@Kixunil : Alternativ würde ich eine ausgefeiltere Fehlerbehandlungsstrategie und/oder umfangreichere Protokollierung und/oder Debugging bevorzugen.

Ich denke, "Implement the RFC" kann abgehakt werden, oder? Zumindest danach ! :Lächeln:

Verzeihen Sie mir, wenn dies völlig ahnungslos ist, aber könnten Sie nicht die Spezialisierung verwenden, um die Fehlerkette nach Möglichkeit zu melden:

use std::error::Error;

impl<E: fmt::Debug> Termination for Result<!, E> {
    default fn report(self) -> i32 {
        let Err(err) = self;
        eprintln!("Error: {:?}", err);
        ExitCode::FAILURE.report()
    }
}

impl<E: fmt::Debug + Error> Termination for Result<!, E> {
    fn report(self) -> i32 {
        let Err(err) = self;
        eprintln!("Error: {:?}", err);

        for cause in Error::chain(&err).skip(1) {
            eprintln!("Caused by: {:?}", cause);
        }
        ExitCode::FAILURE.report()
    }
}

https://github.com/rust-lang/rfcs/blob/f4b8b61a414298ba0f76d9b786d58ccdc34a44bb/text/1937-ques-in-main.md#L260 -L270

impl<T: Termination, E: Display> Termination for Result<T, E> {
    fn report(self) -> i32 {
        match self {
            Ok(val) => val.report(),
            Err(ref err) => {
                print_diagnostics_for_error(err);
                EXIT_FAILURE
            }
        }
    }
}

Gibt es einen Grund, warum dieser Teil des RFC nicht implementiert wurde?

Wir haben einige Änderungen an den genauen Sätzen von Traits und Impls vorgenommen, die verwendet werden sollen, aber ich erinnere mich an dieser Stelle nicht an die Details – und ich glaube, es bleiben noch ein paar Details, die stabilisiert werden müssen. Weitere Einzelheiten finden Sie in den Stabilisierungsberichten, die in der obersten Ausgabe verlinkt sind.

Wie ist der Status dieser Funktion? Gibt es einen Vorschlag zur Stabilisierung?

@GrayJack Dieses Problem sollte geschlossen werden, da es vor einiger Zeit gelandet ist.

Stimmt, und ich war etwas verwirrt, ich bin über den Link in der Eigenschaft Termination hierher gekommen und habe danach gefragt, ob es ein richtiges Problem für termination_trait_lib gibt?

Dieses Problem sollte geschlossen werden

Dieses Problem ist noch offen, um die Stabilisierung von Termination zu verfolgen.

War diese Seite hilfreich?
0 / 5 - 0 Bewertungen

Verwandte Themen

lambda-fairy picture lambda-fairy  ·  3Kommentare

modsec picture modsec  ·  3Kommentare

Robbepop picture Robbepop  ·  3Kommentare

dnsl48 picture dnsl48  ·  3Kommentare

SharplEr picture SharplEr  ·  3Kommentare