Go 言語における弱ポインタ
Go 言語において「弱ポインタ」とは、ガーベジコレクタ(GC)が対象オブジェクトを回収することを妨げない参照を指します。
あるオブジェクトに弱ポインタしか指しておらず、強参照がまったく存在しない場合、GC はそのオブジェクトを到達不能なものとして回収します。その後、それを指していたすべての弱ポインタは自動的に nil になります。
要するに、弱ポインタはオブジェクトの参照カウントを増やしません。あるオブジェクトが弱ポインタだけに参照されているとき、ガーベジコレクタはそれを解放できます。したがって、弱ポインタの値を利用しようとする前には、それが nil でないかを確認する必要があります。
Go 1.24 の weak パッケージ
Go 1.24 では weak パッケージが新しく追加され、弱ポインタを作成・利用するための簡潔な API が提供されました。
import "weak"
type MyStruct struct {
Data string
}
func main() {
obj := &MyStruct{Data: "example"}
wp := weak.Make(obj) // 弱ポインタを作成
val := wp.Value() // 強参照または nil を取得
if val != nil {
fmt.Println(val.Data)
} else {
fmt.Println("オブジェクトはガーベジコレクション済み")
}
}
上記の例では、weak.Make(obj) が obj を指す弱ポインタを生成します。wp.Value() を呼び出すと、オブジェクトがまだ生存していれば強参照を返し、そうでなければ nil を返します。
弱ポインタのテスト
import (
"fmt"
"runtime"
"weak"
)
type MyStruct struct {
Data string
}
func main() {
obj := &MyStruct{Data: "test"}
wp := weak.Make(obj)
obj = nil // 強参照を削除
runtime.GC()
if wp.Value() == nil {
fmt.Println("オブジェクトはガーベジコレクション済み")
} else {
fmt.Println("オブジェクトはまだ生存している")
}
}
強参照 obj を nil にし、明示的に GC を呼び出すことで、オブジェクトが回収された後に弱ポインタが nil を返す挙動を観察できます。
「弱ポインタ」と「強参照」の違い
ガーベジコレクション(GC)への影響:
- 強参照はオブジェクトを生存させ続ける。
- 弱参照はオブジェクトを生存させない。
空値の扱い:
- 強参照が空の場合、その値は単純に
nil。 - 弱参照が空の場合も
nilになるが、これは多くの場合対象オブジェクトがすでに回収されたか、まだ値が設定されていないためである。
アクセス方法:
- 強参照は直接デリファレンスしてオブジェクトにアクセスできる。
- 弱参照はまず
Value()メソッドを呼んでからアクセスする必要がある。
サンプル 1:弱ポインタを一時的キャッシュに使う
弱ポインタの典型的な利用シーンは、キャッシュにエントリを保持しつつ、それらが GC によって回収されるのを妨げないようにする場合です。
package main
import (
"fmt"
"runtime"
"sync"
"weak"
)
type User struct {
Name string
}
var cache sync.Map // map[int]weak.Pointer[*User]
func GetUser(id int) *User {
// ① まずキャッシュから取得
if wp, ok := cache.Load(id); ok {
if u := wp.(weak.Pointer[User]).Value(); u != nil {
fmt.Println("cache hit")
return u
}
}
// ② 実際のロード(ここでは直接構築)
u := &User{Name: fmt.Sprintf("user-%d", id)}
cache.Store(id, weak.Make(u))
fmt.Println("load from DB")
return u
}
func main() {
u := GetUser(1) // load from DB
fmt.Println(u.Name)
runtime.GC() // すぐに GC しても main が強参照を保持しているので User は生存する
u = nil // 最後の強参照を解放
runtime.GC() // GC 発生で User が回収され得る
_ = GetUser(1) // 回収されていれば再度 DB からロードされる
}
このキャッシュ実装では、エントリが弱ポインタとして保存されます。オブジェクトが他に強参照を持っていなければ、GC によって回収されます。次に GetUser が呼ばれたときには、データが再読み込みされます。
実際に上記コードを実行すると次のようになります:
$ go run cache.go
load from DB
user-1
load from DB
なぜ弱ポインタを使うのか?
典型的な利用シーンには以下が含まれます:
- キャッシュ:オブジェクトをメモリに常駐させることを強制せずに保持できる。他で使われなくなれば GC により回収される。
- オブザーバーパターン:オブザーバーへの参照を保持しつつ、それらが GC に回収されるのを妨げない。
- 正規化(Canonicalization):同一オブジェクトが一つのインスタンスしか存在しないようにし、使われなくなれば回収可能にする。
- 依存関係グラフ:木やグラフ構造において、参照サイクルを形成するのを避ける。
弱ポインタ使用時の注意点
-
常に
nilチェックを行う:オブジェクトは任意の GC サイクルで回収される可能性があるため、Value()の結果をキャッシュしてはいけない。 - 循環依存を避ける:弱ポインタ内のオブジェクトが、自分を作ったコンテナを再び強参照してしまうと、結局強参照チェーンが形成されてしまう。
-
性能面でのトレードオフ:弱ポインタへのアクセスには追加の呼び出しが必要であり、頻繁に
nilからオブジェクトを再ロードすると揺らぎが生じる。
サンプル 2:強ポインタの通常利用
package main
import (
"fmt"
"runtime"
)
type Session struct {
ID string
}
func main() {
s := new(Session) // &Session{} と同等
s.ID = "abc123"
fmt.Println("strong ref alive:", s.ID)
s = nil // 最後の強参照を解放
runtime.GC() // GC を試しに呼び出す(実際のタイミングはランタイムが決定)
fmt.Println("done")
}
ここでの s は強ポインタです。これが到達可能である限り、Session オブジェクトは GC によって回収されることは決してありません。
強ポインタが指すオブジェクトはいつ GC されるのか?
-
到達可能性による判定:Go はマーク・スイープ方式の GC を採用しており、GC サイクルの開始時にランタイムはルートオブジェクト(スタック、グローバル変数、現在のレジスタなど)からすべての強参照をたどる。
- 強参照のチェーンを通じて到達できるオブジェクトは「到達可能 (reachable)」とみなされ、必ず生存する。
- それ以外のオブジェクトは「到達不能 (unreachable)」とマークされ、スイープ段階で解放される。
-
参照カウントは存在しない:重要なのは「ルートから到達可能かどうか」だけ。変数の数や値の等価性は回収に影響しない。
-
タイミングは不確定:GC サイクルはスケジューラによって自動的にトリガーされる。開発者は
runtime.GC()を呼んで“提案的”に発生させられるが、即座に回収される保証はない。 -
変数自体も GC の対象:強ポインタ変数
sがヒープ上にあり、それを含む構造体が到達不能になればs自体も回収対象になる。スタック変数は関数リターン時に解放される。
まとめると:
強ポインタは、対象オブジェクトを任意の GC サイクルにおいて「生存集合」にとどめる。最後の強参照チェーンが途切れた瞬間、そのオブジェクトは次の GC サイクルで自動的に解放される。
私たちはLeapcell、Goプロジェクトのホスティングの最適解です。
Leapcellは、Webホスティング、非同期タスク、Redis向けの次世代サーバーレスプラットフォームです:
複数言語サポート
- Node.js、Python、Go、Rustで開発できます。
無制限のプロジェクトデプロイ
- 使用量に応じて料金を支払い、リクエストがなければ料金は発生しません。
比類のないコスト効率
- 使用量に応じた支払い、アイドル時間は課金されません。
- 例: $25で6.94Mリクエスト、平均応答時間60ms。
洗練された開発者体験
- 直感的なUIで簡単に設定できます。
- 完全自動化されたCI/CDパイプラインとGitOps統合。
- 実行可能なインサイトのためのリアルタイムのメトリクスとログ。
簡単なスケーラビリティと高パフォーマンス
- 高い同時実行性を容易に処理するためのオートスケーリング。
- ゼロ運用オーバーヘッド — 構築に集中できます。
Xでフォローする:@LeapcellHQ


