Несколько значений в однозначном контексте

110

Из-за обработки ошибок в Go я часто получаю функции с несколькими значениями. До сих пор я с этим справлялся очень запутанно, и я ищу лучшие практики для написания более чистого кода.

Скажем, у меня есть следующая функция:

type Item struct {
   Value int
   Name string
}

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

  return item, nil
}

Как мне item.Valueэлегантно присвоить новую переменную . Перед тем, как представить обработку ошибок, моя функция только что вернулась, itemи я мог просто сделать это:

val := Get(1).Value

Теперь делаю так:

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

Разве нет возможности напрямую получить доступ к первой возвращаемой переменной?

Подводный охотник
источник
3
itemобычно будет nilв случае ошибки. Без предварительной проверки ошибки ваш код в этом случае выйдет из строя.
Thomas

Ответы:

83

В случае функции возврата с несколькими значениями вы не можете ссылаться на поля или методы определенного значения результата при вызове функции.

И если один из них является error, он существует по какой-то причине (функция может выйти из строя), и вы не должны обходить его, потому что, если вы это сделаете, ваш последующий код также может потерпеть неудачу (например, привести к панике во время выполнения).

Однако могут быть ситуации, когда вы знаете, что код не выйдет из строя ни при каких обстоятельствах. В этих случаях вы можете предоставить вспомогательную функцию (или метод), которая отменит error(или вызовет панику во время выполнения, если она все еще возникает).
Это может быть так, если вы предоставляете входные значения для функции из кода и знаете, что они работают.
Великие примеры этого являются templateи regexpпакеты: если вы предоставить действительный шаблон или регулярное выражение во время компиляции, вы можете быть уверены , что они всегда могут быть разобраны без ошибок во время выполнения. По этой причине templateпакет предоставляет Must(t *Template, err error) *Templateфункцию, а regexpпакет предоставляет MustCompile(str string) *Regexpфункцию: они не возвращаютerrors, потому что их предполагаемое использование - это то, где гарантированно будет действительный ввод.

Примеры:

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

Вернемся к твоему делу

ЕСЛИ вы можете быть уверены, Get()что не будет производить errorдля определенных входных значений, вы можете создать вспомогательную Must()функцию, которая не будет возвращать, errorно вызывает панику во время выполнения, если она все еще возникает:

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

Но вы не должны использовать это во всех случаях, только когда вы уверены, что это удастся. Использование:

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

Альтернатива / упрощение

Вы можете даже упростить его, если включите Get()вызов в свою вспомогательную функцию, назовем ее MustGet:

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

Использование:

val := MustGet(1).Value

См. Некоторые интересные / связанные вопросы:

как разобрать несколько возвратов в голанге

Вернуть карту как 'ok' в Golang для обычных функций

icza
источник
7

Нет, но это хорошо, так как вы всегда должны обрабатывать свои ошибки.

Есть методы, которые можно использовать для отсрочки обработки ошибок, см. Роб Пайк , « Ошибки - это значения ».

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
}

В этом примере из сообщения в блоге он показывает, как можно создать errWriterтип, который откладывает обработку ошибок до завершения вызова write.

Jmaloney
источник
6

Да, есть.

Удивительно, да? Вы можете получить конкретное значение из множественного возврата, используя простую 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-

Обратите внимание, как вы выбираете номер значения, как если бы вы выбирали из среза / массива, а затем тип, чтобы получить фактическое значение.

Вы можете узнать больше о науке, стоящей за этим, из этой статьи . Кредиты автору.

Кесарион
источник
5

Нет, вы не можете напрямую получить доступ к первому значению.

Я полагаю, что взломом для этого было бы возвращение массива значений вместо «item» и «err», а затем просто так, item, _ := Get(1)[0] но я бы не рекомендовал этого.

Сурьма
источник
3

Как насчет этого?

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]
    }
}
Джэхун
источник