Idiomatische Methode zur Konvertierung / Typzusicherung für mehrere Rückgabewerte in Go

75

Was ist die idiomatische Methode, um mehrere Rückgabewerte in Go umzuwandeln?

Können Sie dies in einer einzelnen Zeile tun oder müssen Sie temporäre Variablen verwenden, wie ich es in meinem Beispiel unten getan habe?

package main

import "fmt"

func oneRet() interface{} {
    return "Hello"
}

func twoRet() (interface{}, error) {
    return "Hejsan", nil
}

func main() {
    // With one return value, you can simply do this
    str1 := oneRet().(string)
    fmt.Println("String 1: " + str1)

    // It is not as easy with two return values
    //str2, err := twoRet().(string) // Not possible
    // Do I really have to use a temp variable instead?
    temp, err := twoRet()
    str2 := temp.(string)
    fmt.Println("String 2: " + str2 )


    if err != nil {
        panic("unreachable")
    }   
}

Wird es übrigens genannt, castingwenn es um Schnittstellen geht?

i := interface.(int)
ANisus
quelle

Antworten:

68

Sie können es nicht in einer einzigen Zeile tun. Ihr temporärer variabler Ansatz ist der richtige Weg.

Wird es übrigens Casting genannt, wenn es um Schnittstellen geht?

Es wird eigentlich eine Typzusicherung genannt . Eine Art gegossene Umwandlung unterscheidet:

var a int
var b int64

a = 5
b = int64(a)
jimt
quelle
12
Eigentlich nennt man das auch keine Besetzung. Es wird eine Konvertierung genannt. golang.org/ref/spec#Conversions
Stephen Weinberg
35
func silly() (interface{}, error) {
    return "silly", nil
}

v, err := silly()
if err != nil {
    // handle error
}

s, ok := v.(string)
if !ok {
    // the assertion failed.
}

Wahrscheinlicher ist jedoch, dass Sie einen Typschalter verwenden möchten, wie z.

switch t := v.(type) {
case string:
    // t is a string
case int :
    // t is an int
default:
    // t is some other type that we didn't name.
}

Bei Go geht es mehr um Korrektheit als um Knappheit.

Jorelli
quelle
Trotzdem denke ich, dass es Go gelingt, auch ziemlich knapp zu sein, ohne die Korrektheit aufzugeben. Für andere, die diese Frage sehen, finde ich es gut, dass Sie den Assertion Check erwähnt haben. In meinem Fall, obwohl ich bereits weiß, welcher Typ in der Schnittstelle gespeichert ist, habe ich die Prüfung übersprungen. Ich habe sogar darüber nachgedacht, ein unsafe.Pointeranstelle von zu verwenden interface{}, aber ich vermutete, dass die Typzusicherung nicht genügend Overhead enthält, was die Verwendung unsicherer Zeiger rechtfertigt.
ANisus
1
Ich sollte wahrscheinlich darauf hinweisen, dass der idiomatische Weg, dies zu tun, darin besteht, die Schnittstelle {} nicht zurückzugeben, sondern die Schnittstelle {} als Eingabeparameter zu akzeptieren, den übergebenen Typ zu überprüfen und diesen Typ dann zu füllen. Dies wird in der Unmarshal-Methode des encoding / json-Pakets sehr gut veranschaulicht. Diese Techniken decken nicht alle Situationen ab, daher ist dies möglicherweise nicht immer möglich, aber es ist im Allgemeinen besser, die Schnittstelle {} zu akzeptieren, als die Schnittstelle {} zurückzugeben. Mit dem Reflect-Paket können Sie überprüfen, ob der angegebene Wert ein Zeiger ist.
Jorelli
13

Oder einfach nur in einem, wenn:

if v, ok := value.(migrater); ok {
    v.migrate()
}

Go kümmert sich um die Umwandlung in der if-Klausel und lässt Sie auf die Eigenschaften des umgesetzten Typs zugreifen.

Tarion
quelle
12

template.Must ist der Ansatz der Standardbibliothek, bei dem nur der erste Rückgabewert in einer Anweisung zurückgegeben wird. Könnte für Ihren Fall ähnlich gemacht werden:

func must(v interface{}, err error) interface{} {
    if err != nil {
        panic(err)
    }
    return v
}

// Usage:
str2 := must(twoRet()).(string)

Wenn mustSie verwenden, sagen Sie im Grunde, dass es niemals einen Fehler geben sollte, und wenn dies der Fall ist, kann (oder sollte) das Programm nicht weiterarbeiten und gerät stattdessen in Panik.

Moshe Revah
quelle
6
Es ist unidiomatisch, bei Fehlern in Panik zu geraten, die nicht von einem Programmierer verursacht wurden. Eine Funktion wie template.Must()wird normalerweise zum Laden von Vorlagen aus einer Variablen in Deklarationen und init()Funktionen auf Paketebene verwendet. Die Idee ist, sofort zur Laufzeit in Panik zu geraten und deutlich zu machen, dass etwas, das der Compiler nicht abfangen konnte, falsch ist. Sie sollten dies nicht verwenden, um temporäre Variablen zu vermeiden.
Stephen Weinberg
1
Und welcher Teil von "Wenn mustSie verwenden, sagen Sie im Grunde, dass es niemals einen Fehler geben sollte" war nicht klar? Sie sollten dies nicht verwenden, es sei denn, der Fehler kann nur von einem Programmierer verursacht werden oder das Programm kann wirklich nicht fortgesetzt werden, wenn ein Fehler vorliegt.
Moshe Revah
1
Ich bin nicht einverstanden mit "oder das Programm kann wirklich nicht fortgesetzt werden, wenn ein Fehler vorliegt". In diesem Fall sollten Sie immer noch nicht in Panik geraten.
Stephen Weinberg
Es ist nicht einmal ein Argument, da ich diesem größtenteils zustimme. Es ist nur idiomatisch, wenn Sie den Fehler nicht protokollieren können oder möchten. Es ist also selten idiomatisch.
Moshe Revah
1
Obwohl ich Stephens Argument sehe, ist die Antwort in einigen Fällen immer noch nützlich (wie Stephen auch betonte). Danke für den Wrapper-Gedanken.
ANisus