クイックフィックス:30秒で解決
このパニックを止めるには、変数へのポインタを渡し、.Elem() を呼び出す必要があります。Go はコピーで渡された値を変更できません。リフレクションオブジェクトが元のメモリ番地を所有していないためです。
// ❌ 失敗:コピーを渡している(アドレス指定不可)
val := reflect.ValueOf(myVar)
val.Set(reflect.ValueOf(newValue))
// ✅ 成功:ポインタを渡してデリファレンスする
val := reflect.ValueOf(&myVar).Elem()
val.Set(reflect.ValueOf(newValue))
なぜこのエラーが発生するのか?
Go にメモリ番地を教えずにリフレクション経由で変数を変更しようとすると、この「unaddressable value(アドレス指定不可能な値)」エラーが発生します。通常、これは変数が参照ではなく値によって reflect.ValueOf() に渡された場合に起こります。
panic: reflect: reflect.Value.Set using unaddressable value
goroutine 1 [running]:
reflect.Value.Set(...)
/usr/local/go/src/reflect/value.go:2235
main.main()
/path/to/your/code.go:15
根本原因:アドレス指定可能性 vs 設定可能性
Go のリフレクションパッケージは、潜在的なバグから保護してくれます。reflect.ValueOf(x) を呼び出すと、Go は x のコピーを含むリフレクションオブジェクトを作成します。もしランタイムがこのコピーに対して Set() の呼び出しを許可してしまうと、一時的な変数は更新されますが、元の変数は手付かずのままになってしまいます。この混乱を招く動作を防ぐため、Go はパニックを発生させます。
技術的には、reflect.Value が**設定可能(settable)**であるためには、次の 2 つの基準を満たす必要があります:
- **アドレス指定可能(addressable)**であること(元のデータの実際のメモリ番地を保持していること)。
- **エクスポートされていない(unexported)**構造体フィールドではないこと(プライベートフィールドは読み取り専用のままです)。
ステップバイステップの解決策
1. ポインタ + Elem パターン
変数のアドレス(&name)を渡すことで、リフレクションパッケージはデータがどこにあるかを正確に把握できます。単にポインタを渡すだけでは不十分であることに注意してください。reflect.ValueOf(&name) はポインタ自体を表します。ポインタ型に文字列を無理やり流し込むことはできません。代わりに、.Elem() を使用して、ポインタが指し示している実際の値にアクセスします。
func main() {
name := "初期状態"
// 1. ポインタの値を取得
ptr := reflect.ValueOf(&name)
// 2. デリファレンスして実際の文字列を取得
v := ptr.Elem()
if v.CanSet() {
v.SetString("更新後の状態")
}
fmt.Println(name) // 出力:更新後の状態
}
2. 構造体フィールドの変更
構造体も同じルールに従います。構造体を値で渡すと、そのリフレクションオブジェクト内のすべてのフィールドがアドレス指定不可能になります。特定のフィールドを更新するには、構造体へのポインタから始める必要があります。
type Config struct {
Retries int
}
c := Config{Retries: 3}
// ✅ 常に構造体へのポインタを使用する
rv := reflect.ValueOf(&c).Elem()
field := rv.FieldByName("Retries")
if field.CanSet() {
field.SetInt(5)
}
本番環境での安全性:常に検証する
本番環境のコードでは、入力が正しいと仮定しないでください。CanSet() のチェックによるオーバーヘッドは 10 ナノ秒未満です。誤ってポインタ以外が渡された場合にアプリケーション全体がクラッシュするのを防ぐためのコストとしては、非常にわずかなものです。
func SafeUpdate(target interface{}, data interface{}) {
v := reflect.ValueOf(target)
// ポインタであることを確認
if v.Kind() != reflect.Ptr {
return
}
elem := v.Elem()
if elem.CanSet() {
elem.Set(reflect.ValueOf(data))
}
}
まとめ
- **パニックの原因:**コピーから作成されたリフレクション値に対して `Set()` を使用しようとした。
- **解決策:**メモリ番地(`&変数`)を渡し、`.Elem()` を呼び出す。
- **プロのヒント:**コードの堅牢性を保つため、変更を行う前に必ず `v.CanSet()` をチェックする。

