Plusieurs valeurs dans un contexte à valeur unique

110

En raison de la gestion des erreurs dans Go, je me retrouve souvent avec des fonctions à valeurs multiples. Jusqu'à présent, la façon dont j'ai géré cela a été très compliquée et je recherche les meilleures pratiques pour écrire un 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élégamment. Avant d'introduire la gestion des erreurs, ma fonction vient de revenir itemet 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 retournée?

Spearfisher
la source
3
itemsera typiquement nilen cas d'erreur. Sans rechercher d'abord une erreur, votre code plantera dans ce cas.
Thomas

Réponses:

83

Dans le cas d'une fonction de retour à valeurs multiples, vous ne pouvez pas faire référence à des champs ou des 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 peut échouer) et vous ne devriez pas le contourner car si vous le faites, votre code suivant pourrait également échouer lamentablement (par exemple, ce qui entraînerait une panique d'exécution).

Cependant, il peut y avoir des situations où vous savez que le code n'échouera en aucun cas. Dans ces cas, vous pouvez fournir une fonction d' assistance (ou une méthode) qui supprimera le error(ou déclenchera une panique d'exécution s'il se produit toujours).
Cela peut être le cas si vous fournissez les valeurs d'entrée pour une fonction à partir du code et que vous savez qu'elles fonctionnent.
Les packages templateet sont d'excellents exemples regexp: si vous fournissez un modèle ou une expression rationnelle valide au moment de la compilation, vous pouvez être sûr qu'ils pourront toujours être analysés sans erreur lors de l'exécution. Pour cette raison, le templatepackage fournit la Must(t *Template, err error) *Templatefonction et le regexppackage fournit la MustCompile(str string) *Regexpfonction: ils ne retournent paserrors parce que leur utilisation prévue est celle 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()cela ne produira pas errorpour certaines valeurs d'entrée, vous pouvez créer une Must()fonction d' assistance qui ne renverra pas le errormais soulèvera 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 devriez pas l'utiliser dans tous les cas, juste au moment où vous êtes sûr que cela réussit. Usage:

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

Alternative / Simplification

Vous pouvez même le simplifier davantage si vous intégrez l' Get()appel dans votre fonction d'assistance, appelons-le MustGet:

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

Usage:

val := MustGet(1).Value

Voir quelques questions intéressantes / liées:

comment analyser plusieurs retours dans golang

Retourne la carte comme 'ok' dans Golang sur les fonctions normales

icza
la source
7

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

Il existe des techniques que vous pouvez utiliser pour différer la gestion des erreurs, voir Les erreurs sont des valeurs de 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 errWritertype qui diffère la gestion des erreurs jusqu'à ce que vous ayez fini d'appeler write.

jmaloney
la source
6

Oui il y a.

Surprenant, hein? Vous pouvez obtenir une valeur spécifique à partir d'un retour multiple en utilisant une mutefonction simple :

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 numéro de valeur comme vous le feriez dans une tranche / un tableau, puis le type pour obtenir la valeur réelle.

Vous pouvez en savoir plus sur la science derrière cela dans cet article . Crédits à l'auteur.

Kesarion
la source
5

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", puis de le faire, item, _ := Get(1)[0] mais je ne le recommanderais pas.

Antimoine
la source
3

Que diriez-vous de cette façon?

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
la source