Vorschlag: try - integrierte Fehlerprüfungsfunktion

Zusammenfassung


Es wird ein neues try Konstrukt vorgeschlagen, das speziell entwickelt wurde, um Ausdrücke zu eliminieren, die üblicherweise mit der Fehlerbehandlung in Go verbunden sind. Dies ist die einzige Änderung in der Sprache. Autoren unterstützen die Verwendung von defer und Standardbibliotheksfunktionen, um Fehler anzureichern oder zu verpacken. Diese kleine Erweiterung eignet sich für die meisten Szenarien, praktisch ohne die Sprache zu komplizieren.


Das try Konstrukt ist leicht zu erklären, leicht zu implementieren, diese Funktionalität ist orthogonal zu anderen Sprachkonstrukten und vollständig abwärtskompatibel. Es ist auch erweiterbar, wenn wir es in Zukunft wollen.


Der Rest dieses Dokuments ist wie folgt organisiert: Nach einer kurzen Einführung geben wir eine Definition der integrierten Funktion und erläutern deren Verwendung in der Praxis. Im Diskussionsteil werden alternative Vorschläge und das aktuelle Design besprochen. Am Ende werden die Schlussfolgerungen und der Umsetzungsplan mit Beispielen sowie einem Abschnitt mit Fragen und Antworten gegeben.


Einleitung


Auf der letzten Gophercon-Konferenz in Denver stellten Mitglieder des Go-Teams (Russ Cox, Marcel van Lohuizen) einige neue Ideen vor, wie die mühsame manuelle Fehlerbehandlung in Go ( Entwurfsentwurf ) reduziert werden kann. Seitdem haben wir sehr viel Feedback erhalten.


Wie Russ Cox in seiner Überprüfung des Problems erklärte, ist es unser Ziel, die Fehlerbehandlung einfacher zu gestalten, indem wir die Menge an Code reduzieren, die speziell für die Fehlerprüfung verwendet wird. Wir möchten auch das Schreiben von Fehlerbehandlungscode komfortabler gestalten und die Wahrscheinlichkeit erhöhen, dass Entwickler noch Zeit für die Korrektur der Fehlerbehandlung verwenden. Gleichzeitig möchten wir den Fehlerbehandlungscode im Programmcode deutlich sichtbar lassen.


Die im Entwurfsentwurf diskutierten Ideen konzentrieren sich auf die neue unäre check , die die explizite Überprüfung des Fehlerwerts vereinfacht, der aus einem Ausdruck (normalerweise einem Funktionsaufruf) erhalten wird, sowie auf die Deklaration von Fehlerbehandlungsroutinen ( handle ) und eine Reihe von Regeln, die diese beiden neuen Sprachkonstrukte verbinden.


Die meisten Rückmeldungen konzentrierten sich auf die Details und die Komplexität des handle , und die Idee eines check Bedieners erwies sich als attraktiver. Tatsächlich haben mehrere Mitglieder der Community die Idee eines check aufgegriffen und erweitert. Hier sind einige Beiträge, die unserem Angebot am ähnlichsten sind:



Der aktuelle Vorschlag war zwar im Detail unterschiedlich, stützte sich jedoch auf diese drei und im Allgemeinen auf die Rückmeldungen zu dem im letzten Jahr vorgeschlagenen Entwurfsentwurf.


Um das Bild zu vervollständigen, möchten wir darauf hinweisen, dass auf dieser Wiki-Seite noch mehr Vorschläge zur Fehlerbehandlung zu finden sind. Es ist auch erwähnenswert, dass Liam Breck umfangreiche Anforderungen an den Fehlerbehandlungsmechanismus gestellt hat.


Schließlich haben wir nach der Veröffentlichung dieses Vorschlags erfahren, dass Ryan Hileman try fünf Jahren mit dem og rewriter-Tool implementiert und erfolgreich in realen Projekten eingesetzt hat. Siehe ( https://news.ycombinator.com/item?id=20101417 ).


Eingebaute Try-Funktion


Angebot


Wir empfehlen, ein neues funktionsähnliches Sprachelement namens try hinzuzufügen, das mit einer Signatur aufgerufen wird


 func try(expr) (T1, T2, ... Tn) 

expr bedeutet expr einen Ausdruck eines Eingabeparameters (normalerweise ein Funktionsaufruf), der n + 1 Werte der Typen T1, T2, ... Tn und error für den letzten Wert zurückgibt. Wenn expr ein einzelner Wert ist (n = 0), muss dieser Wert vom Typ error und try gibt kein Ergebnis zurück. Das Aufrufen von try mit einem Ausdruck, der nicht den letzten Wert des Typfehlers error führt zu einem Kompilierungsfehler.


Das try Konstrukt kann nur in einer Funktion verwendet werden, die mindestens einen Wert zurückgibt und deren letzter Rückgabewert vom Typ error . Das Aufrufen von try in anderen Kontexten führt zu einem Kompilierungsfehler.


Rufen Sie try mit der Funktion f() wie im Beispiel auf


 x1, x2, … xn = try(f()) 

führt zu folgendem Code:


 t1, … tn, te := f() // t1, … tn,  ()   if te != nil { err = te //  te    error return //     } x1, … xn = t1, … tn //     //     

Mit anderen Worten, wenn der letzte von expr zurückgegebene expr nil , geben Sie einfach die ersten n Werte zurück und entfernen Sie die letzte nil .


Wenn der letzte von expr Wert nicht nil , dann:


  • Der error der einschließenden Funktion (im oben genannten Pseudocode err , obwohl dies ein beliebiger Bezeichner oder ein unbenannter Rückgabewert sein kann) empfängt den von expr zurückgegebenen expr
  • Es gibt einen Ausgang aus der Hüllkurvenfunktion
  • Wenn die umschließende Funktion zusätzliche Rückgabeparameter enthält, behalten diese Parameter die Werte bei, die vor dem try Aufruf in ihnen enthalten waren.
  • Wenn die umschließende Funktion zusätzliche unbenannte Rückgabeparameter hat, werden die entsprechenden Nullwerte für sie zurückgegeben (was identisch ist mit dem Speichern ihrer ursprünglichen Nullwerte, mit denen sie initialisiert werden).

Wenn try wie im obigen Beispiel in mehreren Zuweisungen verwendet wird und ein Fehler ungleich Null (im Folgenden nicht null - ca. Per.) Festgestellt wird, wird die Zuweisung (nach Benutzervariablen) nicht ausgeführt, und keine der Variablen auf der linken Seite der Zuweisung ändert sich. Das heißt, try verhält sich wie ein Funktionsaufruf: Die Ergebnisse sind nur verfügbar, wenn try die Kontrolle an den Aufrufer zurückgibt (im Gegensatz zum Fall mit einer Rückgabe von der umschließenden Funktion). Wenn die Variablen auf der linken Seite der Zuweisung Rückgabeparameter sind, führt die Verwendung von try zu einem Verhalten, das sich von dem typischen Code unterscheidet, der jetzt auftritt. Wenn beispielsweise a,b, err als Rückgabeparameter einer umschließenden Funktion bezeichnet werden, lautet der folgende Code:


 a, b, err = f() if err != nil { return } 

weist den Variablen a, b und err immer Werte zu, unabhängig davon, ob der Aufruf von f() einen Fehler zurückgegeben hat oder nicht. Gegenteilige Herausforderung


 a, b = try(f()) 

Lassen Sie im Fehlerfall a und b unverändert. Trotz der Tatsache, dass dies eine subtile Nuance ist, glauben wir, dass solche Fälle ziemlich selten sind. Wenn ein bedingungsloses Zuweisungsverhalten erforderlich ist, müssen Sie if Ausdrücke weiterhin verwenden.


Verwenden Sie


Die Definition von try Ihnen explizit, wie Sie es verwenden: Viele if Ausdrücke, die nach Fehlern try können durch try . Zum Beispiel:


 f, err := os.Open(filename) if err != nil { return …, err //       } 

kann vereinfacht werden


 f := try(os.Open(filename)) 

Wenn die aufrufende Funktion keinen Fehler try kann try nicht verwendet werden (siehe Abschnitt Diskussion). In diesem Fall sollte der Fehler auf jeden Fall lokal verarbeitet werden (da keine Fehlerrückgabe erfolgt), und in diesem Fall bleibt der geeignete Mechanismus zur Überprüfung auf Fehler bestehen.


Generell ist es unser Ziel, nicht alle möglichen Fehlerprüfungen durch eine try zu ersetzen. Code, der eine andere Semantik erfordert, kann und sollte weiterhin verwendet werden, if Ausdrücke und explizite Variablen mit Fehlerwerten.


Testen und versuchen


Bei einem unserer früheren Versuche, eine Spezifikation zu schreiben (siehe Abschnitt Design-Iteration unten), wurde try entwickelt, um in Panik zu geraten, wenn ein Fehler auftritt, wenn er innerhalb einer Funktion ohne Rückgabefehler verwendet wird. Dies ermöglichte die Verwendung von try in Unit-Tests basierend auf dem testing der Standardbibliothek.


Als eine der Optionen ist es möglich, Testfunktionen mit Signaturen im Testpaket zu verwenden


 func TestXxx(*testing.T) error func BenchmarkXxx(*testing.B) error 

um die Verwendung von try in Tests zu ermöglichen. Eine Testfunktion, die einen Fehler ungleich Null zurückgibt, ruft implizit t.Fatal(err) oder b.Fatal(err) . Dies ist eine kleine Bibliotheksänderung, bei der je nach Kontext unterschiedliche Verhaltensweisen (Rückkehr oder Panik) für den try vermieden werden müssen.


Einer der Nachteile dieses Ansatzes besteht darin, dass t.Fatal und b.Fatal die Zeilennummer, auf die der Test fiel, nicht zurückgeben können. Ein weiterer Nachteil ist, dass wir auch die Untertests irgendwie ändern müssen. Die Lösung für dieses Problem ist eine offene Frage; In diesem Dokument werden keine spezifischen Änderungen am testing .


Siehe auch # 21111 , in dem vorgeschlagen wird, Beispielfunktionen zu erlauben, einen Fehler zurückzugeben.


Fehlerbehandlung


Der ursprüngliche Entwurfsentwurf befasste sich hauptsächlich mit der Sprachunterstützung für Umbruch- oder Erweiterungsfehler. Der Entwurf schlug ein neues Schlüsselworthandle und eine neue Methode zum Deklarieren von Fehlerbehandlungsroutinen vor . Dieses neue Sprachkonstrukt zog aufgrund der nicht trivialen Semantik Probleme wie Fliegen auf sich, insbesondere wenn man seine Auswirkungen auf den Ausführungsfluss berücksichtigt. Insbesondere die handle hat sich kläglich mit der defer gekreuzt, wodurch die neue Sprachfunktion nicht orthogonal zu allem anderen war.


Dieser Vorschlag reduziert den ursprünglichen Entwurfsentwurf auf das Wesentliche. Wenn eine Anreicherung oder ein Fehlerumbruch erforderlich ist, gibt es zwei Ansätze: Anhängen an if err != nil { return err} oder "Deklarieren" eines Fehlerbehandlers innerhalb des defer Ausdrucks:


 defer func() { if err != nil { //      -   err = … // /  } }() 

In diesem Beispiel ist err der Name des Rückgabeparameters vom Typ error einschließenden Funktion.


In der Praxis stellen wir uns solche Hilfsfunktionen vor wie


 func HandleErrorf(err *error, format string, args ...interface{}) { if *err != nil { *err = fmt.Errorf(format + ": %v", append(args, *err)...) } } 

oder ähnliches. Das fmt Paket kann für solche Helfer zu einem natürlichen Ort werden (es bietet bereits fmt.Errorf ). Mithilfe von Hilfsprogrammen wird die Definition eines Fehlerbehandlers in vielen Fällen auf eine einzelne Zeile reduziert. Um beispielsweise den Fehler aus der Funktion "Kopieren" zu bereichern, können Sie schreiben


 defer fmt.HandleErrorf(&err, "copy %s %s", src, dst) 

Wenn fmt.HandleErrorf implizit Fehlerinformationen hinzufügt, ist eine solche Konstruktion ziemlich einfach zu lesen und hat den Vorteil, dass sie implementiert werden kann, ohne neue Elemente der Sprachsyntax hinzuzufügen.


Der Hauptnachteil dieses Ansatzes besteht darin, dass der zurückgegebene Fehlerparameter benannt werden muss, was möglicherweise zu einer weniger genauen API führt (siehe FAQ zu diesem Thema). Wir glauben, dass wir uns daran gewöhnen werden, wenn der geeignete Schreibstil für Code festgelegt ist.


Effizienzverschiebung


Ein wichtiger Gesichtspunkt bei der Verwendung von defer als Fehlerbehandlungsroutine ist die Effizienz. Der defer Ausdruck wird als langsam angesehen . Wir möchten nicht zwischen effizientem Code und guter Fehlerbehandlung wählen. Unabhängig von diesem Vorschlag diskutierten die Go-Laufzeit- und Compilerteams alternative Implementierungsmethoden und wir glauben, dass wir typische Methoden zur Verwendung von Defer anwenden können, um Fehler zu behandeln, deren Effizienz mit dem vorhandenen „manuellen“ Code vergleichbar ist. Wir hoffen, in Go 1.14 eine schnellere Implementierung von Defer hinzufügen zu können (siehe auch Ticket CL 171158 , was der erste Schritt in diese Richtung ist).


Sonderfälle go try(f), defer try(f)


Das try Konstrukt sieht aus wie eine Funktion, und aus diesem Grund wird erwartet, dass es überall dort verwendet werden kann, wo ein Funktionsaufruf akzeptabel ist. Wenn der try Aufruf jedoch in der go Anweisung verwendet wird, werden die Dinge kompliziert:


 go try(f()) 

Hier wird f() ausgeführt, wenn der go-Ausdruck in der aktuellen Goroutine ausgeführt wird. Die Ergebnisse des Aufrufs von f werden als zu try Argumente übergeben, die in der neuen Goroutine beginnen. Wenn f einen Fehler ungleich Null zurückgibt, wird erwartet, dass try von der umschließenden Funktion zurückkehrt. Es gibt jedoch keine Funktion (und es gibt keinen Rückgabeparameter vom Typ error ), weil Der Code wird in einer separaten Goroutine ausgeführt. Aus diesem Grund schlagen wir vor, try in einem go Ausdruck zu deaktivieren.


Situation mit


 defer try(f()) 

sieht ähnlich aus, aber hier bedeutet die Semantik von defer , dass die Ausführung von try verzögert wird, bis sie von der umschließenden Funktion zurückkehrt. Wie zuvor wird f() ausgewertet, wenn der Aufschub defer , und seine Ergebnisse werden an den zurückgestellten try .


try überprüft den Fehler f() erst im allerletzten Moment zurückgegeben wird, bevor er von der umschließenden Funktion zurückkehrt. Ohne das try ändern, kann ein solcher Fehler einen anderen Fehlerwert überschreiben, den die umschließende Funktion zurückzugeben versucht. Dies verwirrt bestenfalls, im schlimmsten Fall führt es zu Fehlern. Aus diesem Grund schlagen wir vor, dass Sie den Aufruf von try in der defer Anweisung verbieten. Wir können diese Entscheidung jederzeit überdenken, wenn eine solche Semantik angemessen angewendet wird.


Schließlich kann try wie die anderen integrierten Konstrukte nur als Aufruf verwendet werden. Es kann nicht als Wertfunktion oder in einem Variablenzuweisungsausdruck wie in f := try (genau wie f := print und f := new verboten sind).


Die Diskussion


Design-Iterationen


Das Folgende ist eine kurze Diskussion früherer Entwürfe, die zum aktuellen Minimalvorschlag führten. Wir hoffen, dass dies Aufschluss über ausgewählte Designentscheidungen gibt.


Unsere erste Iteration dieses Satzes wurde von zwei Ideen aus dem Artikel „Schlüsselelemente der Fehlerbehandlung“ inspiriert , nämlich der Verwendung der integrierten Funktion anstelle des Operators und der üblichen Go-Funktion zur Behandlung von Fehlern anstelle des neuen Sprachkonstrukts. Im Gegensatz zu dieser Veröffentlichung hatte unser Fehlerbehandler einen festen func(error) error Signaturfunktion func(error) error , um die Sache zu vereinfachen. Ein Fehlerbehandler wird von der try Funktion aufgerufen try wenn ein Fehler aufgetreten ist, bevor try die umschließende Funktion beendet. Hier ist ein Beispiel:


 handler := func(err error) error { return fmt.Errorf("foo failed: %v", err) //   } f := try(os.Open(filename), handler) //      

Dieser Ansatz ermöglichte zwar die Definition effektiver benutzerdefinierter Fehlerbehandlungsroutinen, warf jedoch auch viele Fragen auf, die offensichtlich nicht die richtigen Antworten hatten: Was sollte passieren, wenn nil an die Bearbeiter übergeben wird? Sollten Sie try Panik try geraten, oder dies als Mangel an Handler betrachten? Was ist, wenn der Handler mit einem Fehler ungleich Null aufgerufen wird und dann ein Null-Ergebnis zurückgibt? Bedeutet dies, dass der Fehler "abgebrochen" wird? Oder sollte eine umschließende Funktion einen leeren Fehler zurückgeben? Es gab auch Zweifel, dass die optionale Übertragung eines Fehlerbehandlers Entwickler dazu ermutigen würde, Fehler zu ignorieren, anstatt sie zu korrigieren. Es wäre auch einfach, überall die richtige Fehlerbehandlung durchzuführen, aber eine einmalige Verwendung von try überspringen. Und dergleichen.


In der nächsten Iteration wurde die Möglichkeit, einen benutzerdefinierten Fehlerbehandler zu übergeben, zugunsten der Verwendung von defer zum Umschließen von Fehlern entfernt. Dies schien ein besserer Ansatz zu sein, da dadurch Fehlerbehandler im Quellcode viel deutlicher wahrgenommen wurden. Dieser Schritt beseitigte auch alle Probleme bezüglich der optionalen Übertragung von Handlerfunktionen, verlangte jedoch, dass die zurückgegebenen Parameter mit dem error benannt werden, wenn ein Zugriff erforderlich war (wir entschieden, dass dies normal war). Um try nicht nur für Funktionen nützlich zu machen, die Fehler zurückgeben, war es außerdem erforderlich, das Verhalten von try kontextsensitiv zu machen: Wenn try auf Paketebene verwendet wurde oder wenn es in einer Funktion aufgerufen wurde, die keinen Fehler zurückgibt, wird try automatisch in Panik versetzt, wenn ein Fehler festgestellt wurde. (Und als Nebeneffekt wurde aufgrund dieser Eigenschaft das Sprachkonstrukt in diesem Satz als must statt try .) Das kontextsensitive Verhalten von try (oder must ) schien natürlich und auch sehr nützlich: Es würde viele benutzerdefinierte Funktionen eliminieren, die in Ausdrücken verwendet werden Paketvariablen initialisieren. Es eröffnete auch die Möglichkeit, try in Unit-Tests mit dem Testpaket zu verwenden.


Das kontextsensitive Verhalten von try war jedoch mit Fehlern behaftet: Beispielsweise konnte sich das Verhalten einer Funktion, die try verwendet, beim Hinzufügen oder Entfernen eines Rückgabefehlers zur Funktionssignatur leise ändern (Panik oder nicht). Dies schien eine zu gefährliche Eigenschaft. Die naheliegende Lösung bestand darin, die try Funktionalität in zwei separate must and- try Funktionen aufzuteilen (sehr ähnlich der in # 31442 vorgeschlagenen ). Dies würde jedoch zwei integrierte Funktionen erfordern, während nur try direkt mit einer besseren Unterstützung der Fehlerbehandlung zusammenhängt.


Daher haben wir in der aktuellen Iteration beschlossen, anstelle der zweiten integrierten Funktion die duale Semantik von try zu entfernen und daher die Verwendung nur in Funktionen zuzulassen, die einen Fehler zurückgeben.


Merkmale des vorgeschlagenen Entwurfs


Dieser Vorschlag ist recht kurz und scheint im Vergleich zum letztjährigen Entwurf einen Schritt zurück zu sein. Wir glauben, dass die ausgewählten Lösungen gerechtfertigt sind:


  • Das Wichtigste zuerst: try hat genau die gleiche Semantik wie die im Original vorgeschlagene check Anweisung ohne handle . Dies bestätigt die Treue des ursprünglichen Entwurfs in einem der wichtigen Aspekte.


  • Die Auswahl einer integrierten Funktion anstelle von Operatoren hat mehrere Vorteile. Es ist kein neues Schlüsselwort wie check erforderlich, wodurch das Design nicht mit vorhandenen Parsern kompatibel wäre. Es ist auch nicht erforderlich, die Syntax von Ausdrücken mit einem neuen Operator zu erweitern. Das Hinzufügen einer neuen integrierten Funktion ist relativ trivial und völlig orthogonal zu anderen Merkmalen der Sprache.


  • Die Verwendung einer Inline-Funktion anstelle eines Operators erfordert die Verwendung von Klammern. Wir sollten try(f()) anstelle von try f() schreiben. Dies ist der (kleine) Preis, den wir für die Abwärtskompatibilität mit vorhandenen Parsern zahlen müssen. Dies macht das Design jedoch auch mit zukünftigen Versionen kompatibel: Wenn wir auf dem Weg entscheiden, dass das Übergeben einer Fehlerbehandlungsfunktion in irgendeiner Form oder das Hinzufügen eines zusätzlichen Parameters zu diesem Zweck eine gute Idee ist, ist das Hinzufügen eines zusätzlichen Arguments zum try Aufruf trivial.


  • Wie sich herausstellte, hat die Notwendigkeit, Klammern zu schreiben, seine Vorteile. In komplexeren Ausdrücken mit mehreren try verbessern Klammern die Lesbarkeit, indem sie sich nicht mehr mit der Priorität von Operatoren befassen müssen, wie in den folgenden Beispielen:



 info := try(try(os.Open(file)).Stat()) //   try info := try (try os.Open(file)).Stat() //  try   info := try (try (os.Open(file)).Stat()) //  try   

try , : try , .. try (receiver) .Stat ( os.Open ).


try , : os.Open(file) .. try ( , try os , , try try ).


, .. .


  • . , . , , , .

Schlussfolgerungen


. , . defer , .


Go - , . , Go append . append , . , . , try .


, , Go : panic recover . error try .


, try , , — — , . Go:


  • , try
  • -

, , . if -.


Implementierung


:


  • Go.
  • try . , . .
  • go/types try . .
  • gccgo . ( , ).
  • .

- , . , . .


Robert Griesemer go/types , () cmd/compile . , Go 1.14, 1 2019.


, Ian Lance Taylor gccgo , .


"Go 2, !" , .


1 , , , Go 1.14 .


Beispiele


CopyFile :


 func CopyFile(src, dst string) (err error) { defer func() { if err != nil { err = fmt.Errorf("copy %s %s: %v", src, dst, err) } }() r := try(os.Open(src)) defer r.Close() w := try(os.Create(dst)) defer func() { w.Close() if err != nil { os.Remove(dst) //    “try”    } }() try(io.Copy(w, r)) try(w.Close()) return nil } 

, " ", defer :


 defer fmt.HandleErrorf(&err, "copy %s %s", src, dst) 

( defer -), defer , .


printSum


 func printSum(a, b string) error { x := try(strconv.Atoi(a)) y := try(strconv.Atoi(b)) fmt.Println("result:", x + y) return nil } 

:


 func printSum(a, b string) error { fmt.Println( "result:", try(strconv.Atoi(a)) + try(strconv.Atoi(b)), ) return nil } 

main :


 func localMain() error { hex := try(ioutil.ReadAll(os.Stdin)) data := try(parseHexdump(string(hex))) try(os.Stdout.Write(data)) return nil } func main() { if err := localMain(); err != nil { log.Fatal(err) } } 

- try , :


 n, err := src.Read(buf) if err == io.EOF { break } try(err) 

Fragen und Antworten


, .


: ?


: check handle , . , handle defer , handle .


: try ?


: try Go . - , . , . , " ". try , .. .


: try try?


: , check , must do . try , . try check (, ), - . . must ; try — . , Rust Swift try ( ). .


: ? Rust?


: Go ; , Go ( ; - ). , ? , . , , , (package, interface, if, append, recover, ...), , (struct, var, func, int, len, image, ..). Rust ? try — Go, , ( ) . , ? . , , (, ..) . . , .


: ( error) , defer , go doc. ?


: go doc , - ( _ ) , . , func f() (_ A, _ B, err error) go doc func f() (A, B, error) . , , , . , , . , , , -, (deferred) . Jonathan Geddes try() .


: defer ?


: defer . , , defer "" . . CL 171758 , defer 30%.


: ?


: , . , ( , ), . defer , . defer - https://golang.org/issue/29934 ( Go 2), .


: , try, error. , ?


: error ( ) , , nil . try . ( , . - ).


: Go , try ?


: try , try . super return -, try Go . try . .


: try , . ?


: try ; , . try ( ), . , if .


: , . try, defer . ?


: , . .


: try ( catch )?


: try — ("") , , ( ) . try ; . . "" . , . , try — . , , throw try-catch Go. , (, ), ( ) , . "" try-catch , . , , . Go . panic , .

Source: https://habr.com/ru/post/de472758/


All Articles