Кілька значень у однозначному контексті


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у випадку помилки. Якщо спочатку не перевірити помилку, ваш код в цьому випадку вийде з ладу.
Томас

Відповіді:


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

Дивіться кілька цікавих / пов’язаних питань:

як розібрати кілька повернень у голангу

Поверніть карту на зразок "добре" в Golang на звичайних функціях


7

Ні, але це гарна річ, оскільки ви завжди повинні поводитися зі своїми помилками.

Існують методи, які можна використовувати для відстрочки обробки помилок, див. Помилки - це значення 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
}

У цьому прикладі з повідомлення в блозі він пояснює, як ви могли створити errWriterтип, який відкладає обробку помилок, поки ви не закінчите дзвінок write.


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]
    }
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.