Múltiples valores en contexto de valor único

110

Debido al manejo de errores en Go, a menudo termino con funciones de valores múltiples. Hasta ahora, la forma en que lo he manejado ha sido muy complicada y estoy buscando las mejores prácticas para escribir código más limpio.

Digamos que tengo la siguiente función:

type Item struct {
   Value int
   Name string
}

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

  return item, nil
}

¿Cómo puedo asignar una nueva variable a item.Valueelegantemente? Antes de introducir el manejo de errores, mi función acaba de regresar itemy simplemente podría hacer esto:

val := Get(1).Value

Ahora hago esto:

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

¿No hay alguna forma de acceder directamente a la primera variable devuelta?

Pescador submarino
fuente
3
itemnormalmente será nilen caso de error. Sin comprobar primero si hay un error, su código fallará en ese caso.
Thomas

Respuestas:

83

En el caso de una función de retorno de varios valores, no puede hacer referencia a campos o métodos de un valor específico del resultado al llamar a la función.

Y si uno de ellos es una error, que está ahí por un motivo (que es la función puede fallar) y se debe no by-pass, porque si lo hace, su código subsiguiente podría también fracasar miserablemente (por ejemplo, lo que resulta en tiempo de ejecución de pánico).

Sin embargo, puede haber situaciones en las que sepa que el código no fallará en ninguna circunstancia. En estos casos, puede proporcionar una función auxiliar (o método) que descartará el error(o generará un pánico en tiempo de ejecución si aún ocurre).
Este puede ser el caso si proporciona los valores de entrada para una función desde el código y sabe que funcionan.
Grandes ejemplos de esto son los paquetes templatey regexp: si proporciona una plantilla válida o regexp en tiempo de compilación, puede estar seguro de que siempre se pueden analizar sin errores en tiempo de ejecución. Por esta razón, el templatepaquete proporciona la Must(t *Template, err error) *Templatefunción y el regexppaquete proporciona la MustCompile(str string) *Regexpfunción: no regresanerrors porque su uso previsto es donde se garantiza que la entrada es válida.

Ejemplos:

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

De vuelta a tu caso

SI puede estar seguro de Get()que no producirá errorpara ciertos valores de entrada, puede crear una Must()función auxiliar que no devolverá el, errorpero generará un pánico en tiempo de ejecución si aún ocurre:

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

Pero no debe usar esto en todos los casos, solo cuando esté seguro de que tiene éxito. Uso:

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

Alternativa / simplificación

Incluso puede simplificarlo aún más si incorpora la Get()llamada en su función auxiliar, llamémosla MustGet:

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

Uso:

val := MustGet(1).Value

Vea algunas preguntas interesantes / relacionadas:

cómo analizar múltiples devoluciones en golang

Devuelve el mapa como 'ok' en Golang en funciones normales

icza
fuente
7

No, pero eso es bueno, ya que siempre debes manejar tus errores.

Existen técnicas que puede emplear para diferir el manejo de errores, consulte Los errores son valores 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
}

En este ejemplo de la publicación del blog, ilustra cómo puede crear un errWritertipo que difiera el manejo de errores hasta que haya terminado de llamar write.

jmaloney
fuente
6

Sí hay.

Sorprendente, ¿eh? Puede obtener un valor específico de una devolución múltiple usando una mutefunción 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-

Observe cómo selecciona el número de valor tal como lo haría de un segmento / matriz y luego el tipo para obtener el valor real.

Puede leer más sobre la ciencia detrás de eso en este artículo . Créditos al autor.

Kesarion
fuente
5

No, no puede acceder directamente al primer valor.

Supongo que un truco para esto sería devolver una matriz de valores en lugar de "elemento" y "err", y luego simplemente hacerlo, item, _ := Get(1)[0] pero no lo recomendaría.

Antimonio
fuente
3

¿Qué tal de esta manera?

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
fuente