Golang возвращает несколько значений

Мне было интересно, почему это действительно код go:

func FindUserInfo(id string) (Info, bool) {
    it, present := all[id]
    return it, present
}

но это не

func FindUserInfo(id string) (Info, bool) {
    return all[id]
}

Есть ли способ избежать временных переменных?

Ответы

Ответ 1

Вы можете сохранить пару нажатий клавиш с помощью именованных возвратов:

func FindUserInfo(id string) (i Info, ok bool) {
    i, ok = all[id]
    return
}

Но кроме того, я не думаю, что вы хотите.

Ответ 2

Чтобы подробно описать мой comment, Эффективный переход, означает, что присвоение нескольких значений от доступа к ключу карты называется шаблоном "запятая ok".

Иногда вам нужно различать недостающую запись от нулевого значения. Есть ли запись для "UTC" или это пустая строка, потому что она вообще отсутствует на карте? Вы можете различать форму множественного присваивания.

var seconds int
var ok bool
seconds, ok = timeZone[tz]

По понятным причинам это называется идиомой "запятая". В этом примере, если tz присутствует, секунды будут установлены соответствующим образом, и ok будет истинным; если нет, секунды будут установлены на ноль и ok будет ложным.

Игровая площадка, демонстрирующая это

Мы видим, что это отличается от вызова регулярной функции, где компилятор скажет вам, что что-то не так:

package main

import "fmt"

func multiValueReturn() (int, int) {
    return 0, 0
}

func main() {
    fmt.Println(multiValueReturn)

    asgn1, _ := multiValueReturn()

    asgn2 := multiValueReturn()
}

На игровая площадка будет выводиться

# command-line-arguments
/tmp/sandbox592492597/main.go:14: multiple-value multiValueReturn() in single-value context

Это дает нам подсказку, что это может быть что-то, что делает компилятор. Поиск исходного кода для "commaOk" дает нам несколько мест для поиска, в том числе types.unpack

Во время написания этого этого метода godoc читает:

// unpack takes a getter get and a number of operands n. If n == 1, unpack
// calls the incoming getter for the first operand. If that operand is
// invalid, unpack returns (nil, 0, false). Otherwise, if that operand is a
// function call, or a comma-ok expression and allowCommaOk is set, the result
// is a new getter and operand count providing access to the function results,
// or comma-ok values, respectively. The third result value reports if it
// is indeed the comma-ok case. In all other cases, the incoming getter and
// operand count are returned unchanged, and the third result value is false.
//
// In other words, if there exactly one operand that - after type-checking
// by calling get - stands for multiple operands, the resulting getter provides
// access to those operands instead.
//
// If the returned getter is called at most once for a given operand index i
// (including i == 0), that operand is guaranteed to cause only one call of
// the incoming getter with that i.
//

Ключевыми битами этого является то, что этот метод, по-видимому, определяет, действительно ли что-то действительно является "запомненным".

Копаем в этот метод говорит нам, что он проверит, будет ли режим операндов индексировать карту или если для режима установлено значение commaok (где этот , дает нам много советов о том, когда он использовался, но при поиске источника для присвоений commaok мы можем видеть, что он используется, когда получение значения из канала и тип утверждений). Запомните смелый бит позже!

if x0.mode == mapindex || x0.mode == commaok {
    // comma-ok value
    if allowCommaOk {
        a := [2]Type{x0.typ, Typ[UntypedBool]}
        return func(x *operand, i int) {
            x.mode = value
            x.expr = x0.expr
            x.typ = a[i]
        }, 2, true
    }
    x0.mode = value
}

allowCommaOk является параметром функции. Выяснив, где unpack вызывается в этом файле, мы видим, что все вызывающие абоненты передают false в качестве аргумента. Поиск остальной части репозитория приводит нас к assignments.go в Checker.initVars() method.

l := len(lhs)
get, r, commaOk := unpack(func(x *operand, i int) { check.expr(x, rhs[i]) }, len(rhs), l == 2 && !returnPos.IsValid())

Так как кажется, что мы можем использовать шаблон "comma ok" ​​для получения двух возвращаемых значений при выполнении многозначного присвоения, это кажется правильным местом для поиска! В приведенном выше коде проверяется длина левой стороны, а когда unpack вызывается, параметр allowCommaOk является результатом l == 2 && !returnPos.IsValid(). !returnPos.IsValid() здесь несколько сбивает с толку, поскольку это означает, что позиция не связана с файловой или строковой информацией, но мы просто игнорируйте это.

Далее в этом методе мы имеем:

var x operand
if commaOk {
    var a [2]Type
    for i := range a {
        get(&x, i)
        a[i] = check.initVar(lhs[i], &x, returnPos.IsValid())
    }
    check.recordCommaOkTypes(rhs[0], a)
    return
}

Итак, что все это говорит нам?

  • Так как метод unpack принимает параметр allowCommaOk, который жестко закодирован до false везде, кроме метода assignment.go Checker.initVars(), мы, вероятно, предположим, что вы будете получать только два значения при выполнении задания и иметь две переменные с левой стороны.
  • Метод unpack будет определять, действительно ли вы получаете значение ok в результате, проверяя, индексируете ли вы фрагмент, захватываете значение из канала или выполняете утверждение типа
  • Поскольку вы можете получить только значение ok при выполнении задания, это похоже на ваш конкретный случай, вам всегда понадобятся переменные

Ответ 3

Проще говоря: причина, почему ваш второй пример недействителен. Код Go - это потому, что спецификация языка так говорит.;)

Индексирование карты дает только вторичное значение при присваивании двум переменным. Оператор возврата не является присваиванием.

Индексное выражение на карте a типа map [K] V, используемое при присваивании или инициализации специального вида

v, ok = a [x]
v, ok: = a [x]
var v, ok = a [x]

дает дополнительное нетипизированное логическое значение. Значение ok является истинным, если ключ x присутствует на карте, а false - в противном случае.

Кроме того, индексирование карты не является " единственным вызовом многозначной функции", что является одним из трех способов возврата значения из функции (вторая, две другие не имеют значения здесь):

Существует три способа вернуть значения из функции с типом результата:

  • Возвращаемое значение или значения могут быть явно указаны в операторе return. Каждое выражение должно быть однозначным и присваиваться соответствующему элементу типа результата функции.

  • Список выражений в выражении "return" может быть одним вызовом многозначной функции. Эффект выглядит так, как если бы каждое значение, возвращаемое из этой функции, было назначено временной переменной с типом соответствующего значения, за которым следует оператор "return", в котором перечислены эти переменные, после чего применяются правила предыдущего случая.

  • Список выражений может быть пустым, если тип результата функции указывает имена для его параметров результата. Параметры результата действуют как обычные локальные переменные, и функция может присваивать им значения по мере необходимости. Оператор return возвращает значения этих переменных.

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

Итак, почему спецификация языка не допускает такого рода специальное использование индексирования карт (или утверждение типа или прием канала, оба из которых также могут использовать идиому "запятая" ) в операторах возврата? Это хороший вопрос. Мое предположение: просто сохранить спецификацию языка.

Ответ 4

Я не эксперт, но считаю, что вы получаете ошибку времени компиляции, когда пытаетесь вернуть массив i.e. return all[id]. Причина может заключаться в том, что тип возвращаемых функций специально упоминается как (Info, bool), а когда вы делаете return all[id], он не может отобразить тип возврата all[id] в (Info, bool).

Однако упомянутое выше решение, возвращаемые переменные i и ok являются теми же, что упоминаются в возвращаемом типе функции (i Info, ok bool), и, следовательно, компилятор знает, что он возвращает, а не просто делает (i Info, ok bool).

Ответ 5

По умолчанию карты в golang возвращают одно значение при доступе к ключу

https://blog.golang.org/go-maps-in-action

Следовательно, return all[id] не будет компилироваться для функции, которая ожидает 2 возвращаемых значения.