
こんにちは。よっしーです(^^)
本日は、Go言語の言語仕様について解説しています。
背景
Go言語を学び始めて、公式の「The Go Programming Language Specification(言語仕様書)」を開いてみたものの、「英語で書かれていて読むのが大変…」「専門用語ばかりで何を言っているのかわからない…」と感じたことはありませんか? 実は、多くのGo初心者が同じ壁にぶつかっています。
言語仕様書は、Go言語の「正式な取扱説明書」のような存在です。プログラミング言語がどのように動くのか、どんなルールで書くべきなのかが詳しく書かれていますが、その分、初めて読む人には難しく感じられるのも事実です。
そこでこの記事では、言語仕様書の導入部分を丁寧な日本語訳とともに、初心者の方でも理解しやすい補足説明を加えてお届けします。「強く型付けされている」「ガベージコレクション」「並行プログラミング」といった専門用語も、具体例を交えながらわかりやすく解説していきます。
言語仕様書は難しそうに見えますが、一つひとつの概念を丁寧に読み解いていけば、必ず理解できます。一緒に、Go言語の基礎をしっかり学んでいきましょう!
Types(型)
型は、値の集合と、それらの値に固有の操作およびメソッドを決定します。型は、型名がある場合は型名で示され、型がジェネリックである場合は型引数が続く必要があります。型は、既存の型から型を構成する型リテラルを使用して指定することもできます。
Type = TypeName [ TypeArgs ] | TypeLit | "(" Type ")" .
TypeName = identifier | QualifiedIdent .
TypeArgs = "[" TypeList [ "," ] "]" .
TypeList = Type { "," Type } .
TypeLit = ArrayType | StructType | PointerType | FunctionType | InterfaceType |
SliceType | MapType | ChannelType .
言語は特定の型名を事前宣言しています。その他は型宣言または型パラメータリストで導入されます。複合型(配列、構造体、ポインタ、関数、インターフェース、スライス、マップ、チャネル型)は、型リテラルを使用して構築できます。
事前宣言された型、定義された型、型パラメータは名前付き型と呼ばれます。エイリアスは、エイリアス宣言で指定された型が名前付き型である場合、名前付き型を示します。
解説
型とは何か?
型(Type) は、データの「種類」を表します。型によって、どんな値を入れられるか、どんな操作ができるかが決まります。
たとえ話: 型は「容器の種類」のようなものです。「整数専用の箱」「文字列専用の箱」「住所録専用の箱」など、用途に応じて異なる種類の容器があります。間違った種類のものを入れようとすると、コンパイラがエラーを出してくれます。
package main
import "fmt"
func main() {
// それぞれ異なる型
var age int = 25 // 整数型
var name string = "太郎" // 文字列型
var score float64 = 95.5 // 浮動小数点型
var isStudent bool = true // 真偽値型
fmt.Println(age, name, score, isStudent)
}
1. 型の表現方法
Goでは、型を3つの方法で表現できます。
方法1: 型名(TypeName)
事前宣言された型や、自分で定義した型を名前で指定します。
package main
import "fmt"
func main() {
// 事前宣言された型
var i int
var s string
var b bool
// 自分で定義した型
type MyInt int
var mi MyInt = 42
fmt.Println(i, s, b, mi)
}
方法2: 型リテラル(TypeLit)
複合型を直接記述します。
package main
import "fmt"
func main() {
// 配列の型リテラル
var arr [5]int
// スライスの型リテラル
var slice []int
// マップの型リテラル
var m map[string]int
// 構造体の型リテラル
var p struct {
X, Y int
}
fmt.Println(arr, slice, m, p)
}
方法3: 括弧で囲む
複雑な型を読みやすくするために括弧を使えます。
package main
func main() {
// 括弧なし
var f1 func(int) int
// 括弧あり(意味は同じ)
var f2 (func(int) int)
_ = f1
_ = f2
}
2. 事前宣言された型(Predeclared types)
Goには、最初から用意されている型があります。
真偽値型
bool // true または false
数値型
// 符号付き整数
int8, int16, int32, int64, int
// 符号なし整数
uint8, uint16, uint32, uint64, uint
// 浮動小数点
float32, float64
// 複素数
complex64, complex128
// エイリアス
byte // uint8のエイリアス
rune // int32のエイリアス
文字列型
string // 文字列
その他
error // エラーインターフェース
package main
import "fmt"
func main() {
var b bool = true
var i int = 42
var u uint = 100
var f float64 = 3.14
var c complex128 = 1 + 2i
var s string = "Hello"
var r rune = 'A'
var by byte = 255
fmt.Printf("%T %T %T %T %T %T %T %T\n", b, i, u, f, c, s, r, by)
}
3. 複合型(Composite types)
複数の値をまとめたり、特殊な構造を持つ型です。
配列(Array)
固定長の同じ型の要素の並び。
package main
import "fmt"
func main() {
// 配列の型リテラル: [長さ]要素の型
var arr1 [5]int
arr1[0] = 10
arr1[1] = 20
// 初期化付き
arr2 := [3]string{"apple", "banana", "cherry"}
// 長さ自動計算
arr3 := [...]int{1, 2, 3, 4, 5}
fmt.Println(arr1, arr2, arr3)
}
スライス(Slice)
可変長の同じ型の要素の並び。
package main
import "fmt"
func main() {
// スライスの型リテラル: []要素の型
var slice1 []int
// 初期化付き
slice2 := []string{"Go", "Python", "Java"}
// make で作成
slice3 := make([]int, 5) // 長さ5
slice4 := make([]int, 5, 10) // 長さ5、容量10
// 要素の追加
slice2 = append(slice2, "Rust")
fmt.Println(slice1, slice2, slice3, slice4)
}
マップ(Map)
キーと値のペアの集合。
package main
import "fmt"
func main() {
// マップの型リテラル: map[キーの型]値の型
var m1 map[string]int
// make で作成
m2 := make(map[string]int)
m2["apple"] = 100
m2["banana"] = 80
// 初期化付き
m3 := map[string]int{
"apple": 100,
"banana": 80,
"cherry": 120,
}
fmt.Println(m1, m2, m3)
}
構造体(Struct)
異なる型のフィールドをまとめたもの。
package main
import "fmt"
func main() {
// 構造体の型リテラル
var p1 struct {
X, Y int
}
p1.X = 10
p1.Y = 20
// 名前付き構造体(推奨)
type Point struct {
X, Y int
}
p2 := Point{X: 30, Y: 40}
fmt.Println(p1, p2)
}
ポインタ(Pointer)
他の変数のアドレスを保持する型。
package main
import "fmt"
func main() {
// ポインタの型リテラル: *型
var p1 *int
x := 42
p1 = &x // xのアドレスを取得
fmt.Println("値:", *p1) // 42
fmt.Println("アドレス:", p1) // 0xc0000...
*p1 = 100 // ポインタ経由で値を変更
fmt.Println("x:", x) // 100
}
関数(Function)
関数の型。
package main
import "fmt"
func main() {
// 関数の型リテラル: func(引数の型...) 戻り値の型
var f1 func(int, int) int
// 関数を代入
f1 = func(a, b int) int {
return a + b
}
result := f1(10, 20)
fmt.Println(result) // 30
}
インターフェース(Interface)
メソッドの集合を定義する型。
package main
import "fmt"
// インターフェースの型リテラル
type Writer interface {
Write([]byte) (int, error)
}
type MyWriter struct{}
func (mw MyWriter) Write(data []byte) (int, error) {
fmt.Println("Writing:", string(data))
return len(data), nil
}
func main() {
var w Writer = MyWriter{}
w.Write([]byte("Hello"))
}
チャネル(Channel)
並行処理でデータをやり取りする型。
package main
import "fmt"
func main() {
// チャネルの型リテラル: chan 要素の型
ch := make(chan int)
// ゴルーチンで送信
go func() {
ch <- 42
}()
// 受信
value := <-ch
fmt.Println(value) // 42
}
4. 名前付き型(Named types)
型宣言で名前を付ける
package main
import "fmt"
// 新しい型を定義
type MyInt int
type UserID int64
type Temperature float64
func main() {
var mi MyInt = 42
var uid UserID = 12345
var temp Temperature = 36.5
fmt.Printf("%T %T %T\n", mi, uid, temp)
// main.MyInt main.UserID main.Temperature
}
構造体に名前を付ける
package main
import "fmt"
// 構造体の定義
type Person struct {
Name string
Age int
City string
}
func main() {
p := Person{
Name: "太郎",
Age: 25,
City: "東京",
}
fmt.Printf("%+v\n", p)
// {Name:太郎 Age:25 City:東京}
}
5. 型エイリアス(Type alias)
既存の型に別名を付けます。
package main
import "fmt"
// 型エイリアス(=を使う)
type MyString = string
func main() {
var s1 string = "Hello"
var s2 MyString = "World"
// 同じ型として扱われる
s1 = s2
s2 = s1
fmt.Printf("%T %T\n", s1, s2) // string string
}
型定義 vs 型エイリアス
package main
import "fmt"
// 型定義(新しい型)
type MyInt1 int
// 型エイリアス(既存の型の別名)
type MyInt2 = int
func main() {
var i int = 42
var mi1 MyInt1 = 42
var mi2 MyInt2 = 42
// 型定義は異なる型
// i = mi1 // エラー! 型が違う
i = int(mi1) // 明示的な変換が必要
// 型エイリアスは同じ型
i = mi2 // OK
mi2 = i // OK
fmt.Printf("%T %T %T\n", i, mi1, mi2)
// int main.MyInt1 int
}
6. ジェネリクス(Generics)[Go 1.18+]
型パラメータを使って、複数の型に対応する関数や型を定義できます。
ジェネリック関数
package main
import "fmt"
// 型パラメータ T を持つ関数
func Print[T any](value T) {
fmt.Println(value)
}
func main() {
Print(42) // int
Print("Hello") // string
Print(3.14) // float64
Print([]int{1, 2}) // []int
}
ジェネリック型
package main
import "fmt"
// 型パラメータを持つ構造体
type Stack[T any] struct {
items []T
}
func (s *Stack[T]) Push(item T) {
s.items = append(s.items, item)
}
func (s *Stack[T]) Pop() T {
if len(s.items) == 0 {
var zero T
return zero
}
item := s.items[len(s.items)-1]
s.items = s.items[:len(s.items)-1]
return item
}
func main() {
// intのスタック
intStack := Stack[int]{}
intStack.Push(1)
intStack.Push(2)
fmt.Println(intStack.Pop()) // 2
// stringのスタック
strStack := Stack[string]{}
strStack.Push("Hello")
strStack.Push("World")
fmt.Println(strStack.Pop()) // World
}
型制約
package main
import "fmt"
// 数値型の制約
type Number interface {
int | int64 | float64
}
// 制約付きジェネリック関数
func Sum[T Number](values []T) T {
var sum T
for _, v := range values {
sum += v
}
return sum
}
func main() {
ints := []int{1, 2, 3, 4, 5}
fmt.Println(Sum(ints)) // 15
floats := []float64{1.1, 2.2, 3.3}
fmt.Println(Sum(floats)) // 6.6
}
7. 実用例
例1: ユーザー定義型
package main
import "fmt"
// ユーザー情報
type User struct {
ID int
Name string
Email string
IsActive bool
}
// メソッド
func (u User) Display() {
fmt.Printf("User: %s (ID: %d, Email: %s)\n", u.Name, u.ID, u.Email)
}
func main() {
user := User{
ID: 1,
Name: "太郎",
Email: "taro@example.com",
IsActive: true,
}
user.Display()
}
例2: 型のネスト
package main
import "fmt"
type Address struct {
City string
Country string
}
type Person struct {
Name string
Age int
Address Address // 構造体をネスト
}
func main() {
person := Person{
Name: "太郎",
Age: 25,
Address: Address{
City: "東京",
Country: "日本",
},
}
fmt.Printf("%+v\n", person)
fmt.Println(person.Address.City) // 東京
}
例3: インターフェースを使った抽象化
package main
import "fmt"
// 図形のインターフェース
type Shape interface {
Area() float64
Perimeter() float64
}
// 長方形
type Rectangle struct {
Width, Height float64
}
func (r Rectangle) Area() float64 {
return r.Width * r.Height
}
func (r Rectangle) Perimeter() float64 {
return 2 * (r.Width + r.Height)
}
// 円
type Circle struct {
Radius float64
}
func (c Circle) Area() float64 {
return 3.14159 * c.Radius * c.Radius
}
func (c Circle) Perimeter() float64 {
return 2 * 3.14159 * c.Radius
}
func PrintShapeInfo(s Shape) {
fmt.Printf("面積: %.2f, 周囲: %.2f\n", s.Area(), s.Perimeter())
}
func main() {
rect := Rectangle{Width: 10, Height: 5}
circle := Circle{Radius: 7}
PrintShapeInfo(rect)
PrintShapeInfo(circle)
}
まとめ: 型で覚えておくべきこと
型の3つの表現方法
- 型名:
int,string,MyType - 型リテラル:
[]int,map[string]int,struct{X int} - 括弧:
(func(int) int)
主要な型の分類
| 分類 | 型 |
|---|---|
| 基本型 | bool, int, float64, string |
| 複合型 | 配列、スライス、マップ、構造体 |
| 参照型 | ポインタ、スライス、マップ、チャネル |
| 関数型 | func(int) int |
| インターフェース | interface{}, カスタムインターフェース |
型の使い分け
// 基本型: シンプルなデータ
var age int = 25
// スライス: 可変長のリスト
var numbers []int = []int{1, 2, 3}
// マップ: キーと値のペア
var scores map[string]int
// 構造体: 複数のフィールドをまとめる
type Person struct {
Name string
Age int
}
// インターフェース: 抽象化
type Reader interface {
Read([]byte) (int, error)
}
実用的なアドバイス
// 1. 適切な型を選ぶ
var count int // カウンター
var price float64 // 価格
var message string // メッセージ
var isValid bool // フラグ
// 2. 構造体で関連データをまとめる
type User struct {
ID int
Name string
Email string
}
// 3. インターフェースで柔軟性を
type Writer interface {
Write([]byte) (int, error)
}
// 4. ジェネリクスで再利用性を
func Max[T comparable](a, b T) T {
if a > b {
return a
}
return b
}
型は、Goプログラミングの基礎です。適切な型を選ぶことで、安全で読みやすく、保守しやすいコードが書けます!
おわりに
本日は、Go言語の言語仕様について解説しました。

何か質問や相談があれば、コメントをお願いします。また、エンジニア案件の相談にも随時対応していますので、お気軽にお問い合わせください。
それでは、また明日お会いしましょう(^^)

コメント