Go言語のポインタ
2026/09/13

ポインタ型

ポインタ型*Tは、型Tの値が格納されているメモリ上の位置であるアドレスを保持する型です。例えばint型のポインタ型は*int、配列[3]intなら*[3]intと表記します。元の型Tとポインタ型*Tは別の型であり、int型の変数に*intの値を直接代入することはできません。

未初期化のポインタのゼロ値はnilです。nilは有効なアドレスを指していない状態を表し、この状態で間接参照を行うと実行時パニックが発生します。また、Go言語にはポインタに整数を加減算するようなポインタ演算(p + 1など)はありません。

var p *int      // ゼロ値はnil
fmt.Println(*p) // panic: nilポインタの間接参照

アドレス取得と間接参照

&xは変数などのアドレスを取得し、型*Tのポインタを生成します。*pは間接参照(デリファレンス)であり、ポインタpが指すアドレスの実体を表します。*pを評価すると参照先の値を取得でき、*pへ代入するとその参照先の値を直接書き換えられます。

x := 42
p := &x
fmt.Println(*p) // 42
*p = 100
fmt.Println(x)  // 100

ポインタ変数を代入するとアドレス値がコピーされ、双方が保持する参照先が同じになります。

x := 42
p := &x
q := p
*q = 7
fmt.Println(x, *p, *q) // 7 7 7

ポインタ同士の比較(==)は、同じ変数を指しているか、または両方がnilの場合にtrueになります。参照先の値が等しくても、別の変数を指していればfalseです。

a, b := 1, 1
p, q := &a, &a
r := &b
fmt.Println(p == q) // true(同じ変数a)
fmt.Println(p == r) // false(値は同じでも別の変数)

サイズが0の型(空の構造体struct{}や長さ0の配列など)では、別々の変数でも同じアドレスになることがあり、その場合の==の結果は仕様上どちらでもよいとされています。

var emptyA, emptyB struct{}
pa, pb := &emptyA, &emptyB
fmt.Println(pa == pb) // 結果は仕様で決まっていない

アドレス可能な式

&xでアドレスを取得できるのは、xがアドレス可能な式(addressable)の場合に限られます。変数、間接参照(*p)、スライスの要素、アドレス可能な構造体のフィールド、アドレス可能な配列の要素が該当します。配列・スライス・マップ・構造体の複合リテラルも例外としてアドレスを取得できます。

一方、数値や文字列などの基本リテラル、定数(const)、マップの要素、関数の戻り値などはアドレスを取得できません。

n := 10
p := &n                           // OK: 変数
q := &(*p)                        // OK: 間接参照(pは非nil。同じ変数nを指す)
nums := []int{1, 2}
r := &nums[0]                     // OK: スライスの要素
arrLit := &[3]int{1, 2, 3}        // OK: 配列の複合リテラル
sliceLit := &[]int{1, 2}          // OK: スライスの複合リテラル
mapLit := &map[string]int{"a": 1} // OK: マップの複合リテラル

// &10                             // NG: 基本リテラルはアドレスを取れない
const c = 10
// &c                              // NG: 定数はアドレスを取れない
m := map[string]int{"a": 1}
// &m["a"]                         // NG: マップの要素はアドレスを取れない
// &time.Now()                     // NG: 関数の戻り値はアドレスを取れない

// var np *int
// _ = &(*np)                      // NG: nilの間接参照はパニック

値渡しとポインタ

Go言語の関数呼び出しは常に値渡しです。引数に変数を渡すとその値のコピーが渡るため、関数内で引数を書き換えても呼び出し元の変数は変化しません。

呼び出し元の変数を直接書き換えたい場合は、ポインタを渡します。ポインタを渡す場合もアドレス値自体はコピーされますが、参照先の実体が共有されるため、間接参照(*n)を経由して呼び出し元の値を更新できます。

func setToZero(n int) { n = 0 }       // コピーされた引数nのみが0になる
func setToZeroPtr(n *int) { *n = 0 }  // *n経由で呼び出し元の変数を0にする

count := 5
setToZero(count)
fmt.Println(count) // 5

setToZeroPtr(&count)
fmt.Println(count) // 0

newによる割り当て

組み込み関数newは引数として型または式を1つ受け取り、初期化した変数へのポインタを返します。new(x)を使うと、中間変数を置かずに値xへのポインタを1つの式で得られます。

型Tを渡したnew(T)は、型Tをゼロ値で初期化した変数を確保し、そのポインタ*Tを返します。式xを渡したnew(x)は、式xの型の変数をxの値で初期化して確保し、そのポインタを返します。非型付き定数を渡した場合はそのデフォルト型(new(42)なら*int)になります。事前宣言された識別子nilはnewの引数にできません。

// 型を渡すとゼロ値へのポインタを返す
pZero := new(int)
fmt.Println(*pZero) // 0

// 式を渡すとその値で初期化されたポインタを返す
pVal := new(42)
fmt.Println(*pVal)  // 42

// new(nil)         // NG: nilはnewの引数にできない

スライス、マップ、チャネルに対してnewを使うと、ゼロ値(nil)を指すポインタが返ります。スライスのゼロ値はappendできますが、戻り値を代入し直す必要があります。マップのゼロ値への要素代入はパニックになります。チャネルのゼロ値への送受信は、そのゴルーチンでは完了せず永久に待ち続けます。nilチャネルをcloseするとパニックになります。

送受信できるチャネルと、指定した長さや容量を持つスライスを作るには、内部データ構造を初期化した値そのものを返すmakeを使います。makeの戻り値はポインタではありません。

pMap := new(map[string]int)    // *map[string]int(*pMapはnil)
// (*pMap)["key"] = 1          // NG: nilマップへの代入でパニック

m := map[string]int{}          // リテラルで初期化したら書き込み可能
m["key"] = 1

pSlice := new([]int)           // *[]int(*pSliceはnil)
*pSlice = append(*pSlice, 1)   // OK: 戻り値を代入すればappendできる

s := make([]int, 0, 5)         // []int(指定したlenとcap)
ch := make(chan int)           // chan int(送受信できる)