Go言語入門:言語仕様 -Vol.18-

スポンサーリンク
Go言語入門:言語仕様 -Vol.18- 用語解説
Go言語入門:言語仕様 -Vol.18-
この記事は約18分で読めます。
よっしー
よっしー

こんにちは。よっしーです(^^)

本日は、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つの表現方法

  1. 型名: int, string, MyType
  2. 型リテラル: []int, map[string]int, struct{X int}
  3. 括弧: (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言語の言語仕様について解説しました。

よっしー
よっしー

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

それでは、また明日お会いしましょう(^^)

コメント

タイトルとURLをコピーしました