Múltiples valores en contexto de un solo valor

Resuelto Spearfisher asked hace 9 años • 6 respuestas

Debido al manejo de errores en Go, a menudo termino con funciones de valores múltiples. Hasta ahora, la forma en que he manejado esto ha sido muy complicada y estoy buscando 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 de item.Valueforma elegante? Antes de introducir el manejo de errores, mi función acababa de regresar itemy simplemente podía hacer esto:

val := Get(1).Value

Ahora hago esto:

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

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

Spearfisher avatar Jan 30 '15 07:01 Spearfisher
Aceptado

En el caso de una función de retorno de valores múltiples, 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 un error, está ahí por una razón (que es que la función podría fallar) y no debe omitirlo porque si lo hace, su código posterior también podría fallar estrepitosamente (por ejemplo, provocando un pánico en tiempo de ejecución).

Sin embargo, puede haber situaciones en las que sepa que el código no fallará bajo ninguna circunstancia. En estos casos, puede proporcionar una función (o método) auxiliar que descartará error(o provocará 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.
Excelentes ejemplos de esto son los paquetes templateand regexp: si proporciona una plantilla o expresión regular válida en el momento de la compilación, puede estar seguro de que siempre se podrán analizar sin errores en el 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 devuelven errors porque su uso previsto es donde se garantiza que la entrada será 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]+\]$`)

Volver a tu caso

SI puede estar seguro de Get()que no producirá errorciertos valores de entrada, puede crear una Must()función auxiliar que no devolverá errorpero generará 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 debes utilizar esto en todos los casos, sólo cuando estés seguro de que tiene éxito. Uso:

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

Actualización de genéricos de Go 1.18: Go 1.18 agrega soporte para genéricos, ahora es posible escribir una Must()función genérica:

func Must[T any](v T, err error) T {
    if err != nil {
        panic(err)
    }
    return v
}

Esto está disponible en github.com/icza/gog, como gog.Must()(divulgación: soy el autor).

Alternativa / Simplificación

Incluso puedes simplificarlo aún más si incorporas la Get()llamada a tu 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 pasar múltiples valores de retorno a una función variada?

Devolver mapa como 'ok' en Golang en funciones normales

icza avatar Jan 30 '2015 09:01 icza

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 en un segmento/matriz y luego el tipo para obtener el valor real.

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

Kesarion avatar Dec 21 '2016 15:12 Kesarion

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

Existen técnicas que puede emplear para posponer 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 se puede crear un errWritertipo que posponga el manejo de errores hasta que termine de llamar write.

jmaloney avatar Jan 30 '2015 00:01 jmaloney