Valeurs multiples dans un contexte à valeur unique

en raison de la manipulation d'erreur dans Go, je finis souvent avec des fonctions de valeurs multiples. Jusqu'à présent, la façon dont j'ai géré cela a été très bordélique et je suis à la recherche de meilleures pratiques pour écrire du code plus propre.

disons que j'ai la fonction suivante:

type Item struct {
   Value int
   Name string
}

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

  return item, nil
}

Comment puis-je attribuer une nouvelle variable à item.Value avec élégance. Avant d'introduire la gestion des erreurs, ma fonction vient de renvoyer item et je pourrais simplement faire ceci:

val := Get(1).Value

maintenant je fais ceci:

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

N'y a-t-il pas un moyen d'accéder directement à la première variable renvoyée?

67
demandé sur Flimzy 2015-01-30 03:10:16

5 réponses

dans le cas d'une fonction de retour multi-valeur, vous ne pouvez pas vous référer aux champs ou aux méthodes d'une valeur spécifique du résultat lors de l'appel de la fonction.

et si l'un d'entre eux est un error , il est là pour une raison (qui est la fonction pourrait fail) et vous devriez Non contourner parce que si vous le faites, votre code suivant pourrait également échouer misérablement (par exemple résultant en exécution de panique).

Toutefois, il pourrait y avoir des situations où vous savoir le code ne manquera pas, dans toutes les circonstances. Dans ces cas, vous pouvez fournir une fonction helper (ou une méthode) qui rejettera le error (ou suscitera une panique d'exécution si elle se produit encore).

Cela peut être le cas si vous fournissez les valeurs d'entrée pour la fonction de code, et vous savez qu'ils fonctionnent.

Les paquets template et regexp en sont de bons exemples: si vous fournissez un modèle valide ou regexp au moment de la compilation, vous pouvez être sûr qu'ils peuvent toujours être analysés sans erreur au moment de l'exécution. Pour cette raison, le paquet template fournit la fonction Must(t *Template, err error) *Template et le paquet regexp fournit la fonction MustCompile(str string) *Regexp : ils ne retournent pas error s parce que leur utilisation prévue est là où l'entrée est garantie d'être valide.

exemples:

// "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]+\]$`)

Retour à votre cas

si vous pouvez être certain que Get() ne produira pas error pour certaines valeurs d'entrée, vous pouvez créer un helper Must() fonction qui ne retournerait pas le error mais soulèverait une panique d'exécution si elle se produit toujours:

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

mais vous ne devez pas utiliser cela dans tous les cas, juste quand vous êtes sûr qu'il réussit. Usage:

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

Alternative / Simplification

vous pouvez même le simplifier davantage si vous incorporez l'appel Get() dans votre fonction helper, appelons-le MustGet :

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

Utilisation:

val := MustGet(1).Value

voir certains questions intéressantes / connexes:

comment analyser les multiples retours en golang

carte de retour comme "ok" à Golang sur les fonctions normales

53
répondu icza 2018-10-04 21:04:34

Non, mais c'est une bonne chose car vous devriez toujours gérer vos erreurs.

il y a des techniques que vous pouvez employer pour différer la manipulation d'erreur, voir les erreurs sont des valeurs par 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
}

dans cet exemple du billet de blog, il illustre comment vous pouvez créer un type errWriter qui reporte la gestion des erreurs jusqu'à ce que vous ayez fini d'appeler write .

6
répondu jmaloney 2015-01-30 00:58:51

Non, vous ne pouvez pas accéder directement à la première valeur.

je suppose qu'un hack pour cela serait de retourner un tableau de valeurs au lieu de" item "et" err", et puis juste faire item, _ := Get(1)[0] mais je ne recommanderais pas cela.

3
répondu Antimony 2015-01-30 03:51:26

par ici?

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]
    }
}
3
répondu Jaehoon 2015-01-30 08:52:07

Oui, il y en a.

surprenant, hein? Vous pouvez obtenir une valeur spécifique à partir d'une déclaration multiple en utilisant une simple fonction mute :

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 -

remarquez comment vous sélectionnez le nombre de valeur juste comme vous le feriez à partir d'une tranche/tableau et ensuite le type pour obtenir la valeur réelle.

vous pouvez en savoir plus sur la science derrière cela de cet article . Les crédits à l'auteur.

3
répondu Kesarion 2016-12-21 15:46:57