Skip to content
Go言語とは、Googleが開発した新しいプログラミング言語です。
当サイトではこの新しい言語についての情報を集約していきます。
このサイトの更新が滞っており、情報が古くなっておりますのでご注意ください。

Archive

Tag: Go言語仕様

Go言語仕様がアップデートしていたので日本語訳を更新しました。

【原文】The Go Programming Language Specification

以前はドキュメントにバージョンが記述されていなかったのですが、今回のは「March 25, 2010」と記述されています。

仕様の変更点のうち重要なものを簡単に説明します。

  1. 複素数型(complex, complex64, complex128)の追加
    これと関連して虚数リテラルや、組み込み関数のcmplxrealimagが追加されています。
  2. スライスのコピー機能
    組み込み関数のcopyが追加されています。
  3. ランタイムエラーの扱いの変更
    以前は「ランタイムエラー」と呼ばれていたものは、「ランタイムパニック」と呼ばれるようになりました。
    組み込み関数のpanicrecoverが追加され、try-catchとはちょっと違う風変わりなアプローチで例外処理のハンドリングができるようになりました。

The Go Programming Language Specification

The Go Programming Language Specificationの翻訳、最終回です。
すべての訳はGo言語仕様[日本語訳]にまとめてあります。


システム考察

unsafeパッケージ

組み込みパッケージunsafeは、コンパイラに実装されており、低レベルのプログラミング向けの機能を提供します。これには型システムから逸脱した機能が含まれています。そのためunsafeを使っているパッケージは、型が安全であることを手作業でよく確認しておかなくてはなりません。このパッケージでは、以下のインタフェースを提供しています。

package unsafe

type ArbitraryType int  // shorthand for an arbitrary Go type; it is not a real type
type Pointer *ArbitraryType

func Alignof(variable ArbitraryType) int
func Offsetof(selector ArbitraryType) int
func Sizeof(variable ArbitraryType) int

func Reflect(val interface {}) (typ runtime.Type, addr uintptr)
func Typeof(val interface {}) reflect.Type
func Unreflect(typ runtime.Type, addr uintptr) interface{}

すべてのポインタ、およびuintptr型の値は、Pointerに変換することができます。またその逆も可能です。

Sizeof関数は、変数を表す式を受け取り、その変数のサイズをバイト数で返します。

Offsetof関数は、構造体のフィールド表すセレクタ(§セレクタ)を受け取り、構造体のアドレスからフィールドへの相対オフセットをバイト数で返します。下は構造体sのフィールドfを使った例です。

uintptr(unsafe.Pointer(&s)) + uintptr(unsafe.Offsetof(s.f)) == uintptr(unsafe.Pointer(&s.f))

コンピュータのアーキテクチャによっては、メモリのアドレスにアライメントが必要となることがあるため、変数のアドレスは、変数の型が持つアライメントも考慮します。Alignof関数は、変数を表す式を受け取り、変数(の型)のアライメントをバイト数で返します。下は変数xを使った例です。

uintptr(unsafe.Pointer(&x)) % uintptr(unsafe.Alignof(x)) == 0

AlignofOffsetofSizeofの呼び出しは、コンパイル時にint型の定数となります。

unsafe.Typeofunsafe.Reflectunsafe.Unreflect関数は、実行時にインタフェースに格納されている動的な型および値へのアクセスを許します。Typeofは、valの動的な型を runtime.Typeとして返します。Reflectは、valの動的な値のコピーを割り当て、型とそのコピーのアドレスを返します。UnreflectReflectの逆で、型とアドレスからインタフェースの値を作成します。これらの関数を利用しているreflectパッケージでは、インタフェースの値を安全、かつより便利に調べる方法を提供しています。

サイズとアライメントの保証

数値型(§数値型)では、以下に示すサイズが保証されています。

型                        サイズ(バイト数)

byte, uint8, int8         1
uint16, int16             2
uint32, int32, float32    4
uint64, int64, float64    8

以下に示す、アライメントの特性が最低限保証されています。

  1. 変数x(型は問わない):1 <= unsafe.Alignof(x) <= unsafe.Maxalign
  2. 変数xが数値型のとき:unsafe.Alignof(x)は、 unsafe.Sizeof(x)およびunsafe.Maxalign 以下であり、少なくとも1以上である。
  3. 変数xが構造体型のとき:unsafe.Alignof(x)は、xの各フィールドをfとしたときunsafe.Alignof(x.f)の中で一番大きい値と同じであり、少なくとも1以上である。
  4. 変数xが配列型のとき:unsafe.Alignof(x)は、unsafe.Alignof(x[0])と同じであり、少なくとも1以上である。

実装との差異 – TODO

  • 実装は、gotoステートメント、および(宣言以外の)宛先に対する制約を守っていません。
  • メソッド式は未実装です。
  • Gccgoではひとつのソースファイルに、ひとつのinit()関数しか許していません。

The Go Programming Language Specificationの翻訳、13回目です。
前回までの訳はGo言語仕様[日本語訳]にまとめてあります。


プログラムの初期化と実行

ゼロ値

値を格納するため宣言や、make()new()の呼び出しによりメモリが割り当てられたとき、明示的に初期化をしなければ、そのメモリはデフォルトの初期化が行われます。このような値の要素には、それぞれその型のゼロ値がセットされます。論理値型のゼロ値はfalse、整数型は0、浮動小数点型は0.0、文字列型は""です。ポインタ、関数、インタフェース、スライス、チャネル、マップ型のゼロ値はnilです。この初期化は再帰的に行われるので、たとえば構造体が配列のとき、初期値が指定されなければ、各要素のフィールドはゼロ値となります。

次の2つの宣言は等価です。

var i int;
var i int = 0;

続いて、

type T struct { i int; f float; next *T };
t := new(T);

これは、下の値を持ちます。

t.i == 0
t.f == 0.0
t.next == nil

次も同じことが当てはまります。

var t T

プログラムの実行

インポートを伴わないパッケージの初期化は、パッケージレベルのすべての変数への初期値代入、およびソース内で定義されている次の名前とシグネチャを持ったパッケージレベルの関数の呼び出しにより行われます。

func init()

パッケージに複数のinit()関数が含まれるとき(ひとつのソースファイルに複数記述も可)は、順不同で実行されます。

パッケージ内で、パッケージレベルの変数の初期化、および定数値の決定は、それぞれのデータの依存する順で行われます。たとえばAのイニシャライザがBの値に依存するならば、Aの値はBの後に決まります。この依存関係がループしてしまうときはエラーとなります。
依存関係の分析は、辞書的かつ再帰的に行われます。Aの値がBに影響を受けている、またはAのイニシャライザがBの影響を受けている、またはAが影響を受けている関数がさらにBの影響を受けているときに、ABに依存しているとみなされます。
ある2つのアイテムがお互いに依存していなければ、それらはソース内に出現した順で初期化されます。依存関係の分析はパッケージごとに行われるため、Aのイニシャライザが、Bを参照している別のパッケージで定義されている関数を呼び出したときの結果は規定されていません。

初期化コードが、“go”ステートメントを含んでいたとしても、全プログラムの初期化が終了するまで、そのステートメントで指定した関数の実行を開始しません。したがって、すべての初期化コードは単一のゴルーチン内で動作します。

init()関数はプログラムのどこからも参照することができません。すなわち、明示的に呼び出すことも、変数にこの関数のポインタを代入することもできません。

パッケージがインポートを伴うときは、このパッケージ自身の初期化より前に、インポートされたパッケージが初期化されます。ひとつのパッケージを複数回インポートしても、そのパッケージが初期化されるのは一回だけです。

パッケージのインポートでは構造上、初期化の依存関係が循環しないことが保証されています。

完成されたプログラムは、次の関数を持つmainパッケージを持たなくてはなりません。

func main() { ... }

これは、複数のパッケージを結合して作られたとしても同様です。このmain.main()関数は、引数および戻り値を持ちません。

プログラムの実行はmainパッケージを初期化したあと、main.main()関数を実行することで開始されます。

main.main()から抜けたときに、プログラムは終了します。このとき、他(main以外)のゴルーチンの終了待ちは行いません。

実装上の制約:コンパイラはmainパッケージが、他のどのパッケージからもインポートされることはないと仮定しています。

The Go Programming Language Specificationの翻訳、12回目です。
前回までの訳はGo言語仕様[日本語訳]にまとめてあります。


パッケージ

Go言語のプログラムは、複数のパッケージをひとつにリンクすることによって作られます。さらに各パッケージは、そのパッケージに所属する定数、型、変数、関数を宣言している、ひとつ以上のソースファイルから構成されます。これらパッケージ内の要素は、同一パッケージ内であれば、別ファイルからアクセスすることができます。また要素がエクスポートされていれば、他パッケージからアクセスすることができます。

ソースファイルの構成

各ソースファイルは、そのファイルがどのパッケージに属しているかを定義するパッケージ文で始まります。以降は必須ではありませんが、ソースファイル内で使用したいパッケージを宣言する一連のインポート宣言。さらに、関数、型、変数、定数の一連の定義が続きます。

SourceFile       = PackageClause { ImportDecl [ ";" ] } { TopLevelDecl [ ";" ] } .

パッケージ文

各ソースファイルはパッケージ文で始まり、そのファイルが所属するパッケージを定めます。

PackageClause  = "package" PackageName .
PackageName    = identifier .

このパッケージ名(PackageName)はブランク識別子であってはなりません。

package math

パッケージの実装は、同じパッケージ名を共有するファイル群によって構成されます。実装によっては、同一パッケージ内のすべてのソースファイルが、同一ディレクトリ内に置かれている必要があります。

インポート宣言

インポート宣言によって、インポートされたパッケージ内でエクスポートされている識別子を使うことで、インポート宣言を記述しているソースファイルから、それらにアクセス可能になります。インポートでは、アクセスするために使用する識別子(PackageName)、およびインポートされるパッケージを指定するImportPathを指定します。

ImportDecl       = "import" ( ImportSpec | "(" [ ImportSpecList ] ")" ) .
ImportSpecList   = ImportSpec { ";" ImportSpec } [ ";" ] .
ImportSpec       = [ "." | PackageName ] ImportPath .
ImportPath       = StringLit .

PackageNameは、限定付き識別子を使い、そのパッケージでエクスポートされている識別子にアクセスするために使用されます。PackageNameは、ファイルブロック内で宣言されます。PackageNameを省略したときは、インポートされた側パッケージのパッケージ文で指定されている識別子がデフォルトとして使用されます。名前の代わりにピリオド(.)が指定されたときは、そのパッケージでエクスポートされている全識別子が、カレントのファイルのファイルブロックで宣言され、プレフィックスなしでアクセスできるようになります。

ImportPathの解釈は実装に依存しますが、一般的には、コンパイル済みパッケージのパス名の一部であり、パッケージのリポジトリへの相対パスです。

仮に、Sin関数をエクスポートしているmathパッケージがあり、"lib/math"というパスにそのコンパイル済みパッケージがインストールされているとします。下の表では、各インポート方法でこのパッケージをインポートしたとき、そのファイル内でSin関数がどのようにしてアクセスされるかを説明します。

インポート宣言                Sinのローカル名

import   "lib/math"         math.Sin
import M "lib/math"         M.Sin
import . "lib/math"         Sin

インポート宣言は、インポート「する側」と「される側」の依存関係を宣言します。自分自身のパッケージをインポートすること、またはインポートしたパッケージ内でエクスポートされている識別子を一切参照しないことは誤った使い方です。インポートによる副作用(初期化)のためだけにパッケージをインポートするときは、パッケージ名としてブランク識別子を使ってください。

import _ "lib/math"

パッケージサンプル

これは、並列処理による「素数のふるい」を実装した、Goのパッケージ一式です。

package main

import "fmt"

// 2,3,4,...というシーケンスをチャネル'ch'に送信
func generate(ch chan<- int) {
	for i := 2; ; i++ {
		ch <- i;	// 'i'をチャネル'ch'に送信
	}
}

// チャネル'in'の値をチャネル'out'にコピー
// ただし'prime'で割り切れる値を除く
func filter(src <-chan int, dst chan<- int, prime int) {
	for i := range src {	// 'src'から受信した値でループ
		if i%prime != 0 {
			dst <- i;	// 'i'をチャネル'dst'に送信
		}
	}
}

// 素数のふるい:フィルターを数珠つなぎに組み合わせて処理する
func sieve() {
	ch := make(chan int);	// 新しいチャネルを作成
	go generate(ch);	// generate()をサブプロセスとして開始
	for {
		prime := <-ch;
		fmt.Print(prime, "\n");
		ch1 := make(chan int);
		go filter(ch, ch1, prime);
		ch = ch1;
	}
}

func main() {
	sieve();
}

The Go Programming Language Specificationの翻訳、11回目です。
前回までの訳はGo言語仕様[日本語訳]にまとめてあります。


組み込み関数

組み込み関数として、いくつかの関数が事前宣言済みとなっています。組み込み関数の呼び出しは他の関数と同様ですが、いくつかの関数では、第一引数に「式」ではなく「型」を受け取ります。

BuiltinCall = identifier "(" [ BuiltinArgs ] ")" .
BuiltinArgs = Type [ "," ExpressionList ] | ExpressionList .

close、closed

cをチャネルと仮定したとき、事前宣言済み関数close(c)は、これ以上の値を送信操作により受け付けないようチャネルをマーキングします。closeより前に送信された値を受信したあとは、受信操作はそのチャネル型のゼロ値を返すようになります。このようにゼロ値が受信されたあとは、closed(c)trueを返すようになります。

長さ、キャパシティ

組み込み関数lencapは、様々な型の引数を取り、int型の結果を返します。実装上、これらが返す結果が常にintと適合することが保証されています。

呼び出し   引数の型              戻り値

len(s)    文字列型              文字列のバイト長
          [n]T, *[n]T          配列の長さ(== n)
          []T                  スライスの長さ
          map[K]T              マップの長さ(定義されているキーの数)
          chan T               チャネルのバッファ内でキューイングされている要素数

cap(s)    [n]T, *[n]T          配列の長さ(== n)
          []T                  スライスのキャパシティ
          chan T               チャネルのバッファのキャパシティ

スライスのキャパシティは、根底にある配列に割り当てられている要素数です。これは常に、下の関係が保たれます。

0 <= len(s) <= cap(s)

メモリの割当

組み込み関数newは型Tを取って、型*Tの値を返します。このときメモリの内容は初期値のセクション(§ゼロ値)で記述されているように初期化されます。

new(T)

例です。

type S struct { a int; b float }
new(S)

この例では、型Sの変数に対してメモリを動的に割り当て、初期化(a=0, b=0.0)したのち、そのメモリのアドレスを持つ、型*Sの値を返します。

スライス、マップ、チャネルの作成

スライス、マップ、チャネルは、newによる間接的なメモリ割り当てを必要としない、参照型です。
組み込み関数makeは、型Tを取ります。このTはスライス、マップ、チャネル型でなければなりません。また、オプションとして式のリスト(作成する種類によって異なる)を取ります。makeは型T(*Tではない)を返します。このときメモリの内容は初期値のセクション(§ゼロ値)で記述されているように初期化されます。

make(T [, 式のリスト(オプション)])

例です。

make(map[string] int)

この例では、新しいマップの値を作成し、空のマップとして初期化します。

パラメータの値によって、スライス、マップ、バッファリングされたチャネルの割り当てサイズが変更できます。

s := make([]int, 10, 100);        // len(s) == 10, cap(s) == 100のスライス
s := make([]int, 10);             // len(s) == cap(s) == 10のスライス
c := make(chan int, 10);          // バッファサイズ10のチャネル
m := make(map[string] int, 100);  // 100要素を初期容量として持つマップ

ブートストラッピング

現在の実装では、ブートストラッピング(Go言語コンパイラ自身のコンパイル)に役立つ、いくつかの組み込み関数を提供しています。これらの関数は文書化されていますが、このまま言語に残されるかどうかは保証されません。また、これらの関数は結果を返しません。

関数        振舞い

print      全引数を出力する(各フォーマットは実装依存)
println    printと同じだが、各引数間にスペース、および最後に改行を出力する
panic      printと同じだが、出力後に実行を中断する
panicln    printlnと同じだが、出力後に実行を中断する