0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Google Colabで学ぶGoのポインタ入門 失敗例を含めて解説

0
Last updated at Posted at 2026-07-05

Go言語を学んでいると、途中で *& が出てきます。
ポインタが使われたGoのコード

本記事では、Google Colab上でGoのコードを動かしながら、

  • 値渡し
  • ポインタ渡し
  • &*
  • nilポインタ
  • structとポインタ
  • rangeでやりがちなミス

を、解説していきます。
Google Colabを使いながら、よくある失敗例も多めに紹介するので、参考にしてください。

対象読者

この記事は、次のような人を対象にしています。

  • Google Colabを使ったことがある人
  • Goの基本文法を少し触ったことがある人
  • func main()、関数、struct、sliceがなんとなく読める人
  • *& が出てくると急に読めなくなる人
  • Goのポインタで値が思った通りに変わらない理由を知りたい人

本格的なメモリ管理の話ではなく、まずはGoのコードを読むためのポインタ入門です。

学習を進めるために

なお、「Google ColabでGoのプログラムを動かすなんてできるの?」で引っかかってる読者は、まず下記記事で予習してください。

また、先に、Go言語の基本文法が知りたいという人は、下記記事を見てください。

すぐに使えるチートシートはこちら Google Colab版

すぐに実行して、試せるコードレシピはこちら。

ポインタとは何か

ポインタは「値のメモリアドレスを保持するもの」であり、Goでは、次の2つの記号が重要です。

&  : 変数のアドレスを取得する
*  : ポインタの指す先の値を読む、または変更する

まずは小さな例を見ます。

%%writefile pointer_basic.go
package main

import "fmt"

func main() {
    x := 10

    p := &x

    fmt.Println("xの値:", x)
    fmt.Println("xのアドレス:", &x)
    fmt.Println("pの値:", p)
    fmt.Println("pが指す先の値:", *p)
}
xの値: 10
xのアドレス: 0xc0000120c0
pの値: 0xc0000120c0
pが指す先の値: 10

&x は、x のアドレスです。

p := &x
pはxの場所を覚えている

という意味で、*pと書くと、p が指している先の値を取り出せます。

ここでは、px を指しているため、*p10 になります。

ポインタ経由で値を変更する

ポインタを使うと、指している先の値を変更できます。

%%writefile pointer_update.go
package main

import "fmt"

func main() {
    x := 10

    p := &x

    *p = 20

    fmt.Println("x:", x)
    fmt.Println("*p:", *p)
}

実行結果は、こうなります。

x: 20
*p: 20

*p = 20 と書くことで、p が指している先の値を変更しています。

px を指しているため、結果として x の値も20になります。

つまり、

*p = 20は、pが指している元データを書き換えるという意味です。

失敗例1: 関数に渡したのに値が変わらない

Goでは、関数に値を渡すと、基本的に値はコピーされます。

まず、値が変わらない例を見ます。

%%writefile value_copy.go
package main

import "fmt"

func addOne(n int) {
    n = n + 1
    fmt.Println("関数の中:", n)
}

func main() {
    x := 10
    addOne(x)
    fmt.Println("mainの中:", x)
}

実行結果です。

関数の中: 11
mainの中: 10

関数の中では 11 になっています。

しかし、main の中の x10 のままです。

これは、addOne(x) で渡された x がコピーされているからです。

mainのx = 10
↓
addOneにコピーを渡す
↓
関数の中のnだけが11になる
↓
mainのxは10のまま

ここでやりがちな誤解は、

関数の中でnを変更したから、元のxも変わるはず

というものです。

しかし、普通に値を渡しただけでは、元の変数は変わりません。

正解: ポインタを渡す

元の値を変更したい場合は、ポインタを渡します。

%%writefile pointer_func.go
package main

import "fmt"

func addOne(n *int) {
    *n = *n + 1
}

func main() {
    x := 10

    addOne(&x)

    fmt.Println("mainの中:", x)
}
mainの中: 11

ここで重要なのは、関数の引数です。

func addOne(n *int)

*int は、「int型の値を指すポインタ」という意味です。

呼び出す側では、&x と書いています。

addOne(&x)

これは、x のアドレスを渡しています。

関数の中では、*n でポインタの指す先を変更します。

*n = *n + 1

つまり、

xそのものを渡すのではなく、xの場所を渡す
その場所にある値を書き換える

という流れです。

失敗例2: structを変更したつもりなのに変わらない

次に、structでよくある失敗です。

%%writefile struct_value_copy.go
package main

import "fmt"

type User struct {
    Name string
    Age  int
}

func birthday(u User) {
    u.Age = u.Age + 1
    fmt.Println("関数の中:", u)
}

func main() {
    user := User{Name: "Alice", Age: 30}

    birthday(user)

    fmt.Println("mainの中:", user)
}
関数の中: {Alice 31}
mainの中: {Alice 30}

関数の中では Age が31になっています。

しかし、main の中では30のままです。

これも、structがコピーされているからです。

userを関数に渡す
↓
Userのコピーが作られる
↓
関数の中ではコピーのAgeを変更する
↓
元のuserは変わらない

正解: structのポインタを渡す

元のstructを変更したい場合は、structのポインタを渡します。

%%writefile struct_pointer.go
package main

import "fmt"

type User struct {
    Name string
    Age  int
}

func birthday(u *User) {
    u.Age = u.Age + 1
}

func main() {
    user := User{Name: "Alice", Age: 30}

    birthday(&user)

    fmt.Println("mainの中:", user)
}

実行結果です。

mainの中: {Alice 31}

ここでは、関数の引数が *User になっています。

func birthday(u *User)

呼び出し側では、&user と書いています。

birthday(&user)

つまり、user の場所を渡しています。

なお、structのポインタを使うとき、本来は次のように書けます。

(*u).Age = (*u).Age + 1

ただし、この書き方は面倒です。

Goでは、structのポインタに対しても、次のように書けます。

u.Age = u.Age + 1

Go公式Tourでも、structポインタのフィールドには p.X のようにアクセスできると説明されています。

失敗例3: nilポインタを読んでpanicする

ポインタのゼロ値は nil です。

nil は、どこも指していない状態です。

%%writefile nil_pointer.go
package main

import "fmt"

func main() {
    var p *int

    fmt.Println(p)
    fmt.Println(*p)
}

これはpanicとなります。

実行結果の例です。

panic: runtime error: invalid memory address or nil pointer dereference

p はどこも指していません。

その状態で、*p

と書くと、「どこも指していないのに、その先の値を読もうとする」ことになり、panicとなります。

nilの可能性がある場合は、先に確認します。

%%writefile nil_check.go
package main

import "fmt"

func main() {
    var p *int

    if p == nil {
        fmt.Println("pはnilです")
        return
    }

    fmt.Println(*p)
}

実行結果です。

pはnilです

失敗例4: sliceの要素を変更したつもりなのに変わらない

次に、sliceとstructの組み合わせでよくある失敗です。

%%writefile range_value_copy.go
package main

import "fmt"

type User struct {
    Name string
    Age  int
}

func main() {
    users := []User{
        {Name: "Alice", Age: 30},
        {Name: "Bob", Age: 25},
        {Name: "Carol", Age: 40},
    }

    for _, user := range users {
        user.Age = user.Age + 1
    }

    fmt.Println(users)
}
[{Alice 30} {Bob 25} {Carol 40}]

年齢を1つずつ増やしたつもりなのに、変わっていません。

原因はここです。

for _, user := range users {
    user.Age = user.Age + 1
}

range で取り出した user は、sliceの要素そのものではなく、要素のコピーです。

そのため、user.Age を変更しても、元の users の中身は変わりません。

users[0] のコピーを user に入れる
user.Age を変更する
でも users[0] は変わらない

正解: indexで元の要素を変更する

sliceの元の要素を変更したい場合は、indexでアクセスします。

%%writefile range_index_update.go
package main

import "fmt"

type User struct {
    Name string
    Age  int
}

func main() {
    users := []User{
        {Name: "Alice", Age: 30},
        {Name: "Bob", Age: 25},
        {Name: "Carol", Age: 40},
    }

    for i := range users {
        users[i].Age = users[i].Age + 1
    }

    fmt.Println(users)
}

実行結果です。

[{Alice 31} {Bob 26} {Carol 41}]

今度はコピーではなく、sliceの中の要素を直接変更できました。

ここまでの整理

ポインタで混乱したときは、まず次のように考えるとよいです。

普通の変数:
値そのものを持っている

ポインタ:
値の場所を持っている

&x:
xの場所を取る

*p:
pが指している場所の値を読む、または変更する

そして、よくある失敗は次のように整理できます。

関数に値を渡しただけ
→ コピーなので元の値は変わらない

structを関数に渡しただけ
→ コピーなので元のstructは変わらない

rangeで取り出した値を変更する
→ コピーなので元のsliceは変わらない

range変数のアドレスを保存する
→ 元のslice要素を指しているとは限らない

nilポインタを読む
→ panicする

ポインタを使うべき場面

入門段階では、ポインタは次のような場面で使うと理解しやすいです。

  • 関数の中で元の値を変更したい
  • structが大きいのでコピーを避けたい
  • メソッドで元のstructを変更したい
  • sync.WaitGroupのように、共有して使う状態を渡したい

逆に、何でもポインタにすればよいわけではありません。

ポインタを使うと、複数の場所から同じ値を変更できるようになります。しかし、どこで値が変わったのかわかりにくくなることもあります。

そのため、まずは、

* 元の値を変更する必要があるか
* コピーでよいか

を考えるとよいと思います。

まとめ

本記事では、Google Colab上でGoのポインタを学びました。

Google ColabやJupyter Notebookで学ぶと、こうした失敗例を小さなセルごとに試せます。

完成した正解コードを暗記するよりも、

* 変更したつもりなのに変わらない
* 全部同じ値になってしまう
* panicになる

といった失敗を実際に見てみるのがおすすめです。また、小さなコードで「作って・壊して」をやってみるのも効果的です。

弊社について

本記事を書いている 合同会社インクルーシブソリューションズ は、データ基盤構築・分析基盤設計・システム改善支援を中心に活動している小規模IT法人です。

主な領域は、

  • データマート設計・データパイプライン構築
  • SQL / Python を用いたデータ処理設計
  • BI導入支援・分析基盤の整備
  • 既存システムの運用改善・可視化支援

といった、「データを使える状態にする」ための活動です。

弊社の企業活動に興味がある方は、ぜひ公式サイトも覗いてみてください。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?