【Go言語学習|豆知識】Go言語における「変数の不変性」との付き合い方:constと擬似的な不変性の実装

導入

Go言語には、多くの関数型言語にあるような「宣言時に値を固定し、二度と変更できない変数(イミュータブル変数)」という概念が言語仕様として存在しません。`const`はあくまでコンパイル時の定数であり、実行時の値には使えません。しかし、予期せぬ値の書き換えはバグの温床となります。本記事では、Goでどのように「不変性」を担保し、安全なコードを書くべきかについて解説します。

基礎知識

Goにおける変数は、メモリ上の特定の領域を指し示しています。変数を代入するということは、そのメモリ領域の値を書き換える行為です。Goの設計思想として、メモリの効率的な管理を優先しているため、基本的に変数は「書き換え可能」な状態として扱われます。特にスライスやマップ、構造体へのポインタは、参照先の実体を簡単に書き換えることができるため、意図しない副作用を生みやすいという特徴があります。

実装/解決策

Goで擬似的に不変性を実現するには、以下の手法が有効です。
1. ゲッターメソッドによるカプセル化: 構造体のフィールドを非公開(小文字開始)にし、公開メソッド経由でのみ値を取得させることで、外部からの直接書き換えを防ぎます。
2. 値のコピーを返す: メソッドから値を返す際に、参照ではなく値をコピーして渡すことで、元のデータへの影響を遮断します。
3. 型による制限: `const`として定義できるものは積極的に定数化し、実行時の変更を許容しない設計を心がけます。

サンプルプログラム

以下のコードは、カプセル化を用いて内部データの安全性を保つ例です。

package main

import “fmt”

// User構造体。フィールドを小文字にすることでパッケージ外からの直接アクセスを禁止
type User struct {
name string
}

// NewUser はUserのコンストラクタ的な役割を果たします
func NewUser(name string) User {
return User{name: name}
}

// GetName は名前を取得するメソッド。値をコピーして返すため、元データは変更されない
func (u User) GetName() string {
return u.name
}

func main() {
u := NewUser(“Go Engineer”)

// u.name = “Hacker” // これはコンパイルエラーになる(フィールドが非公開のため)

fmt.Println(“名前:”, u.GetName())
}

応用・注意点

現場で陥りやすいのが、スライスを引数として渡すケースです。スライスは内部的に配列へのポインタを持っているため、関数内で`slice[0] = 10`のように書き換えると、呼び出し元の変数にも影響が及びます。
これを防ぐには、関数内でスライスのコピーを作成してから操作するか、破壊的変更を行わない設計を徹底することが重要です。また、大規模なプロジェクトでは、`DeepCopy`を行うライブラリを利用するか、あるいはイミュータブルな設計を強制するコード規約をチームで共有することをお勧めします。メモリの書き換え可能性というGoの特性を理解し、適切にガードを設けることが、堅牢なバックエンド開発への第一歩です。

コメント

タイトルとURLをコピーしました