Più valori in un contesto a valore singolo


110

A causa della gestione degli errori in Go, spesso mi ritrovo con più funzioni di valori. Finora, il modo in cui l'ho gestito è stato molto disordinato e sto cercando le migliori pratiche per scrivere codice più pulito.

Diciamo che ho la seguente funzione:

type Item struct {
   Value int
   Name string
}

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

  return item, nil
}

Come posso assegnare una nuova variabile a item.Valueelegantemente. Prima di introdurre la gestione degli errori, la mia funzione è appena tornata iteme potrei semplicemente fare questo:

val := Get(1).Value

Adesso lo faccio:

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

Non c'è un modo per accedere direttamente alla prima variabile restituita?


3
itemsarà tipicamente nilin caso di errore. Senza prima verificare la presenza di un errore, il codice andrà in crash in quel caso.
Thomas,

Risposte:


83

In caso di una funzione di ritorno multivalore non è possibile fare riferimento a campi o metodi di un valore specifico del risultato quando si chiama la funzione.

E se uno di loro è una error, è lì per un motivo (che è la funzione potrebbe fallire) e si dovrebbe non bypass, perché se lo fai, il tuo codice successivo potrebbe anche fallire miseramente (ad esempio, con conseguente tempo di esecuzione di panico).

Tuttavia, potrebbero esserci situazioni in cui sai che il codice non fallirà in nessuna circostanza. In questi casi è possibile fornire una funzione (o metodo) di supporto che scarterà il error(o solleverà un panico di runtime se si verifica ancora).
Questo può essere il caso se fornisci i valori di input per una funzione dal codice e sai che funzionano.
Grandi esempi di questo sono i pacchetti templatee regexp: se fornisci un modello valido o una regexp in fase di compilazione, puoi essere certo che possono sempre essere analizzati senza errori in fase di esecuzione. Per questo motivo il templatepacchetto fornisce la Must(t *Template, err error) *Templatefunzione e il regexppacchetto fornisce la MustCompile(str string) *Regexpfunzione: non restituisconoerrors perché il loro uso previsto è dove l'input è garantito per essere valido.

Esempi:

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

Torniamo al tuo caso

Se puoi essere certo Get()che non produrrà errorper determinati valori di input, puoi creare una Must()funzione di supporto che non restituirebbe il errorma solleverà un panico di runtime se si verifica ancora:

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

Ma non dovresti usarlo in tutti i casi, solo quando sei sicuro che abbia successo. Uso:

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

Alternativa / Semplificazione

Puoi anche semplificarlo ulteriormente se incorpori la Get()chiamata nella tua funzione di supporto, chiamiamola MustGet:

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

Uso:

val := MustGet(1).Value

Vedi alcune domande interessanti / correlate:

come analizzare più ritorni in golang

Restituisci mappa come "ok" in Golang sulle funzioni normali


7

No, ma questa è una buona cosa poiché dovresti sempre gestire i tuoi errori.

Ci sono tecniche che puoi utilizzare per differire la gestione degli errori, vedi Gli errori sono valori di 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 questo esempio tratto dal post del blog, illustra come è possibile creare un errWritertipo che rimanda la gestione degli errori finché non si è terminato di chiamare write.


6

Si C'è.

Sorprendente, eh? Puoi ottenere un valore specifico da un ritorno multiplo utilizzando una semplice mutefunzione:

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-

Nota come selezioni il numero del valore proprio come faresti da uno slice / array e quindi il tipo per ottenere il valore effettivo.

Puoi leggere di più sulla scienza dietro a questo da questo articolo . Crediti all'autore.


5

No, non puoi accedere direttamente al primo valore.

Suppongo che un trucco per questo sarebbe quello di restituire un array di valori invece di "item" e "err", e poi farlo item, _ := Get(1)[0] ma non lo consiglierei.


3

Che ne dici di questo modo?

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]
    }
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.