Каков идиоматический способ вернуть структуру или ошибку?

У меня есть функция, которая возвращает либо Card, тип struct, либо ошибку.

Проблема в том, как я могу вернуться из функции при возникновении ошибки? nil недействителен для структур, и у меня нет допустимого нулевого значения для моего типа Card.

func canFail() (card Card, err error) {
    // return nil, errors.New("Not yet implemented"); // Fails
    return Card{Ace, Spades}, errors.New("not yet implemented"); // Works, but very ugly
}

Единственный обходной путь, который я нашел, - это использовать *Card вместо Card, сделать его либо nil, когда есть ошибка, либо указать фактический Card, когда ошибки не происходит, но это довольно неуклюже.

func canFail() (card *Card, err error) {
    return nil, errors.New("not yet implemented");
}

Есть ли способ лучше ?

РЕДАКТИРОВАТЬ: я нашел другой способ, но не знаю, является ли это идиоматичным или даже хорошим стилем.

func canFail() (card Card, err error) {
    return card, errors.New("not yet implemented")
}

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


person Fabien    schedule 11.03.2013    source источник


Ответы (6)


func canFail() (card Card, err error) {
    return card, errors.New("not yet implemented")
}

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

person Sonia    schedule 11.03.2013
comment
Довольно идиоматично возвращать указатель, чтобы можно было использовать nil. Это помогает избежать ошибок и упрощает ранний возврат, когда вы не используете именованные возвращаемые значения. - person Jeremy Wall; 12.03.2013
comment
Хотя, безусловно, удобно иметь возможность возвращать nil вместо указателя, я бы не стал использовать указатели только по этой причине. Тот факт, что была возвращена ненулевая ошибка, достаточен, чтобы сказать вам не использовать данные в «карте». Вы должны проверять ошибку, а не значение. В противном случае нет смысла вообще возвращать ошибку... - person weberc2; 12.03.2013

Например,

type Card struct {
}

func canFail() (card Card, err error) {
    return Card{}, errors.New("not yet implemented")
}
person peterSO    schedule 11.03.2013
comment
В этом примере возвращаемые значения даже не должны быть названы. (Возможно, вы по-прежнему хотите, чтобы они были названы по другим причинам, или вам может показаться, что функция более понятна без беспорядка имен.) - person Sonia; 11.03.2013
comment
Прочитайте вопрос и мой ответ. Это функция-заглушка, которая будет реализована позже — пустое сообщение struct и "not yet implemented". Поскольку мы понятия не имеем, какой код будет использоваться для реализации функции, единственный безопасный способ — возвращать четко определенные значения, какой бы код ни использовался позже. - person peterSO; 11.03.2013

Лично я предпочитаю ваш второй вариант.

func canFail() (card *Card, err error) {
    return nil, errors.New("not yet implemented");
}

Таким образом, вы можете быть уверены, что при возникновении ошибок вызывающие canFail() не смогут использовать card, поскольку он равен нулю. Мы не можем быть уверены, что вызывающие абоненты сначала проверят ошибку.

person hutabalian    schedule 01.02.2019

ответ peterSO самый близкий, но это не совсем то, что я бы использовал. Я думаю, что это лучше всего:

func canFail() (Card, error) {
   return Card{}, errors.New("not yet implemented")
}

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

func canFail() (card Card, err error) {
   return
}

и это проблематично по двум причинам. Во-первых, вы не всегда будете в ситуации, когда вы можете просто иметь возвращаемое значение, какое бы эта переменная ни была в данный момент. Во-вторых, если у вас есть более крупная функция, вы не сможете использовать голый возврат на более глубоких уровнях, так как вы получите переменные теневые ошибки. Наконец, использование Card{} вместо nil или card более подробно, но лучше передает то, что вы делаете. Если вы используете любой из них:

return
return card, err

Без контекста неясно, была ли функция успешной или нет, а это:

return Card{}, err

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

return false, err
return 0, err
return '\x00', err
return "", err
return []byte{}, err

https://github.com/golang/go/wiki/CodeReviewComments#pass-values

person Steven Penny    schedule 10.02.2021

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

func canFail(card *Card) (err error) {
    if someCondition {
        // set one property
        card.Suit = Diamond

        // set all at once
        *card = Card{Ace, Spade}
    } else {
        err = errors.New("something went wrong")
    }

    return
}

Если вам неудобно делать вид, что Go поддерживает ссылки на стиль C++, вы также должны отметить card как nil.

https://play.golang.org/p/o-2TYwWCTL

person Billy    schedule 03.02.2017

person    schedule
comment
Спасибо, так что это семантически близко к моему последнему решению, хотя и немного отличается синтаксис - person Fabien; 11.03.2013