Да, есть несколько вещей, которые нужно учитывать. Сначала: давайте начнем с очевидной синтаксической ошибки в вашем примере указателя:
type Employee struct {
FirstName *string `json:"name"`
Salary *int `json:"salary"`
FullTime *bool `json:"fullTime"`
}
Итак, я переместил звездочку в тип и заполнил поля. Пакет encoding/json
использует отражение для установки значений полей, поэтому их необходимо экспортировать.
Поскольку вы используете теги json, давайте начнем с простых вещей:
type Foo struct {
Bar string `json:"bar"`
Foo *string `json:"foo,omitempty"`
}
Когда я отменяю сообщение, которое не имеет значения bar
, поле Bar
будет просто пустой строкой. Из-за этого сложно понять, было ли поле отправлено или нет. Особенно при работе с целыми числами: как я могу отличить поле, которое не было отправлено, от поля, которое было отправлено со значением 0?
Использование поля указателя и указание omitempty
позволяет вам это сделать , Если поле не было указано в данных JSON, то поле в вашей структуре будет nil
, если нет: оно будет указывать на целое число со значением 0.
Конечно, имея проверка на наличие нулевых указателей может быть утомительной, это делает код более подверженным ошибкам, и поэтому вам нужно делать это только в том случае, если есть реальная причина, по которой вы хотите провести различие между полем, которое не устанавливается, и нулевым значением.
подводные камни
Указатели позволяют изменять значения того, на что они указывают
Перейдем к рискам, которые по своей природе связаны с ними. Предполагая, что ваша Employee
структура с полями указателя, и тип с именем EmployeeV
, который такой же, но с полями значений, рассмотрим следующие функции:
func (e Employee) SetName(name string) {
if e.Firstname == nil {
e.Firstname = &name
return
}
*e.Firstname = name
}
Теперь эта функция будет работать только на половине время. Вы звоните SetName
на получателя значения. Если Firstname
равно nil, тогда вы установите указатель на копию вашей исходной переменной, и ваша переменная не будет отражать изменения, внесенные вами в функцию. Однако, если Firstname
было установлено , копия будет указывать на ту же строку, что и исходная переменная, а значение, на которое указывает указатель на , будет обновляться. Это плохо.
Реализуйте ту же функцию на EmployeeV
, однако:
func (e EmployeeV) SetName(name string) {
e.Firstname = name
}
И она просто не будет когда-либо работать. Вы всегда будете обновлять копию, и эти изменения не затронут переменную, для которой вы вызываете функцию SetName
. По этой причине идиоматический c способ, в go, сделать что-то вроде этого будет:
type Employee struct {
Firstname string
// other fields
}
func (e *Employee) SetName(name string) {
e.Firstname = name
}
Итак, мы меняем метод для использования указателя receiveer.
Data races
Как всегда: если вы используете указатели, вы, по сути, позволяете коду манипулировать памятью, на которую указывает что-то напрямую. Учитывая, что golang является языком, который, как известно, облегчает параллелизм, и доступ к тому же биту памяти означает, что вы рискуете создать гонки данных:
func main() {
n := "name"
e := Employee{
Firstname: &n,
}
go func() {
*e.Firstname = "foo"
}()
race(e)
}
func race(e Employee) {
go race(e)
go func() {
*e.Firstname = "in routine"
}()
*e.Firstname = fmt.Sprintf("%d", time.Now().UnixNano())
}
Доступ к этому полю Firstname
во множестве разных процедур. Какова будет его возможная ценность? Ты вообще знаешь? Детектор расы golang, скорее всего, пометит этот код как потенциальную гонку данных.
С точки зрения использования памяти: отдельные поля, такие как целые или тип bools, на самом деле не должны беспокоить около. Если вы передаете размерную структуру и знаете, что она безопасна, то, вероятно, будет хорошей идеей передать указатель на указанную структуру. С другой стороны, доступ к значениям через указатель, а не прямой доступ к ним, не является бесплатным: косвенное добавление добавляет небольшие накладные расходы.