2015-05-08 3 views
15

мне было интересно, почему это правильный код идти:golang возвращать несколько значений Issue

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

, но это не

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

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

+2

Я не являюсь экспертом в том, как Go работает в бэкэнд, но шаблон «comma ok» ​​применяется к встроенным операциям (а не «реальным» функциям). Поскольку это необязательно, похоже, что компилятор проверяет многозначное * присваивание *. Может быть, кто-то [более знакомый с компилятором] (https://github.com/golang/go/blob/254964074f34dc1cb39693f3e23a95938092044f/src/go/types/call.go#L118-L134) может разработать – Lander

+0

, не могли бы вы рассказать о " запятая ok "? ответ был бы оценен (и одобрен), так как я не только пытаюсь сохранить нажатие клавиш, но и понимаю, почему ** это происходит. –

+1

У меня есть сильное подозрение, почему это не разрешено, но мне нужно будет обратиться к спецификации. Я думаю, что для этого есть разумная логическая причина. Довольно уверен, что если бы вы разрешили это, я мог бы придумать некоторые крайние случаи, которые нарушили бы компилятор или имели неопределенное/непоследовательное поведение. – evanmcdonnal

ответ

14

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

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

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

2

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

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

+0

'return all [id]' не возвращает массив, а элемент в ** карте **. если тип возвращаемого значения - это просто элемент, который в порядке, но на самом деле он возвращает 2 значения, поэтому вы можете сделать 'a, b: = foo [id]' –

1

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

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

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

11

Чтобы подробно рассказать о моем comment, Effective Go упоминает, что назначение нескольких значений при доступе к ключу карты называется шаблоном «запятая».

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

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

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

Playground demonstrating this

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

package main 

import "fmt" 

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

func main() { 
    fmt.Println(multiValueReturn) 

    asgn1, _ := multiValueReturn() 

    asgn2 := multiValueReturn() 
} 

На playground это будет выводить

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

Это дает нам подсказку, что это может быть что-то компилируемое er делает.Searching the source code для «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's 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 (где это is defined дает нам много намеков о том, когда он используется, но поиск источника для присвоений commaok мы видим, что он используется, когда getting a value from a channel и type assertions). Помните смелый бит позже!

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()) 

Так как кажется, что мы можем использовать только «запятая ОК» образец, чтобы получить два возвращаемых значений при выполнении задания на многозначное это походит на правильное место, чтобы посмотреть! В приведенном выше коде проверяется длина левой стороны, а когда unpack называется параметром allowCommaOk, это результат l == 2 && !returnPos.IsValid(). !returnPos.IsValid() здесь несколько сбивает с толку, поскольку это будет означать, что позиция has no file or line information associated с ним, но мы просто проигнорируем это.

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

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 параметра, который жёстко ложь везде кроме в Checker.initVars() методы assignment.go «s, вероятно, мы можем предположить, что вы будете только когда-либо получить два значение при выполнении задания и две переменных слева.
  • Метод unpack будет определить, действительно ли вы на самом деле сделать получить ok значение в ответ, проверяя, если вы индексировать кусочек, захватывая значение из канала, или делает утверждение типа
  • Так как вы можете только получить значение ok при выполнении задания он выглядит в вашем конкретном случае, вы всегда должны использовать переменные
6

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

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

Выражение Индекс на карте в типа карты [K] V используется в назначении или инициализации специальной формы

В, ок = а [х]
v, ок: = а [x]
var v, ok = a [x]

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

Кроме того, индексация карты не является «single call to a multi-valued function», который является одним из трех способов возврата значения из функции (второй один, а два других не имеющие актуальное значение здесь):

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

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

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

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

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

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

Смежные вопросы