↗️

Goのポインタって何がいいの?

2024/12/09に公開

メリット

1. メモリや処理速度の向上

何かの処理を行う関数に引数として渡すとき、値渡しとポインタ渡しの2種類がある。値渡しの場合は、データをコピーして渡すのに対して、ポインタ渡しはアドレス(例:0xc00009e210)を渡す。大きなデータ構造を引数にする場合、値私の場合はコピーするのにメモリや処理時間を消費してしまうが、ポインタを使用することでそのデメリットがなくなる。
https://go.dev/play/p/0iueowiNQ0W

package main

import "fmt"

type User struct {
	name  string
	phone int
}

func main() {
	u := User{name: "test", phone: 123456789}
	printUser(u)

	printPointerUser(&u)

	fmt.Println("exit")
}

// 引数のuserはmain関数からコピーして渡されるので、Userのデータ構造が巨大な場合その分コストがかかる
func printUser(user User) {
	fmt.Println(user)
}

// アドレスが渡される
func printPointerUser(user *User) {
	fmt.Println(user)
}

2. 値の上書き

Goの仕様として、ポインタを使用することで関数内でデータの変更を呼び出しもとに反映させることができる。値渡しの場合、関数内での変更は呼び出し元の変更ができない(コピーされたのものの変更をするため)。
https://go.dev/play/p/8AWc_gNG7cg

package main

import "fmt"

type User struct {
	name  string
	phone int
}

func main() {
	u := User{name: "test", phone: 123456789}
	u.changeName()
	// changeName関数でnameを"test" -> "aaaaaaaa"に変えたはずなのに、testのまま出力される
	fmt.Println(u)

	fmt.Println("========")

	u.changePhone()
	fmt.Println(u)
}

func (u User) changeName() {
	u.name = "aaaaaaaa"
}

// 呼び出し元(main)変更を反映できる
func (u *User) changePhone() {
	u.phone = 000000000
}

3. nil値を扱うことができる

ポインタを使用しなければ、初期化した際に各型の初期値(0や空文字、falseなど)が指定される。
https://go.dev/play/p/vYtnoO6Nsdt

package main

import "fmt"

func main() {
	var a int
	fmt.Println(a)

	fmt.Println("========")

	var b *int
	fmt.Println(b)
}

デメリット

1. nilポインタ参照のリスク

ポインタがnilの状態で実体を参照しようとすると、nil pointer errorが発生する。

2. メモリ管理の負担

Goはガベージコレクション(不要になったメモリを解放する機能)を備えているが、ポインタを過度に使用すると不要なメモリ参照が残り、パフォーマンスの低下につながる。

3. 値のコピーよりも非効率になる場合がある

小さなデータ型をポインタで渡すと、かえってパフォーマンスが低下することがある。

Discussion