Closed1

[Go] Pointer

yagi_engyagi_eng

目的

  • 扱いを定めて記法を統一する

方針

原則的に値を用いる。
ただし、以下の場合にはポインタを用いる。

  • ライブラリにてポインタで定義されている型
  • コンストラクタの返り値
  • レシーバ
  • 数字型で存在しないこと(NULL)を表現したい場合
  • structのembedding, 構造体をプロパティとして持たせる場合
  • 関数内で引数で受け取ったarray, slice, structを変更したい場合
    • 副作用が発生するので基本的には使わない
  • mapに持たせる場合の構造体
  • Request Parameterに対応する構造体の数値型のうち一部(後に詳細)

性能

以下の記事によると、値の方がパフォーマンス良いとのこと。
いくつかの記事を漁ってみたが、だいだい似たような検証結果が記載されていた。

Goにおけるポインタの使いどころ

しかし、引数、返り値、レシーバ、埋め込みなど様々なユースケースがあるので、一概に値の方がパフォーマンスが良いと断言はできないと思われる。

各方針の参考・例

コンストラクタの返り値

ポインタで返すのが一般的。

Effective Go - Constructors and composite literals

レシーバ

ポインタを使うのが一般的。

CodeReviewComments - Receiver Type

Choosing whether to use a value or pointer receiver on methods can be difficult, especially to new Go programmers. If in doubt, use a pointer

数字型で存在しないこと(NULL)を表現したい場合

例えば、RepositoryDTO/Entityのうち、プロパティに対応するDBのフィールドがNULL許容の場合など。

CREATE TABLE `hoge` (
  `id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT 'ID',
  `user_id` bigint unsigned DEFAULT NULL COMMENT 'ユーザーID',
  PRIMARY KEY (`id`)
)
type Hoge struct {
    ID         uint
    UserID *uint
}

ポインタにしない場合

ゼロ値で初期化される。
上記の場合で値にすると、UserIDは0で初期化され、本当に0の場合と区別がつかない。

ゼロ値を使おう #golang

ポインタにする対象の型

数字型(int, uint)のみとする。
極力ポインタを扱いたくなく、数字型以外の型でNULLを扱いたいシーンが想定されないためである。

仮に、NULL許容なstring型のフィールドがあったとしても、それはアプリ上では空文字""と表現しても差し支えない。
NULL許容なboolean型は想定されづらい。

sql.NullXXXは使わない

実際に使っていないが、取り回しが悪いように思われる。

SQLのNULL値に対応する(JSON定義の変更まで)【Go】

structのembedding, 構造体をプロパティとして持たせる場合

ポインタを埋め込むのが一般的。

Effective Go - Embedding

The embedded elements are pointers to structs and of course must be initialized to point to valid structs before they can be used.

ポインタにしない場合

埋め込んだ構造体がゼロ値で初期化されてしまうため、埋め込んだ構造体が数字型のプロパティを持っていると、前述のように本当に0かどうか判別できなくなってしまう。

関数内で引数で受け取ったarray, slice, map, structを変更したい場合

副作用が発生するので基本的には使わない。
やむを得えない場合にのみポインタ渡しとする。

mapに持たせる場合の構造体

mapに持たせる場合の構造体はポインタを使う。
Go言語 構造体のmapはポインタを使おう

Request Parameterに対応する構造体の数値型のうち一部

requiredタグが付与された数値型のうち、0が代入される可能性がある場合、ポインタを使用する。

値の場合、0が代入されるとvalidation errorとなる。

validator

This validates that the value is not the data types default zero value. For numbers ensures value is not zero.

Error when put 0 for required int field

you should use a pointer to an integer instead of an integer itself.

参考

ポインタにしない場合に関する整理

引数

上述の通り、基本的に引数は値渡しとする。

上記のその他に掲載しているリンク先では、コピーのコストを考えて、大きい構造体を渡す場合にはポインタで渡すことを提唱している。
しかし、可読性と統一性を考慮して、値渡しで統一する。

ただし、シビアな性能要件が発生した場合には別途検討する。

array, slice

array, sliceの要素は値で持つ

ポインタで持つとメモリ効率が悪いため、値で持つ。

Bad Go: slices of pointers

The no-pointer version allocates less memory, performs fewer allocations and is over an order of magnitude faster.

sliceの宣言

capacityを宣言しない

appendを前提とする場合

varで宣言すると初期値はnilとなる。
宣言時に配列がアロケートされるのを防ぐ。

var a []int
a = append(a, 1)

宣言段階で空のスライスが生成されることが望ましい場合

Repositoryでスライスを返す関数などが想定される。

a := []int{}
a = append(a, 1)

map

mapはポインタなので、そのまま扱う。
# 正確にはmap構造体へのポインタ

Go言語でハマったことメモ(slice・map・string)

このスクラップは2022/02/26にクローズされました