Go言語の関数で型パラメータ(ジェネリクス)を使う基本
生徒
「Go言語で同じような関数を複数の型で使い回す方法ってありますか?」
先生
「はい、Go1.18以降では『ジェネリクス』という仕組みを使って、関数に型パラメータを渡すことができるようになりました。」
生徒
「型パラメータってなんですか?」
先生
「型パラメータとは、関数や構造体に『型』を後から指定できるしくみのことです。同じ処理を整数や文字列など、さまざまな型で使いたいときにとても便利です。」
生徒
「なるほど!具体的な使い方を教えてください!」
先生
「では、Go言語の型パラメータの基本を見ていきましょう。」
1. ジェネリクスとは何か?
ジェネリクス(generics)とは、1つの関数や構造体を、違う型でもそのまま使い回せるようにするしくみです。たとえば、整数を表示する関数と、文字列を表示する関数を別々に作ると、同じようなコードが増えてしまいます。そこで、ジェネリクスを使うと、型だけを後から差し替えられるため、同じ処理を共通化できてスッキリ書けます。Go1.18以降で利用できる新しい機能なので、Go言語でも現代的なプログラミングができるようになりました。
「型パラメータ」という箱を用意しておき、関数を使うときに「この型で使いたい」と指定するイメージです。型が違っても同じロジックを再利用できるので、同じような関数を何度も作る必要がなくなります。
生徒
「ジェネリクスって、具体的にどんな場面で使うと便利なんですか?」
先生
「たとえば『値を表示するだけの関数』を考えてみましょう。同じ表示処理でも、整数も文字列も浮動小数点数もありますよね?ジェネリクスを使えば、型ごとに関数を分けなくても、1つの関数で全部に対応できます。」
生徒
「なるほど!型が違っても同じ処理なら、ジェネリクスにまとめられるということですね!」
先生
「その通りです。コードが少なくなるだけでなく、後から型を増やしても関数を書き直さなくていいというメリットがあります。」
2. 型パラメータを使った基本的な関数の書き方
型パラメータは、関数名の直後に角カッコで書きます。もっとも基本的な形は[T any]です。ここでTは「これから入ってくる型の記号名」、anyは「どんな型でも受け付ける」という意味です。記号名はTでなくてもかまらず、ItemやValueのように読みやすい名前にできます。
package main
import "fmt"
// 「どんな型でも受け取り、そのまま表示する」関数
func PrintValue[T any](value T) {
fmt.Println(value)
}
func main() {
// 呼び出し時に型を角カッコで明示(基本形)
PrintValue
PrintValue[string]("こんにちは")
}
ポイントは3つです。①型パラメータは関数名の直後に角カッコで定義すること、②関数の引数や戻り値の型にその記号(ここではT)を使うこと、③呼び出し時は角カッコで具体的な型(intやstringなど)を指定できることです。たったこれだけで、同じ処理を異なる型に使い回せます。なお、型を省略しても動く場面がありますが、それは次の章「型推論」で説明します。
もうひとつ、記号名を変えた例です。読みやすさのために、用途に合う名前を付けるのも実務ではよくあります。
package main
import "fmt"
// 記号名を Value にした例(意味が伝わりやすい)
func Show[Value any](v Value) {
fmt.Println(v)
}
func main() {
Show
Show[string]("Go")
}
このように、ジェネリクスの基本は「角カッコで型の入れ物を宣言し、その入れ物(Tなど)を関数の引数や戻り値の型として使う」ことです。最初は「型の名前を後で差し込む枠」と覚えると、すっと理解できます。
3. 型推論を活用しよう
型推論とは、呼び出し側で明示的に型を書かなくても、渡した値や代入先の型からコンパイラが自動的に型パラメータを決めてくれる仕組みです。毎回角カッコで型を書く手間を減らせるため、読みやすく間違いの少ないコードになります。まずは、引数から推論される一番シンプルな例を見てみましょう。
func main() {
PrintValue(123) // 引数が整数なので T は int と推論
PrintValue("Go言語") // 引数が文字列なので T は string と推論
}
次に、戻り値の受け取り方(代入先の型)から推論させる例です。引数だけで決めづらい場合でも、左辺の型情報があれば正しく推論できます。
package main
import "fmt"
func Echo[T any](v T) T { return v }
func main() {
var n int
n = Echo(10) // 左辺が int なので T は int と推論
s := Echo("こんにちは") // 右辺が文字列なので T は string と推論
fmt.Println(n, s)
}
なお、推論には「手がかり」が必要です。たとえば、引数がなくて型情報が得られない関数や、nilだけを渡すケースでは型が決められません。その場合は、角カッコで型を明示します。
package main
// 値をそのまま返すだけの関数(説明用)
func NewZero[T any]() T {
var z T
return z
}
func main() {
// 引数がないので推論できない → 型を明示する
_ = NewZero[int]() // T を int と指定して呼び出す
}
まとめると、「引数」または「代入先の型」のどちらかに手がかりがあれば型は自動で決まります。手がかりがないときだけ、最小限の型指定を足す。この感覚で使うと、スッキリしたコードを書けます。
4. 制約(constraint)を使って特定の型だけ許可する
型パラメータにconstraint(制約)を指定すると、特定の型に限定できます。たとえば数値型だけ許可したい場合は、constraintsパッケージを使います。
import (
"fmt"
"golang.org/x/exp/constraints"
)
func Double[T constraints.Integer](x T) T {
return x * 2
}
このようにすると、整数型のみが許可され、文字列などは使えなくなります。
5. 複数の型パラメータを使う例
ジェネリクスでは、複数の型パラメータも扱えます。以下の例では、2つの値を受け取ってペアにします。
type Pair[A, B any] struct {
First A
Second B
}
func main() {
p := Pair[int, string]{First: 1, Second: "Apple"}
fmt.Println(p)
}
構造体にも型パラメータを使えることがわかります。
6. 型パラメータを使うときの注意点
- 型パラメータの記述には角カッコ
[]を使います。 - Go1.17以前ではジェネリクスは使えません。必ずGo1.18以降で試してください。
- コードが複雑になりすぎないように、シンプルな関数から始めましょう。
まとめ
ここまで、Go言語で関数に型パラメータを指定して使える「ジェネリクス」の基本を、最初の一歩から順番に見てきました。そもそもジェネリクスとは、ひとつの関数で色々な型を扱えるようにする仕組みであり、整数や文字列など型が異なる値に対して、同じ処理を繰り返すためのコードを書く手間を省く役割を持ちます。プログラム初心者にとって「型が違うのに同じ関数で処理できる」というだけでも、仕組みの便利さが実感できるはずです。
今までは、整数なら整数用の関数、文字列なら文字列用の関数を別々に用意していましたが、ジェネリクスを使えば、そのような重複した定義を省き、より読みやすいプログラムを書くことができます。特に、複数の場面で同じ処理を繰り返すときに効果が大きく、メンテナンスしやすいコードになります。プログラムが長くなるほど、こうした「共通化できる部分」をまとめる意味が大きくなり、あとから見返しても理解しやすい構造になります。
また、型パラメータには「制約」を指定できるという特徴もあり、「整数だけ許可する」「文字列だけ許可する」といった細かい条件を付けることができます。これによって誤った型を渡してしまうミスを防ぎやすくなり、より安全なプログラムを書けます。さらに複数の型パラメータを使えば、二つの値をまとめたり、違う型同士の組み合わせを柔軟に扱うこともできます。
最後に、型推論のおかげで呼び出し側が型を明示しなくても動作する場面が多く、初心者でも扱いやすいのがGoの魅力です。角カッコで型パラメータを書くというルールさえ覚えれば、難しい理屈を知らなくても少しずつ慣れていくことができます。ここで紹介したコードはそのまま動かせるので、手元の環境で実際に試し、値を変えたり型を増やしたりしながら、感覚を掴んでみてください。
短いサンプルでもう一度復習
これまで登場した仕組みを小さなコードにまとめると、次のような形になります。「整数」「文字列」「小数」など、色々な型で呼び出して試せます。
package main
import "fmt"
// 型パラメータを使った簡単な関数
func Echo[T any](v T) T {
return v
}
func main() {
fmt.Println(Echo)
fmt.Println(Echo("こんにちは"))
fmt.Println(Echo[float64](3.14))
}
この短いプログラムの中に、ジェネリクスの基本が詰まれています。型パラメータを書いて、好きな型を指定して呼び出し、型推論でも呼び出せる。考え方が見えると、一気に理解しやすくなります。
よくあるつまずきと注意点
初めてジェネリクスを使うと、つい「この型は使えるの?」「制約を付けたほうが良いの?」と悩むことがあります。しかし、最初から難しい使い方をしなくても問題ありません。まずは「なんでも受け付ける」anyから始めて、少しずつ制約を加える経験を積むと理解が確実になります。
また、型パラメータを付けすぎるとコードが読みにくくなることもあります。複雑な構造を作るより、シンプルな場面を整理しながら使うほうが、初心者には理解しやすい使い方になります。必要なときだけ型パラメータを活用し、自分が読める、仲間が読める、未来の自分が読み返せるコードを意識することが大切です。
生徒
「先生、型パラメータって思ったより難しくなかったです。ひとつの関数で色んな型を扱えるのはすごく便利ですね!」
先生
「そうなんです。Go言語でもジェネリクスが使えるようになったことで、コードの重複を減らしやすくなりました。今後の開発では、よく使う処理をまとめるときに役立ちます。」
生徒
「型推論も便利ですね。毎回角カッコで指定しなくても動くのは助かります。」
先生
「最初は簡単な関数で慣れて、あとから制約や複数の型パラメータに挑戦すると理解が深まります。ジェネリクスは使うほど意味がわかるので、どんどんコードを書いてみましょう。」
生徒
「次は、自分で色んな型を受け取る関数に挑戦してみます!」
この記事を読んだ人からの質問
プログラミング初心者からのよくある疑問/質問を解決します
Go言語のジェネリクスは、なぜ複数の型で同じ関数を使い回せるのですか?
Go言語のジェネリクスは、関数に型パラメータを持たせることで、整数や文字列だけでなく、あらゆる型を受け取れる仕組みを用意しています。従来は型ごとに別々の関数を作らなければいけませんでしたが、ジェネリクスで共通化することで、同じ処理を複数の型に使い回せます。結果として、Go言語のコードが短くなり、保守もしやすくなります。
【超入門】ゼロから始めるGo言語プログラミング:最速で「動くアプリ」を作るマンツーマン指導
「プログラミングの仕組み」が根本からわかる。Go言語でバックエンド開発の第一歩を。
本講座を受講することで、単なる文法の暗記ではなく、「プログラムがコンピュータの中でどう動いているか」という本質的な理解につながります。シンプルながら強力なGo言語(Golang)を通じて、現代のバックエンドエンジニアに求められる基礎体力を最短距離で身につけます。
具体的な開発内容と環境
【つくるもの】
ターミナル(黒い画面)上で動作する「対話型計算プログラム」や、データを整理して表示する「ミニ・ツール」をゼロから作成します。自分の書いたコードが形になる感動を体験してください。
【開発環境】
プロの現場でシェアNo.1のVisual Studio Code (VS Code)を使用します。インストールから日本語化、Go言語用の拡張機能設定まで、現場基準の環境を一緒に構築します。
この60分で得られる3つの理解
「なぜ動くのか」という設定の仕組みを理解し、今後の独学で詰まらない土台を作ります。
データの種類やメモリの概念など、他言語にも通じるプログラミングの本質を学びます。
ただ動くだけでなく、誰が見ても分かりやすい「綺麗なコード」を書くための考え方を伝授します。
※本講座は、将来的にバックエンドエンジニアやクラウドインフラに興味がある未経験者のためのエントリー講座です。マンツーマン形式により、あなたの理解度に合わせて進行します。
初めてのGo言語を一緒に学びましょう!