Mehrere Werte im Einzelwertkontext

110

Aufgrund der Fehlerbehandlung in Go habe ich häufig Funktionen mit mehreren Werten. Bisher war die Art und Weise, wie ich das geschafft habe, sehr chaotisch und ich suche nach Best Practices, um saubereren Code zu schreiben.

Angenommen, ich habe folgende Funktion:

type Item struct {
   Value int
   Name string
}

func Get(value int) (Item, error) {
  // some code

  return item, nil
}

Wie kann ich item.Valueelegant eine neue Variable zuweisen ? Bevor itemich die Fehlerbehandlung einführte, kehrte meine Funktion zurück und ich konnte einfach Folgendes tun:

val := Get(1).Value

Jetzt mache ich das:

item, _ := Get(1)
val := item.Value

Gibt es nicht eine Möglichkeit, direkt auf die erste zurückgegebene Variable zuzugreifen?

Speerfischer
quelle
3
itemwird in der Regel nilim Falle eines Fehlers sein. Ohne vorher nach einem Fehler zu suchen, stürzt Ihr Code in diesem Fall ab.
Thomas

Antworten:

83

Bei einer mehrwertigen Rückgabefunktion können Sie beim Aufrufen der Funktion nicht auf Felder oder Methoden eines bestimmten Werts des Ergebnisses verweisen.

Und wenn einer von ihnen ein ist error, ist er aus einem Grund vorhanden (die Funktion kann fehlschlagen), und Sie sollten ihn nicht umgehen, da in diesem Fall Ihr nachfolgender Code möglicherweise auch kläglich fehlschlägt (z. B. was zur Laufzeitpanik führt).

Es kann jedoch Situationen geben, in denen Sie wissen, dass der Code unter keinen Umständen fehlschlägt. In diesen Fällen können Sie eine Hilfsfunktion (oder -methode) bereitstellen, mit der die Funktion verworfen wird error(oder eine Laufzeitpanik ausgelöst wird, wenn sie weiterhin auftritt).
Dies kann der Fall sein, wenn Sie die Eingabewerte für eine Funktion aus Code bereitstellen und wissen, dass sie funktionieren. Gute
Beispiele hierfür sind die Pakete templateund regexp: Wenn Sie zur Kompilierungszeit eine gültige Vorlage oder einen gültigen regulären Ausdruck bereitstellen, können Sie sicher sein, dass sie zur Laufzeit immer fehlerfrei analysiert werden können. Aus diesem Grund templatebietet das Paket die Must(t *Template, err error) *TemplateFunktion und das regexpPaket die MustCompile(str string) *RegexpFunktion: Sie kehren nicht zurückerrors weil bei bestimmungsgemäßer Verwendung die Gültigkeit der Eingabe garantiert ist.

Beispiele:

// "text" is a valid template, parsing it will not fail
var t = template.Must(template.New("name").Parse("text"))

// `^[a-z]+\[[0-9]+\]$` is a valid regexp, always compiles
var validID = regexp.MustCompile(`^[a-z]+\[[0-9]+\]$`)

Zurück zu Ihrem Fall

Wenn Sie sicher Get()sein können, errordass bestimmte Eingabewerte nicht erzeugt werden, können Sie eine Hilfsfunktion erstellen, Must()die das nicht zurückgibt, erroraber eine Laufzeitpanik auslöst, wenn es immer noch auftritt:

func Must(i Item, err error) Item {
    if err != nil {
        panic(err)
    }
    return i
}

Sie sollten dies jedoch nicht in allen Fällen verwenden, nur wenn Sie sicher sind, dass es erfolgreich ist. Verwendung:

val := Must(Get(1)).Value

Alternative / Vereinfachung

Sie können es sogar noch weiter vereinfachen, wenn Sie den Get()Aufruf in Ihre Hilfsfunktion integrieren. Nennen wir ihn MustGet:

func MustGet(value int) Item {
    i, err := Get(value)
    if err != nil {
        panic(err)
    }
    return i
}

Verwendung:

val := MustGet(1).Value

Sehen Sie einige interessante / verwandte Fragen:

wie man mehrere Rückgaben in Golang analysiert

Rückgabe der Karte wie 'ok' in Golang bei normalen Funktionen

icza
quelle
7

Nein, aber das ist gut so, denn Sie sollten immer mit Ihren Fehlern umgehen.

Es gibt Techniken, mit denen Sie die Fehlerbehandlung verschieben können. Siehe Fehler sind Werte von Rob Pike.

ew := &errWriter{w: fd}
ew.write(p0[a:b])
ew.write(p1[c:d])
ew.write(p2[e:f])
// and so on
if ew.err != nil {
    return ew.err
}

In diesem Beispiel aus dem Blog-Beitrag zeigt er, wie Sie einen errWriterTyp erstellen können, der die Fehlerbehandlung verzögert, bis Sie mit dem Aufrufen fertig sind write.

jmaloney
quelle
6

Ja da ist.

Überraschend, oder? Mit einer einfachen muteFunktion können Sie einen bestimmten Wert aus einer Mehrfachrückgabe abrufen :

package main

import "fmt"
import "strings"

func µ(a ...interface{}) []interface{} {
    return a
}

type A struct {
    B string
    C func()(string)
}

func main() {
    a := A {
        B:strings.TrimSpace(µ(E())[1].(string)),
        C:µ(G())[0].(func()(string)),
    }

    fmt.Printf ("%s says %s\n", a.B, a.C())
}

func E() (bool, string) {
    return false, "F"
}

func G() (func()(string), bool) {
    return func() string { return "Hello" }, true
}

https://play.golang.org/p/IwqmoKwVm-

Beachten Sie, wie Sie die Wertnummer wie aus einem Slice / Array auswählen und dann den Typ, um den tatsächlichen Wert zu erhalten.

Sie können mehr über die Wissenschaft dahinter lesen diesem Artikel . Credits an den Autor.

Kesarion
quelle
5

Nein, Sie können nicht direkt auf den ersten Wert zugreifen.

Ich nehme an, ein Hack dafür wäre, ein Array von Werten anstelle von "item" und "err" zurückzugeben und dann einfach zu tun, item, _ := Get(1)[0] aber ich würde dies nicht empfehlen.

Antimon
quelle
3

Wie wäre es mit diesem Weg?

package main

import (
    "fmt"
    "errors"
)

type Item struct {
    Value int
    Name string
}

var items []Item = []Item{{Value:0, Name:"zero"}, 
                        {Value:1, Name:"one"}, 
                        {Value:2, Name:"two"}}

func main() {
    var err error
    v := Get(3, &err).Value
    if err != nil {
        fmt.Println(err)
        return
    }
    fmt.Println(v)

}

func Get(value int, err *error) Item {
    if value > (len(items) - 1) {
        *err = errors.New("error")
        return Item{}
    } else {
        return items[value]
    }
}
Jaehoon
quelle