Go言語の関数パラメータ!値渡しと参照渡しの違いを理解しよう
生徒
「Go言語で関数に値を渡すときって、どういう仕組みなんですか?」
先生
「いい質問ですね。Go言語ではすべて値渡しですが、ポインタを使うことで元の値を変更できます。」
生徒
「えっ、参照渡しはないんですか?」
先生
「厳密にはありません。ただし、ポインタを使うと参照渡しのような挙動になります。順番に解説しましょう。」
1. Go言語の関数とは?
まず、関数とは「何かの処理をまとめて、名前をつけて呼び出せるようにしたもの」です。例えば、「足し算をする関数」を作れば、好きなときにその計算を呼び出して使うことができます。
Go言語では、関数は次のように書きます。
func 足し算(a int, b int) int {
return a + b
}
funcは関数を定義するキーワード、a intとb intは引数(関数に渡すデータ)、intは戻り値の型を表します。
2. 値渡しとは?
Go言語では、関数に変数を渡すとき、基本的には値渡し(英語でpass by value)になります。これは「変数の中身のコピーを渡す」という意味です。
つまり、関数の中で変数の値を変更しても、元の変数には影響がありません。
たとえば、次のようなコードです。
func 変更する(x int) {
x = 100
}
func main() {
a := 10
変更する(a)
fmt.Println(a)
}
このとき、出力結果はどうなると思いますか?
10
関数内でxを100に変えても、aには影響がないのです。なぜなら、aのコピー(x)を渡しているからです。
3. 参照渡しのように見える挙動とは?
Go言語では、すべての引数は値渡しです。これはポインタを使った場合でも変わりません。
ただし、ポインタを引数として渡すことで、「元のデータを変更しているように見える」挙動を実現できます。
ポインタとは「変数のアドレスを格納した値」です。つまり、関数にはアドレスのコピーが渡されています。
func 変更する(x *int) {
*x = 100
}
func main() {
a := 10
変更する(&a)
fmt.Println(a)
}
出力結果は次のとおりです。
100
&aで変数aのアドレスを渡し、*xでそのアドレスが指す値を書き換えています。そのため、結果としてaの値が変更されます。
4. 値渡しとポインタ引数の違いをイメージで理解しよう
値渡しとポインタを使った引数の違いを、日常の例で考えてみましょう。
友達に「メニュー表のコピー」を渡す場合、友達が何を書き加えても、あなたの手元のメニュー表は変わりません。これは値渡しです。
一方、「メニュー表が置いてある場所の住所」を教えた場合、友達がその場所にあるメニュー表を書き換えると、あなたの手元の内容も変わります。
Go言語のポインタは、この「住所(アドレス)を渡す」イメージに近い仕組みです。
5. ポインタの使い方をもう少し深く
Go言語では、ポインタ(アドレス)を引数として渡すことで、関数の中から呼び出し元の値を更新できます。なお、ポインタを渡す場合でも、関数に渡っているのは「アドレスという値のコピー」です。
func ポインタの例(x *int) {
*x = *x + 1
}
*xは「xが指している場所の中身」という意味です。*x = *x + 1と書けば、中身の値を1増やすことができます。
そして、呼び出し時には&変数名を使います。
num := 5
ポインタの例(&num)
fmt.Println(num) // 結果は6
6. 値渡しとポインタ引数の使い分け
Go言語では、基本的に値渡しを使う設計が推奨されています。
ただし、次のような場合にはポインタを引数として渡すことがあります。
- 関数内で値を変更し、その結果を呼び出し元に反映させたい場合
- 大きな構造体をコピーせず、効率的に処理したい場合
ポインタを使うと副作用が発生しやすくなるため、「本当に変更が必要か」を意識して設計することが重要です。
7. 関数の引数での注意点
関数に渡すときにポインタを使うと、データを意図せず変更してしまうこともあります。初心者のうちは「どの関数がデータを変更するか」に注意しましょう。
ポインタを使う=関数が元の変数を変える可能性がある、ということを意識しておくと良いです。
まとめ
Go言語の関数パラメータと値渡しの基本を振り返る
この記事では、Go言語における関数の引数の仕組みについて、値渡しと参照渡しの違いを軸に解説してきました。Go言語で関数を定義するとき、引数に渡される値は、基本的にすべて値渡しになります。これは、数値型や文字列型といった基本的な型だけでなく、ポインタを使った場合でも同じ考え方です。関数を呼び出すとき、呼び出し元の変数そのものが渡されるのではなく、変数の値、あるいはアドレスという値のコピーが渡される点が重要です。
値渡しの仕組みを正しく理解しておくことで、「関数の中で値を変更したのに、なぜ外側の変数は変わらないのか」といった疑問を自然に説明できるようになります。Go言語の関数設計では、この値渡しの性質を前提として、安全で予測しやすいコードを書くことが推奨されています。
ポインタを使った引数と参照的な挙動
一方で、Go言語ではポインタを引数として渡すことで、参照渡しのように見える挙動を実現できます。ポインタを使うと、関数の中から呼び出し元の変数が保持している値を書き換えることが可能になります。ただし、この場合でも、関数に渡っているのは「変数そのもの」ではなく、「変数のアドレスという値」である点は変わりません。
この考え方を理解せずにポインタを使ってしまうと、意図しない副作用が発生しやすくなります。特に、Go言語での関数設計やコードレビューでは、「この関数は値を変更するのか」「読み取り専用なのか」を意識することが重要になります。
まとめとして確認しておきたいサンプルプログラム
最後に、値渡しとポインタ引数の違いをあらためて確認できる簡単なサンプルを見てみましょう。Go言語で関数を使うとき、どのような書き方をすると値が変わり、どのような場合に変わらないのかを意識することが大切です。
func 値渡しの例(x int) {
x = x + 10
}
func ポインタ引数の例(x *int) {
*x = *x + 10
}
func main() {
a := 5
値渡しの例(a)
fmt.Println(a)
ポインタ引数の例(&a)
fmt.Println(a)
}
このプログラムでは、最初の関数呼び出しでは値渡しのため変数の値は変わらず、次のポインタを使った関数呼び出しで初めて値が更新されます。この違いを理解しておくことで、Go言語の関数と引数の挙動をより深く理解できるようになります。
生徒
「Go言語って、参照渡しがあると思っていましたけど、実際には全部値渡しなんですね。ポインタも値として渡されていると聞いて、だいぶ整理できました。」
先生
「その理解で大丈夫です。Go言語では、関数に渡るものは必ず値です。ただし、その値がアドレスの場合、結果として元の変数が変更される、という仕組みですね。」
生徒
「だから、関数の引数にポインタがあるときは、この関数は値を書き換える可能性がある、と意識したほうがいいんですね。」
先生
「その通りです。値渡しとポインタ引数の違いを意識することで、Go言語らしい読みやすく安全なコードが書けるようになりますよ。」