式
式は演算子と関数をオペランドに適用することによって値の計算を指定します。
オペランド
オペランドは式の基本的な値を表します。オペランドは、リテラル、(場合によっては修飾された)空白でない識別子(定数、変数、または関数を示す)、あるいは括弧で囲まれた式です。
Operand = Literal | OperandName [ TypeArgs ] | "(" Expression ")" .
Literal = BasicLit | CompositeLit | FunctionLit .
BasicLit = int_lit | float_lit | imaginary_lit | rune_lit | string_lit .
OperandName = identifier | QualifiedIdent .
ジェネリック関数を示すオペランド名の後には型引数のリストが続くことがあります。結果として得られるオペランドはインスタンス化された関数です。
空白識別子は、代入文の左辺においてのみオペランドとして表示されることがあります。
実装上の制約:コンパイラは、オペランドの型が空の型セットを持つ型パラメータである場合にエラーを報告する必要はありません。そのような型パラメータを持つ関数はインスタンス化できません。インスタンス化の試みはインスタンス化のサイトでエラーになります。
修飾識別子
修飾識別子はパッケージ名の接頭辞で修飾された識別子です。パッケージ名と識別子の両方が空白であってはなりません。
QualifiedIdent = PackageName "." identifier .
修飾識別子は、異なるパッケージの識別子にアクセスします。そのパッケージはインポートされている必要があります。識別子はエクスポートされており、そのパッケージのパッケージブロックで宣言されている必要があります。
math.Sin // mathパッケージのSin関数を表す
複合リテラル
複合リテラルは、評価されるたびに構造体、配列、スライス、およびマップの新しい値を構築します。これらは、リテラルの型の後に中括弧で囲まれた要素のリストが続くものです。各要素の前に、対応するキーをオプションで付けることができます。
CompositeLit = LiteralType LiteralValue .
LiteralType = StructType | ArrayType | "[" "..." "]" ElementType |
SliceType | MapType | TypeName [ TypeArgs ] .
LiteralValue = "{" [ ElementList [ "," ] ] "}" .
ElementList = KeyedElement { "," KeyedElement } .
KeyedElement = [ Key ":" ] Element .
Key = FieldName | Expression | LiteralValue .
FieldName = identifier .
Element = Expression | LiteralValue .
LiteralTypeが型パラメータでない限り、その基底型は構造体、配列、スライス、またはマップ型でなければなりません(型がTypeNameとして与えられる場合を除き、構文はこの制約を強制します)。LiteralTypeが型パラメータである場合、その型セット内のすべての型は同じ基底型を持ち、それは有効な複合リテラル型でなければなりません。要素とキーの型は、型T
の各フィールド、要素、およびキーの型に代入可能でなければなりません。追加の変換はありません。キーは構造体リテラルではフィールド名、配列とスライスリテラルではインデックス、マップリテラルではキーとして解釈されます。マップリテラルの場合、すべての要素にキーが必要です。同じフィールド名または定数キー値を持つ複数の要素を指定するとエラーになります。非定数のマップキーについては、評価順序のセクションを参照してください。
構造体リテラルには以下のルールが適用されます:
- キーは構造体型で宣言されたフィールド名でなければなりません。
- キーを含まない要素リストは、フィールドが宣言されている順序で各構造体フィールドの要素をリストしなければなりません。
- いずれかの要素にキーがある場合、すべての要素にキーが必要です。
- キーを含む要素リストは、各構造体フィールドに要素を持つ必要はありません。省略されたフィールドはそのフィールドのゼロ値になります。
- リテラルは要素リストを省略することができます。そのようなリテラルはその型のゼロ値として評価されます。
- 異なるパッケージに属する構造体の非エクスポートフィールドに要素を指定するとエラーになります。
以下の宣言があるとします:
type Point3D struct { x, y, z float64 }
type Line struct { p, q Point3D }
次のように書くことができます:
origin := Point3D{} // Point3Dのゼロ値
line := Line{origin, Point3D{y: -4, z: 12.3}} // line.q.xのゼロ値
配列とスライスリテラルには以下のルールが適用されます:
- 各要素には、配列内の位置を示す関連する整数インデックスがあります。
- キーを持つ要素はそのキーをインデックスとして使用します。キーは
int
型の値で表現可能な非負の定数でなければなりません。型付けされている場合は整数型でなければなりません。 - キーを持たない要素は、前の要素のインデックスに1を加えたものを使用します。最初の要素にキーがない場合、そのインデックスは0です。
複合リテラルのアドレスを取得すると、そのリテラルの値で初期化された一意の変数へのポインタが生成されます。
var pointer *Point3D = &Point3D{y: 1000}
スライスまたはマップ型のゼロ値は、同じ型の初期化されたが空の値と同じではないことに注意してください。したがって、空のスライスやマップの複合リテラルのアドレスを取得することは、newで新しいスライスやマップ値を割り当てるのと同じ効果はありません。
p1 := &[]int{} // p1は初期化された空のスライスを指し、値は[]int{}で長さは0
p2 := new([]int) // p2は初期化されていないスライスを指し、値はnilで長さは0
配列リテラルの長さはリテラル型で指定された長さです。リテラルに長さより少ない要素が提供されている場合、不足している要素は配列要素型のゼロ値に設定されます。配列のインデックス範囲外のインデックス値を持つ要素を提供するとエラーになります。表記法...
は、最大要素インデックスに1を加えた配列の長さを指定します。
buffer := [10]string{} // len(buffer) == 10
intSet := [6]int{1, 2, 3, 5} // len(intSet) == 6
days := [...]string{"Sat", "Sun"} // len(days) == 2
スライスリテラルは基礎となる配列リテラル全体を記述します。したがって、スライスリテラルの長さと容量は最大要素インデックスに1を加えたものです。スライスリテラルの形式は次のとおりです:
[]T{x1, x2, … xn}
これは配列に適用されるスライス操作の省略形です:
tmp := [n]T{x1, x2, … xn}
tmp[0 : n]
型T
の配列、スライス、またはマップ型の複合リテラル内で、自身が複合リテラルである要素またはマップキーは、T
の要素またはキー型と同一である場合、それぞれのリテラル型を省略することができます。同様に、複合リテラルのアドレスである要素またはキーは、要素またはキー型が*T
である場合、&T
を省略することができます。
[...]Point{{1.5, -3.5}, {0, 0}} // [...]Point{Point{1.5, -3.5}, Point{0, 0}}と同じ
[][]int{{1, 2, 3}, {4, 5}} // [][]int{[]int{1, 2, 3}, []int{4, 5}}と同じ
[][]Point{{{0, 1}, {1, 2}}} // [][]Point{[]Point{Point{0, 1}, Point{1, 2}}}と同じ
map[string]Point{"orig": {0, 0}} // map[string]Point{"orig": Point{0, 0}}と同じ
map[Point]string{{0, 0}: "orig"} // map[Point]string{Point{0, 0}: "orig"}と同じ
type PPoint *Point
[2]*Point{{1.5, -3.5}, {}} // [2]*Point{&Point{1.5, -3.5}, &Point{}}と同じ
[2]PPoint{{1.5, -3.5}, {}} // [2]PPoint{PPoint(&Point{1.5, -3.5}), PPoint(&Point{})}と同じ
LiteralTypeのTypeNameフォームを使用する複合リテラルが"if"、“for”、または"switch"文のキーワードとブロックの開始中括弧の間のオペランドとして表示され、複合リテラルが括弧、角括弧、または中括弧で囲まれていない場合、構文解析の曖昧さが生じます。このまれなケースでは、リテラルの開始中括弧が文のブロックを導入するものとして誤って解析されます。この曖昧さを解決するために、複合リテラルは括弧内に表示する必要があります。
if x == (T{a,b,c}[i]) { … }
if (x == T{a,b,c}[i]) { … }
有効な配列、スライス、およびマップリテラルの例:
// 素数のリスト
primes := []int{2, 3, 5, 7, 9, 2147483647}
// vowels[ch]はchが母音の場合trueになる
vowels := [128]bool{'a': true, 'e': true, 'i': true, 'o': true, 'u': true, 'y': true}
// 配列[10]float32{-1, 0, 0, 0, -0.1, -0.1, 0, 0, 0, -1}
filter := [10]float32{-1, 4: -0.1, -0.1, 9: -1}
// 平均律の周波数(Hz)(A4 = 440Hz)
noteFrequency := map[string]float32{
"C0": 16.35, "D0": 18.35, "E0": 20.60, "F0": 21.83,
"G0": 24.50, "A0": 27.50, "B0": 30.87,
}
関数リテラル
関数リテラルは匿名の関数を表します。関数リテラルは型パラメータを宣言できません。
FunctionLit = "func" Signature FunctionBody .
func(a, b int, z float64) bool { return a*b < int(z) }
関数リテラルは変数に代入したり、直接呼び出したりすることができます。
f := func(x, y int) int { return x + y }
func(ch chan int) { ch <- ACK }(replyChan)
関数リテラルはクロージャです:周囲の関数で定義された変数を参照することができます。これらの変数は、周囲の関数と関数リテラルの間で共有され、アクセス可能な限り存続します。
主式
主式は単項式および二項式のオペランドです。
PrimaryExpr = Operand |
Conversion |
MethodExpr |
PrimaryExpr Selector |
PrimaryExpr Index |
PrimaryExpr Slice |
PrimaryExpr TypeAssertion |
PrimaryExpr Arguments .
Selector = "." identifier .
Index = "[" Expression [ "," ] "]" .
Slice = "[" [ Expression ] ":" [ Expression ] "]" |
"[" [ Expression ] ":" Expression ":" Expression "]" .
TypeAssertion = "." "(" Type ")" .
Arguments = "(" [ ( ExpressionList | Type [ "," ExpressionList ] ) [ "..." ] [ "," ] ] ")" .
x
2
(s + ".txt")
f(3.1415, true)
Point{1, 2}
m["foo"]
s[i : j + 1]
obj.color
f.p[i].x()
セレクタ
x.f
は値x
(または場合によっては*x
、下記参照)のフィールドまたはメソッドf
を示します。識別子f
は(フィールドまたはメソッドの)セレクタと呼ばれ、空白識別子であってはなりません。セレクタ式の型はf
の型です。x
がパッケージ名の場合は、修飾識別子のセクションを参照してください。
セレクタf
は型T
のフィールドまたはメソッドf
を示すか、T
の入れ子の埋め込みフィールドのフィールドまたはメソッドf
を参照することができます。f
に到達するために通過する埋め込みフィールドの数は、T
の深さと呼ばれます。T
で宣言されたフィールドまたはメソッドf
の深さはゼロです。T
の埋め込みフィールドA
で宣言されたフィールドまたはメソッドf
の深さは、A
におけるf
の深さに1を加えたものです。
セレクタには以下のルールが適用されます:
ポインタまたはインターフェース型ではない型
T
または*T
の値x
に対して、x.f
はT
の最も浅い深さにあるフィールドまたはメソッドf
を示します。最も浅い深さに一意的なf
が存在しない場合、セレクタ式は不正です。インターフェース型
I
の値x
に対して、x.f
はx
の動的な値の名前f
を持つ実際のメソッドを示します。I
のメソッドセットに名前f
のメソッドがない場合、セレクタ式は不正です。例外として、
x
の型が定義されたポインタ型であり、(*x).f
がフィールド(ただしメソッドではない)を示す有効なセレクタ式である場合、x.f
は(*x).f
の省略形です。他のすべての場合において、
x.f
は不正です。x
がポインタ型で値がnil
であり、x.f
が構造体フィールドを示す場合、x.f
への代入または評価は実行時パニックを引き起こします。
例えば、以下の宣言がある場合:
type T0 struct {
x int
}
func (*T0) M0()
type T1 struct {
y int
}
func (T1) M1()
type T2 struct {
z int
T1
*T0
}
func (*T2) M2()
type Q *T2
var t T2 // t.T0 != nilの場合
var p *T2 // p != nilかつ(*p).T0 != nilの場合
var q Q = p
次のように書くことができます:
t.z // t.z
t.y // t.T1.y
t.x // (*t.T0).x
p.z // (*p).z
p.y // (*p).T1.y
p.x // (*(*p).T0).x
q.x // (*(*q).T0).x (*q).xは有効なフィールドセレクタ
p.M0() // ((*p).T0).M0() M0は*T0レシーバーを期待する
p.M1() // ((*p).T1).M1() M1はT1レシーバーを期待する
p.M2() // p.M2() M2は*T2レシーバーを期待する
t.M2() // (&t).M2() M2は*T2レシーバーを期待する、呼び出しのセクションを参照
しかし、以下は無効です:
q.M0() // (*q).M0は有効だがフィールドセレクタではない
メソッド式
M
が型T
のメソッドセットにある場合、T.M
は通常の関数として呼び出し可能な関数であり、M
と同じ引数を持ちますが、メソッドのレシーバーである追加の引数が先頭に付きます。
MethodExpr = ReceiverType "." MethodName .
ReceiverType = Type .
2つのメソッドを持つ構造体型T
を考えてみましょう。T
型のレシーバーを持つMv
と、*T
型のレシーバーを持つMp
です。
type T struct {
a int
}
func (tv T) Mv(a int) int { return 0 } // 値レシーバー
func (tp *T) Mp(f float32) float32 { return 1 } // ポインタレシーバー
var t T
式:
T.Mv
はMv
と同等の関数を生成しますが、最初の引数として明示的なレシーバーを持ちます。その関数のシグネチャは次のとおりです:
func(tv T, a int) int
この関数は明示的なレシーバーで通常どおり呼び出すことができるため、以下の5つの呼び出しは同等です:
t.Mv(7)
T.Mv(t, 7)
(T).Mv(t, 7)
f1 := T.Mv; f1(t, 7)
f2 := (T).Mv; f2(t, 7)
同様に、式:
(*T).Mp
は次のシグネチャを持つMp
を表す関数値を生成します:
func(tp *T, f float32) float32
値レシーバーを持つメソッドの場合、明示的なポインタレシーバーを持つ関数を導出できるため:
(*T).Mv
は次のシグネチャを持つMv
を表す関数値を生成します:
func(tv *T, a int) int
このような関数は、レシーバーを通じて間接参照して、基礎となるメソッドに渡すレシーバーとして値を作成します。メソッドは、関数呼び出しで渡されるアドレスの値を上書きしません。
最後のケース、ポインタレシーバーメソッドの値レシーバー関数は、ポインタレシーバーメソッドが値型のメソッドセットに含まれないため不正です。
メソッドから派生した関数値は関数呼び出し構文で呼び出されます。レシーバーは呼び出しの最初の引数として提供されます。つまり、f := T.Mv
が与えられた場合、f
はt.f(7)
ではなくf(t, 7)
として呼び出されます。レシーバーをバインドする関数を構築するには、関数リテラルまたはメソッド値を使用してください。
インターフェース型のメソッドから関数値を導出することは合法です。結果として得られる関数は、そのインターフェース型の明示的なレシーバーを取ります。
メソッド値
式x
が静的型T
を持ち、M
が型T
のメソッドセットにある場合、x.M
はメソッド値と呼ばれます。メソッド値x.M
は、x.M
のメソッド呼び出しと同じ引数で呼び出し可能な関数値です。式x
はメソッド値の評価中に評価され保存されます。保存されたコピーは、後で実行される可能性のある呼び出しでレシーバーとして使用されます。
type S struct { *T }
type T int
func (t T) M() { print(t) }
t := new(T)
s := S{T: t}
f := t.M // レシーバー*tが評価されfに格納される
g := s.M // レシーバー*(s.T)が評価されgに格納される
*t = 42 // fとgに格納されたレシーバーには影響しない
型T
はインターフェース型または非インターフェース型です。
上記のメソッド式の議論と同様に、2つのメソッドを持つ構造体型T
を考えてみましょう。T
型のレシーバーを持つMv
と、*T
型のレシーバーを持つMp
です。
type T struct {
a int
}
func (tv T) Mv(a int) int { return 0 } // 値レシーバー
func (tp *T) Mp(f float32) float32 { return 1 } // ポインタレシーバー
var t T
var pt *T
func makeT() T
式:
t.Mv
は次の型の関数値を生成します:
func(int) int
これら2つの呼び出しは同等です:
t.Mv(7)
f := t.Mv; f(7)
同様に、式:
pt.Mp
は次の型の関数値を生成します:
func(float32) float32
セレクタと同様に、ポインタを使用した値レシーバーを持つ非インターフェースメソッドへの参照は、そのポインタを自動的に逆参照します:pt.Mv
は(*pt).Mv
と同等です。
メソッド呼び出しと同様に、アドレス指定可能な値を使用したポインタレシーバーを持つ非インターフェースメソッドへの参照は、その値のアドレスを自動的に取得します:t.Mp
は(&t).Mp
と同等です。
f := t.Mv; f(7) // t.Mv(7)のような
f := pt.Mp; f(7) // pt.Mp(7)のような
f := pt.Mv; f(7) // (*pt).Mv(7)のような
f := t.Mp; f(7) // (&t).Mp(7)のような
f := makeT().Mp // 無効:makeT()の結果はアドレス指定不可能
上記の例では非インターフェース型を使用していますが、インターフェース型の値からメソッド値を作成することも合法です。
var i interface { M(int) } = myVal
f := i.M; f(7) // i.M(7)のような
インデックス式
以下の形式の主式
a[x]
は配列、配列へのポインタ、スライス、文字列またはマップ a
の x
によってインデックス付けされた要素を表します。
値 x
は、それぞれ インデックス または マップキー と呼ばれます。
以下のルールが適用されます:
a
がマップでも型パラメータでもない場合:
- インデックス
x
は型付けされていない定数であるか、その型が整数または整数型のみを含む型集合を持つ型パラメータでなければなりません - 定数インデックスは非負であり、
int
型の値として表現可能でなければなりません - 型付けされていない定数インデックスには
int
型が与えられます - インデックス
x
は0 <= x < len(a)
の場合に 範囲内 であり、それ以外の場合は 範囲外 です
a
が配列型 A
の場合:
a
が配列型へのポインタの場合:
a[x]
は(*a)[x]
の省略形です
a
がスライス型 S
の場合:
x
が実行時に範囲外の場合、実行時パニックが発生しますa[x]
はインデックスx
のスライス要素であり、a[x]
の型はS
の要素型です
a
が文字列型の場合:
- 文字列
a
も定数である場合、定数インデックスは範囲内でなければなりません x
が実行時に範囲外の場合、実行時パニックが発生しますa[x]
はインデックスx
の非定数バイト値であり、a[x]
の型はbyte
ですa[x]
に代入することはできません
a
がマップ型 M
の場合:
x
の型はM
のキー型に代入可能でなければなりません- マップにキー
x
のエントリが含まれる場合、a[x]
はキーx
を持つマップ要素であり、a[x]
の型はM
の要素型です - マップが
nil
であるか、そのようなエントリを含まない場合、a[x]
はM
の要素型のゼロ値です
a
が型パラメータ型 P
の場合:
- インデックス式
a[x]
はP
の型集合内のすべての型の値に対して有効でなければなりません P
の型集合内のすべての型の要素型は同一でなければなりません。この文脈では、文字列型の要素型はbyte
ですP
の型集合にマップ型がある場合、その型集合内のすべての型はマップ型でなければならず、各キー型はすべて同一でなければなりませんa[x]
はP
がインスタンス化された型引数のインデックスx
での配列、スライス、または文字列要素、あるいはキーx
を持つマップ要素であり、a[x]
の型は(同一の)要素型の型ですP
の型集合に文字列型が含まれる場合、a[x]
に代入することはできません
それ以外の場合、a[x]
は不正です。
型 map[K]V
のマップ a
に対するインデックス式が、以下の特殊な形式の代入文または初期化で使用される場合:
v, ok = a[x]
v, ok := a[x]
var v, ok = a[x]
追加の型付けされていないブール値が生成されます。ok
の値は、キー x
がマップに存在する場合は true
、それ以外の場合は false
です。
nil
マップの要素への代入は実行時パニックを引き起こします。
スライス式
スライス式は文字列、配列、配列へのポインタ、またはスライスのオペランドから部分文字列またはスライスを構築します。 2つの変種があります:下限と上限を指定する単純な形式と、容量の制限も指定する完全な形式です。
オペランド型が型パラメータである場合、文字列型を含む型集合でない限り、
型集合内のすべての型は同じ基底型を持つ必要があり、スライス式はその型のオペランドに対して有効でなければなりません。
型集合に文字列型が含まれる場合、基底型が []byte
のバイトスライスも含むことがあります。
この場合、スライス式は string
型のオペランドに対して有効でなければなりません。
単純なスライス式
文字列、配列、配列へのポインタ、またはスライス a
に対して、主式
a[low : high]
は部分文字列またはスライスを構築します。
インデックス low
と high
はオペランド a
のどの要素が結果に現れるかを選択します。
結果は0から始まるインデックスを持ち、長さは high
- low
に等しくなります。
配列 a
をスライスした後
a := [5]int{1, 2, 3, 4, 5}
s := a[1:4]
スライス s
は型 []int
、長さ3、容量4を持ち、要素は以下の通りです
s[0] == 2
s[1] == 3
s[2] == 4
便宜上、いずれのインデックスも省略できます。省略された low
インデックスはデフォルトで0になります。省略された high
インデックスはデフォルトでスライスされるオペランドの長さになります:
a[2:] // a[2 : len(a)]と同じ
a[:3] // a[0 : 3]と同じ
a[:] // a[0 : len(a)]と同じ
a
が配列へのポインタである場合、a[low : high]
は (*a)[low : high]
の省略形です。
配列または文字列の場合、インデックスは 0
<= low
<= high
<= len(a)
の場合に 範囲内 であり、それ以外の場合は 範囲外 です。
スライスの場合、上限インデックスは長さではなくスライスの容量 cap(a)
です。
定数インデックスは非負であり、int
型の値として表現可能でなければなりません。配列または定数文字列の場合、定数インデックスも範囲内でなければなりません。
両方のインデックスが定数である場合、low <= high
を満たす必要があります。
インデックスが実行時に範囲外である場合、実行時パニックが発生します。
型付けされていない文字列を除き、スライス操作のオペランドが文字列またはスライスである場合、
スライス操作の結果はオペランドと同じ型の非定数値です。
型付けされていない文字列オペランドの場合、結果は型 string
の非定数値です。
スライス操作のオペランドが配列である場合、それはアドレス指定可能でなければならず、
スライス操作の結果は配列と同じ要素型を持つスライスです。
有効なスライス式のスライス操作のオペランドが nil
スライスである場合、結果は
nil
スライスです。それ以外の場合、結果がスライスであれば、オペランドと基底配列を共有します。
var a [10]int
s1 := a[3:7] // s1の基底配列は配列a; &s1[2] == &a[5]
s2 := s1[1:4] // s2の基底配列はs1の基底配列である配列a; &s2[1] == &a[5]
s2[1] = 42 // s2[1] == s1[2] == a[5] == 42; すべて同じ基底配列要素を参照
var s []int
s3 := s[:0] // s3 == nil
完全なスライス式
配列、配列へのポインタ、またはスライス a
(ただし文字列ではない)に対して、主式
a[low : high : max]
は同じ型、同じ長さ、そして単純なスライス式 a[low : high]
と同じ要素を持つスライスを構築します。
さらに、結果のスライスの容量を max - low
に設定することでコントロールします。
最初のインデックスのみ省略可能で、デフォルトは0です。
配列 a
をスライスした後
a := [5]int{1, 2, 3, 4, 5}
t := a[1:3:5]
スライス t
は型 []int
、長さ2、容量4を持ち、要素は以下の通りです
t[0] == 2
t[1] == 3
単純なスライス式と同様に、a
が配列へのポインタである場合、
a[low : high : max]
は (*a)[low : high : max]
の省略形です。
スライス操作のオペランドが配列である場合、それはアドレス指定可能でなければなりません。
インデックスは 0 <= low <= high <= max <= cap(a)
の場合に 範囲内 であり、
それ以外の場合は 範囲外 です。
定数インデックスは非負であり、
int
型の値として表現可能でなければなりません。配列の場合、定数インデックスも範囲内でなければなりません。
複数のインデックスが定数である場合、存在する定数は互いに相対的に範囲内でなければなりません。
インデックスが実行時に範囲外である場合、実行時パニックが発生します。
型アサーション
インターフェース型の式 x
(ただし型パラメータではない)と型 T
に対して、主式
x.(T)
は x
が nil
でなく、x
に格納された値が型 T
であることをアサートします。
表記 x.(T)
は 型アサーション と呼ばれます。
より正確には、T
がインターフェース型でない場合、x.(T)
は x
の動的型が型 T
と同一であることをアサートします。
この場合、T
は x
の(インターフェース)型を実装しなければなりません。
そうでなければ、x
が型 T
の値を格納することは不可能なため、型アサーションは無効です。
T
がインターフェース型である場合、x.(T)
は x
の動的型がインターフェース T
を実装していることをアサートします。
型アサーションが成立する場合、式の値は x
に格納された値であり、その型は T
です。
型アサーションが偽である場合、実行時パニックが発生します。
言い換えれば、x
の動的型が実行時にのみ知られるにもかかわらず、正しいプログラムでは x.(T)
の型は T
であることが分かっています。
var x interface{} = 7 // xは動的型intと値7を持つ
i := x.(int) // iは型intと値7を持つ
type I interface { m() }
func f(y I) {
s := y.(string) // 不正:stringはI(メソッドmがない)を実装していない
r := y.(io.Reader) // rは型io.Readerを持ち、yの動的型はIとio.Readerの両方を実装しなければならない
…
}
以下の特殊な形式の代入文または初期化で使用される型アサーション
v, ok = x.(T)
v, ok := x.(T)
var v, ok = x.(T)
var v, ok interface{} = x.(T) // vとokの動的型はそれぞれTとbool
は追加の型付けされていないブール値を生成します。ok
の値は、アサーションが成立する場合に true
です。
そうでない場合は false
であり、v
の値は型 T
のゼロ値です。
この場合、実行時パニックは発生しません。
関数呼び出し
関数型 F
の式 f
に対して、
f(a1, a2, … an)
は引数 a1, a2, … an
で f
を呼び出します。
1つの特殊なケースを除いて、引数は単一値の式であり、F
のパラメータ型に代入可能でなければならず、関数が呼び出される前に評価されます。
式の型は F
の結果型です。
メソッド呼び出しも同様ですが、メソッド自体はメソッドのレシーバー型の値に対するセレクタとして指定されます。
math.Atan2(x, y) // 関数呼び出し
var pt *Point
pt.Scale(3.5) // レシーバーptを持つメソッド呼び出し
f
がジェネリック関数を示す場合、呼び出されるか関数値として使用される前にインスタンス化されなければなりません。
f
の型が型パラメータである場合、その型集合内のすべての型は同じ基底型を持つ必要があり、それは関数型でなければならず、関数呼び出しはその型に対して有効でなければなりません。
関数呼び出しでは、関数値と引数は通常の順序で評価されます。 評価後、関数の変数(パラメータと結果を含む)のための新しいストレージが割り当てられます。 次に、呼び出しの引数が関数に 渡され ます。これは、対応する関数パラメータに代入されることを意味し、呼び出された関数の実行が開始されます。 関数の戻りパラメータは、関数が戻るときに呼び出し元に渡されます。
nil
関数値の呼び出しは実行時パニックを引き起こします。
特殊なケースとして、関数またはメソッド g
の戻り値が、別の関数またはメソッド f
のパラメータと数が等しく個別に代入可能である場合、呼び出し f(g(parameters_of_g))
は g
の戻り値を順番に f
のパラメータに渡した後に f
を呼び出します。
f
の呼び出しには g
の呼び出し以外のパラメータを含めることはできず、g
は少なくとも1つの戻り値を持つ必要があります。
f
に最終的な ...
パラメータがある場合、通常のパラメータの割り当て後に残る g
の戻り値が割り当てられます。
func Split(s string, pos int) (string, string) {
return s[0:pos], s[pos:]
}
func Join(s, t string) string {
return s + t
}
if Join(Split(value, len(value)/2)) != value {
log.Panic("test fails")
}
メソッド呼び出し x.m()
は、(型の)x
のメソッドセットに m
が含まれ、引数リストが m
のパラメータリストに代入可能である場合に有効です。
x
がアドレス指定可能であり、&x
のメソッドセットに m
が含まれる場合、x.m()
は (&x).m()
の省略形です:
var p Point
p.Scale(3.5)
メソッド型とメソッドリテラルは存在しません。
...
パラメータへの引数の渡し方
f
が最終パラメータ p
が型 ...T
である可変引数である場合、f
内では p
の型は型 []T
と同等です。
f
が p
に対する実際の引数なしで呼び出された場合、p
に渡される値は nil
です。
それ以外の場合、渡される値は型 []T
の新しいスライスであり、その要素が実際の引数である新しい基底配列を持ちます。これらはすべて T
に代入可能でなければなりません。
したがって、スライスの長さと容量は p
に束縛された引数の数であり、各呼び出しサイトで異なる場合があります。
関数と呼び出しが与えられた場合:
func Greeting(prefix string, who ...string)
Greeting("nobody")
Greeting("hello:", "Joe", "Anna", "Eileen")
Greeting
内では、who
は最初の呼び出しでは nil
の値を持ち、2番目の呼び出しでは []string{"Joe", "Anna", "Eileen"}
の値を持ちます。
最後の引数がスライス型 []T
に代入可能であり、それに ...
が続く場合、それは ...T
パラメータの値として変更なしに渡されます。この場合、新しいスライスは作成されません。
スライス s
と呼び出しが与えられた場合:
s := []string{"James", "Jasmine"}
Greeting("goodbye:", s...)
Greeting
内では、who
は同じ基底配列を持つ s
と同じ値を持ちます。
インスタンス化
ジェネリック関数や型は、型パラメータに 型引数 を代入することでインスタンス化されます [Go 1.18]。 インスタンス化は以下の2つのステップで進行します:
各型引数はジェネリック宣言の対応する型パラメータに代入されます。 この代入は型パラメータリスト自体とそのリスト内の任意の型を含め、関数または型宣言全体で行われます。
代入後、各型引数は対応する型パラメータの制約(必要に応じてインスタンス化される)を満たさなければなりません。 そうでなければインスタンス化は失敗します。
型をインスタンス化すると、新しい非ジェネリックな名前付き型が生成されます。 関数をインスタンス化すると、新しい非ジェネリックな関数が生成されます。
型パラメータリスト 型引数 代入後
[P any] int intはanyを満たす
[S ~[]E, E any] []int, int []intは~[]intを満たし、intはanyを満たす
[P io.Writer] string 不正:stringはio.Writerを満たさない
[P comparable] any anyはcomparableを満たす(ただし実装していない)
ジェネリック関数を使用する場合、型引数は明示的に提供されるか、関数が使用されるコンテキストから部分的または完全に推論される場合があります。 推論できる場合、関数が次のような場合には型引数リストを完全に省略できます:
その他のすべての場合、(場合によっては部分的な)型引数リストが存在しなければなりません。 型引数リストが存在しないか部分的である場合、欠けているすべての型引数は関数が使用されるコンテキストから推論可能でなければなりません。
// sumは引数の合計(文字列の場合は連結)を返します。
func sum[T ~int | ~float64 | ~string](x... T) T { … }
x := sum // 不正:xの型は不明
intSum := sum[int] // intSumはfunc(x... int) int型を持つ
a := intSum(2, 3) // aは型intの値5を持つ
b := sum[float64](2.0, 3) // bは型float64の値5.0を持つ
c := sum(b, -1) // cは型float64の値4.0を持つ
type sumFunc func(x... string) string
var f sumFunc = sum // var f sumFunc = sum[string]と同じ
f = sum // f = sum[string]と同じ
部分的な型引数リストは空にできません。少なくとも最初の引数が存在しなければなりません。 リストは型引数の完全なリストのプレフィックスであり、残りの引数は推論されます。 大まかに言えば、型引数は「右から左へ」省略できます。
func apply[S ~[]E, E any](s S, f func(E) E) S { … }
f0 := apply[] // 不正:型引数リストを空にすることはできない
f1 := apply[[]int] // Sの型引数が明示的に提供され、Eの型引数は推論される
f2 := apply[[]string, string] // 両方の型引数が明示的に提供される
var bytes []byte
r := apply(bytes, func(byte) byte { … }) // 両方の型引数が関数引数から推論される
ジェネリック型の場合、すべての型引数は常に明示的に提供されなければなりません。
型推論
ジェネリック関数の使用では、関数が使用されるコンテキスト(関数の型パラメータの制約を含む)から推論できる場合、一部または全部の型引数を省略できます。 型推論は、欠けている型引数を推論し、推論された型引数でインスタンス化が成功する場合に成功します。 そうでなければ、型推論は失敗し、プログラムは無効になります。
型推論は、型のペア間の型関係を推論に使用します。 例えば、関数引数はそれぞれの関数パラメータに代入可能でなければなりません。これにより、引数の型とパラメータの型の間に関係が確立されます。 これらの二つの型のいずれかに型パラメータが含まれる場合、型推論は代入可能関係が満たされるように型パラメータを置き換える型引数を探します。 同様に、型推論は型引数がそれぞれの型パラメータの制約を満たさなければならないという事実を使用します。
一致する型のこのようなペアは、一つまたは複数の型パラメータを含む型方程式に対応し、一つまたは場合によっては複数のジェネリック関数からのものです。 欠けている型引数を推論するとは、それぞれの型パラメータに対する型方程式の結果セットを解くことを意味します。
例えば、以下の場合を考えます:
// dedupは引数スライスの重複エントリを削除したコピーを返します。
func dedup[S ~[]E, E comparable](S) S { … }
type Slice []int
var s Slice
s = dedup(s) // s = dedup[Slice, int](s)と同じ
プログラムが有効であるためには、型Slice
の変数s
が関数パラメータ型S
に代入可能でなければなりません。
複雑さを減らすために、型推論は代入の方向性を無視するため、Slice
とS
の型関係は(対称的な)型方程式Slice ≡A S
(または同様にS ≡A Slice
)で表現できます。ここで≡A
のA
は左辺と右辺の型が代入可能性ルールに従って一致する必要があることを示しています(詳細については型統一のセクションを参照)。
同様に、型パラメータS
はその制約~[]E
を満たさなければなりません。これはS ≡C ~[]E
と表現できます。ここでX ≡C Y
は「X
は制約Y
を満たす」を意味します。
これらの観察から、以下の2つの方程式が導き出されます:
Slice ≡A S (1)
S ≡C ~[]E (2)
これで型パラメータS
とE
の解を求めることができます。
(1)からコンパイラはS
の型引数がSlice
であると推論できます。
同様に、Slice
の基底型が[]int
であり、[]int
は制約の[]E
と一致する必要があるため、コンパイラはE
がint
でなければならないと推論できます。
したがって、これらの2つの方程式に対して、型推論は次のことを推論します:
S ➞ Slice
E ➞ int
型方程式のセットが与えられた場合、解く対象の型パラメータは、インスタンス化が必要で明示的な型引数が提供されていない関数の型パラメータです。
これらの型パラメータは束縛された型パラメータと呼ばれます。
例えば、上記のdedup
の例では、型パラメータS
とE
はdedup
に束縛されています。
ジェネリック関数呼び出しの引数自体がジェネリック関数である場合があります。
その関数の型パラメータは束縛された型パラメータのセットに含まれます。
関数引数の型には、他の関数(例えば関数呼び出しを囲むジェネリック関数)からの型パラメータが含まれる場合があります。
これらの型パラメータも型方程式に現れる可能性がありますが、そのコンテキストでは束縛されていません。
型方程式は常に束縛された型パラメータのみについて解かれます。
型推論はジェネリック関数の呼び出しと、ジェネリック関数の(明示的に関数型付けされた)変数への代入をサポートします。 これには、ジェネリック関数を他の(場合によってもジェネリックな)関数への引数として渡すこと、およびジェネリック関数を結果として返すことが含まれます。 型推論はこれらの各ケースに固有の方程式のセットで動作します。 方程式は以下の通りです(明確にするために型引数リストは省略されています):
関数呼び出し
f(a0, a1, …)
において、f
または関数引数ai
がジェネリック関数である場合:ai
が型付けされていない定数でない場合、対応する関数引数とパラメータのペア(ai, pi)
ごとに方程式typeof(pi) ≡A typeof(ai)
が生成されます。ai
が型付けされていない定数cj
であり、typeof(pi)
が束縛された型パラメータPk
である場合、ペア(cj, Pk)
は型方程式とは別に収集されます。ジェネリック関数
f
を関数型の(非ジェネリックな)変数v
に代入するv = f
の場合:typeof(v) ≡A typeof(f)
。ジェネリック関数
f
が(非ジェネリックな)関数型の結果変数r
に結果として返される戻り文return …, f, …
の場合:typeof(r) ≡A typeof(f)
。
さらに、各型パラメータPk
と対応する型制約Ck
は型方程式Pk ≡C Ck
を生成します。
型推論は、型付けされていない定数を考慮する前に、型付けされたオペランドから得られる型情報を優先します。 したがって、推論は2つのフェーズで進行します:
型方程式は型統一を使用して束縛された型パラメータについて解かれます。統一が失敗した場合、型推論は失敗します。
まだ型引数が推論されていない各束縛型パラメータ
Pk
について、同じ型パラメータを持つ一つ以上のペア(cj, Pk)
が収集された場合、定数式と同様に、これらすべてのペアの定数cj
の定数種別を決定します。Pk
の型引数は決定された定数種別のデフォルト型です。定数種別が競合する定数種別のために決定できない場合、型推論は失敗します。
これらの2つのフェーズの後ですべての型引数が見つからない場合、型推論は失敗します。
2つのフェーズが成功した場合、型推論は各束縛型パラメータに対して型引数を決定しました:
Pk ➞ Ak
型引数Ak
は要素型として他の束縛型パラメータPk
を含む複合型である場合があります(または単に別の束縛型パラメータである場合もあります)。
繰り返し単純化のプロセスでは、各型引数内の束縛型パラメータは、それぞれの型パラメータの型引数で置き換えられ、各型引数が束縛型パラメータから自由になるまで続きます。
型引数が束縛型パラメータを通して自分自身への循環参照を含む場合、単純化、したがって型推論は失敗します。 それ以外の場合、型推論は成功します。
型統一
型推論は型統一を通じて型方程式を解きます。
型統一は方程式の左辺と右辺の型を再帰的に比較し、いずれか、または両方の型が束縛型パラメータであるか含む場合、左辺と右辺が一致する(コンテキストに応じて同一または代入互換になる)ようにそれらの型パラメータの型引数を探します。
そのために、型推論は束縛型パラメータから推論された型引数へのマップを維持します。このマップは型統一中に参照され更新されます。
最初は、束縛型パラメータは知られていますが、マップは空です。
型統一中に新しい型引数A
が推論された場合、型パラメータから引数へのそれぞれのマッピングP ➞ A
がマップに追加されます。
逆に、型を比較するとき、既知の型引数(マップエントリが既に存在する型引数)がその対応する型パラメータの場所を取ります。
型推論が進むにつれて、マップはすべての方程式が考慮されるか、または統一が失敗するまでどんどん埋められていきます。
型推論は統一ステップが失敗せず、マップが各型パラメータのエントリを持つ場合に成功します。
例えば、束縛型パラメータP
を持つ型方程式が与えられた場合:
[10]struct{ elem P, list []P } ≡A [10]struct{ elem string; list []string }
型推論は空のマップから始まります。
統一はまず左辺と右辺の型のトップレベル構造を比較します。
両方とも同じ長さの配列です。要素型が統一される場合に統一されます。
両方の要素型が構造体です。それらは同じ数のフィールドを持ち、同じ名前を持ち、フィールドの型が統一される場合に統一されます。
P
の型引数はまだ知られていません(マップエントリがない)、したがってP
とstring
を統一することで、マップにP ➞ string
というマッピングが追加されます。
list
フィールドの型を統一するには、[]P
と[]string
、したがってP
とstring
を統一する必要があります。
P
の型引数はこの時点で知られているため(P
のマップエントリがある)、その型引数string
がP
の場所を取ります。
string
はstring
と同一であるため、この統一ステップも成功します。
方程式の左辺と右辺の統一がこれで完了しました。
型方程式が1つだけで、統一ステップが失敗せず、マップが完全に埋められているため、型推論は成功します。
統一は、2つの型が同一であるか、代入互換であるか、または構造的に等しいだけであるかによって、厳密な統一と緩い統一の組み合わせを使用します。 それぞれの型統一規則は付録で詳細に説明されています。
代入(パラメータ渡しおよび戻り文を含む)に関与する型X
とY
の形式X ≡A Y
の方程式の場合、トップレベルの型構造は緩く統一される場合がありますが、要素型は代入のルールに一致するように厳密に統一される必要があります。
型パラメータP
とその対応する制約C
の形式P ≡C C
の方程式の場合、統一規則はもう少し複雑です:
C
の型集合内のすべての型が同じ基底型U
を持ち、P
が既知の型引数A
を持つ場合、U
とA
は緩く統一される必要があります。同様に、
C
の型集合内のすべての型が同じ要素型と非競合チャネル方向を持つチャネル型であり、P
が既知の型引数A
を持つ場合、C
の型集合内の最も制限的なチャネル型とA
は緩く統一される必要があります。P
が既知の型引数を持たず、C
に基底(チルダ)型ではない正確に1つの型項T
が含まれる場合、統一はマップにP ➞ T
というマッピングを追加します。C
が上記のような型U
を持たず、P
が既知の型引数A
を持つ場合、A
はC
のすべてのメソッド(もしあれば)を持ち、対応するメソッド型は厳密に統一される必要があります。
型制約からの型方程式を解く際、一つの方程式を解くことで追加の型引数が推論され、それによってそれらの型引数に依存する他の方程式を解くことが可能になる場合があります。 型推論は新しい型引数が推論される限り型統一を繰り返します。
演算子
演算子はオペランドを組み合わせて式を形成します。
Expression = UnaryExpr | Expression binary_op Expression .
UnaryExpr = PrimaryExpr | unary_op UnaryExpr .
binary_op = "||" | "&&" | rel_op | add_op | mul_op .
rel_op = "==" | "!=" | "<" | "<=" | ">" | ">=" .
add_op = "+" | "-" | "|" | "^" .
mul_op = "*" | "/" | "%" | "<<" | ">>" | "&" | "&^" .
unary_op = "+" | "-" | "!" | "^" | "*" | "&" | "<-" .
比較演算子については別の箇所で説明します。 他の二項演算子では、シフト演算や型付けされていない定数を含む演算でない限り、オペランドの型は同一でなければなりません。 定数のみを含む演算については、定数式のセクションを参照してください。
シフト演算を除き、一方のオペランドが型付けされていない定数であり、もう一方がそうでない場合、定数は暗黙的に他方のオペランドの型に変換されます。
シフト式の右オペランドは整数型でなければならず、あるいはuint
型の値として表現可能な型付けされていない定数でなければなりません [Go 1.13]。
非定数シフト式の左オペランドが型付けされていない定数である場合、シフト式が左オペランドだけに置き換えられた場合に想定される型に最初に暗黙的に変換されます。
var a [1024]byte
var s uint = 33
// 以下の例の結果は64ビット整数に対するものです
var i = 1<<s // 1はint型
var j int32 = 1<<s // 1はint32型; j == 0
var k = uint64(1<<s) // 1はuint64型; k == 1<<33
var m int = 1.0<<s // 1.0はint型; m == 1<<33
var n = 1.0<<s == j // 1.0はint32型; n == true
var o = 1<<s == 2<<s // 1と2はint型; o == false
var p = 1<<s == 1<<33 // 1はint型; p == true
var u = 1.0<<s // 不正: 1.0はfloat64型、シフトできない
var u1 = 1.0<<s != 0 // 不正: 1.0はfloat64型、シフトできない
var u2 = 1<<s != 1.0 // 不正: 1はfloat64型、シフトできない
var v1 float32 = 1<<s // 不正: 1はfloat32型、シフトできない
var v2 = string(1<<s) // 不正: 1は文字列に変換され、シフトできない
var w int64 = 1.0<<33 // 1.0<<33は定数シフト式; w == 1<<33
var x = a[1.0<<s] // パニック: 1.0はint型だが、1<<33は配列の境界を超える
var b = make([]byte, 1.0<<s) // 1.0はint型; len(b) == 1<<33
// 以下の例の結果は32ビット整数に対するもので、
// シフトはオーバーフローします
var mm int = 1.0<<s // 1.0はint型; mm == 0
var oo = 1<<s == 2<<s // 1と2はint型; oo == true
var pp = 1<<s == 1<<33 // 不正: 1はint型だが、1<<33はintをオーバーフロー
var xx = a[1.0<<s] // 1.0はint型; xx == a[0]
var bb = make([]byte, 1.0<<s) // 1.0はint型; len(bb) == 0
演算子の優先順位
単項演算子は最も高い優先順位を持ちます。
++
と--
演算子は式ではなく文を形成するため、演算子の階層の外にあります。
その結果、文*p++
は(*p)++
と同じです。
二項演算子には5つの優先順位レベルがあります。
乗算演算子が最も強く結合し、続いて加算演算子、比較演算子、&&
(論理AND)、最後に||
(論理OR)の順です:
優先順位 演算子
5 * / % << >> & &^
4 + - | ^
3 == != < <= > >=
2 &&
1 ||
同じ優先順位の二項演算子は左から右に結合します。
例えば、x / y * z
は(x / y) * z
と同じです。
+x // x
42 + a - b // (42 + a) - b
23 + 3*x[i] // 23 + (3 * x[i])
x <= f() // x <= f()
^a >> b // (^a) >> b
f() || g() // f() || g()
x == y+1 && <-chanInt > 0 // (x == (y+1)) && ((<-chanInt) > 0)
算術演算子
算術演算子は数値に適用され、最初のオペランドと同じ型の結果を生成します。四つの標準的な算術演算子(+
、-
、*
、/
)は整数、浮動小数点、複素数型に適用されます;+
は文字列にも適用されます。
ビット論理演算子とシフト演算子は整数にのみ適用されます。
+ 合計 整数、浮動小数点、複素数、文字列
- 差 整数、浮動小数点、複素数
* 積 整数、浮動小数点、複素数
/ 商 整数、浮動小数点、複素数
% 剰余 整数
& ビット単位AND 整数
| ビット単位OR 整数
^ ビット単位XOR 整数
&^ ビットクリア(AND NOT) 整数
<< 左シフト 整数 << 整数 >= 0
>> 右シフト 整数 >> 整数 >= 0
オペランド型が型パラメータである場合、演算子はその型セット内の各型に適用できる必要があります。 オペランドは、型パラメータがインスタンス化される型引数の値として表され、操作はその型引数の精度で計算されます。例えば、以下の関数では:
func dotProduct[F ~float32|~float64](v1, v2 []F) F {
var s F
for i, x := range v1 {
y := v2[i]
s += x * y
}
return s
}
積x * y
と加算s += x * y
は、F
の型引数に応じて、それぞれfloat32
またはfloat64
の精度で計算されます。
整数演算子
二つの整数値x
とy
について、整数の商q = x / y
と剰余r = x % y
は以下の関係を満たします:
x = q*y + r かつ |r| < |y|
ここで、x / y
はゼロ方向に切り捨てられます(「切り捨て除算」)。
x y x / y x % y
5 3 1 2
-5 3 -1 -2
5 -3 -1 2
-5 -3 1 -2
このルールの例外が一つあります。被除数x
がx
の整数型における最も負の値である場合、商q = x / -1
は2の補数の整数オーバーフローによりx
に等しくなります(そしてr = 0
):
x, q
int8 -128
int16 -32768
int32 -2147483648
int64 -9223372036854775808
除数が定数の場合、それはゼロであってはなりません。 実行時に除数がゼロの場合、実行時パニックが発生します。 被除数が非負で除数が2の定数べき乗である場合、除算は右シフトに置き換えられ、剰余の計算はビット単位のAND操作に置き換えられることがあります:
x x / 4 x % 4 x >> 2 x & 3
11 2 3 2 3
-11 -2 -3 -3 1
シフト演算子は、右オペランドで指定されたシフト数だけ左オペランドをシフトします。シフト数は非負でなければなりません。実行時にシフト数が負の場合、実行時パニックが発生します。
シフト演算子は、左オペランドが符号付き整数の場合は算術シフトを、符号なし整数の場合は論理シフトを実装します。
シフト数には上限がありません。シフトはシフト数n
に対して、左オペランドが1ずつn
回シフトされるかのように動作します。
その結果、x << 1
はx*2
と同じであり、x >> 1
はx/2
と同じですが、負の無限大に向かって切り捨てられます。
整数オペランドの場合、単項演算子+
、-
、および^
は次のように定義されます:
+x は 0 + x
-x 否定 は 0 - x
^x ビット単位の補数 は m ^ x ここで m = 符号なしxの場合は「すべてのビットが1に設定」
符号付きxの場合は -1
整数オーバーフロー
符号なし整数値の場合、演算+
、-
、*
、および<<
は2nを法として計算されます。ここでnは符号なし整数の型のビット幅です。
簡単に言えば、これらの符号なし整数演算はオーバーフロー時に上位ビットを破棄し、プログラムは「ラップアラウンド」に依存することができます。
符号付き整数の場合、演算+
、-
、*
、/
、および<<
は合法的にオーバーフローする可能性があり、結果の値は符号付き整数の表現、演算、およびそのオペランドによって決定論的に定義されます。
オーバーフローは実行時パニックを引き起こしません。
コンパイラは、オーバーフローが発生しないという前提の下でコードを最適化することはできません。例えば、x < x + 1
が常に真であると仮定することはできません。
浮動小数点演算子
浮動小数点数と複素数の場合、+x
はx
と同じであり、-x
はx
の否定です。
ゼロによる浮動小数点または複素数の除算の結果はIEEE 754標準を超えて指定されておらず、実行時パニックが発生するかどうかは実装固有です。
実装は、複数の浮動小数点演算を単一の融合演算に組み合わせる場合があり、これは文をまたいで行われる可能性があり、命令を個別に実行して丸めることによって得られる値とは異なる結果を生成する場合があります。 明示的な浮動小数点型変換は、ターゲット型の精度に丸め、その丸めを破棄する融合を防ぎます。
例えば、一部のアーキテクチャでは「融合乗算加算」(FMA)命令が提供されており、中間結果x*y
を丸めずにx*y + z
を計算します。
これらの例は、Go実装がその命令をいつ使用できるかを示しています:
// rの計算にFMAが許可される、x*yが明示的に丸められないため:
r = x*y + z
r = z; r += x*y
t = x*y; r = t + z
*p = x*y; r = *p + z
r = x*y + float64(z)
// rの計算にFMAが許可されない、x*yの丸めが省略されるため:
r = float64(x*y) + z
r = z; r += float64(x*y)
t = float64(x*y); r = t + z
文字列連結
文字列は+
演算子または+=
代入演算子を使用して連結できます:
s := "hi" + string(c)
s += " and good bye"
文字列の加算は、オペランドを連結して新しい文字列を作成します。
比較演算子
比較演算子は2つのオペランドを比較し、型付けされていないブール値を生成します。
== 等しい
!= 等しくない
< より小さい
<= より小さいか等しい
> より大きい
>= より大きいか等しい
任意の比較において、最初のオペランドは2番目のオペランドの型に代入可能でなければならず、またはその逆も同様です。
等価演算子==
と!=
は比較可能な型のオペランドに適用されます。
順序付け演算子<
、<=
、>
、および>=
は順序付けられた型のオペランドに適用されます。
これらの用語と比較の結果は次のように定義されます:
ブール型は比較可能です。 2つのブール値は、両方が
true
または両方がfalse
の場合に等しくなります。整数型は比較可能で順序付けられています。 2つの整数値は通常の方法で比較されます。
浮動小数点型は比較可能で順序付けられています。 2つの浮動小数点値はIEEE 754標準で定義されているように比較されます。
複素数型は比較可能です。 2つの複素数値
u
とv
は、real(u) == real(v)
かつimag(u) == imag(v)
の場合に等しくなります。文字列型は比較可能で順序付けられています。 2つの文字列値はバイト単位で辞書的に比較されます。
ポインタ型は比較可能です。 2つのポインタ値は、同じ変数を指す場合、または両方の値が
nil
の場合に等しくなります。 異なるゼロサイズ変数へのポインタは等しいか等しくないかもしれません。チャネル型は比較可能です。 2つのチャネル値は、同じ
make
呼び出しによって作成された場合、または両方の値がnil
の場合に等しくなります。型パラメータではないインターフェース型は比較可能です。 2つのインターフェース値は、同一の動的型と等しい動的値を持つ場合、または両方の値が
nil
の場合に等しくなります。非インターフェース型
X
の値x
とインターフェース型T
の値t
は、型X
が比較可能で、X
がT
を実装している場合に比較できます。 これらは、t
の動的型がX
と同一であり、t
の動的値がx
に等しい場合に等しくなります。構造体型は、そのすべてのフィールド型が比較可能である場合に比較可能です。 2つの構造体値は、対応する非ブランクフィールド値が等しい場合に等しくなります。 フィールドはソース順に比較され、2つのフィールド値が異なるとすぐに比較が停止します(またはすべてのフィールドが比較された場合)。
配列型は、その配列要素型が比較可能である場合に比較可能です。 2つの配列値は、対応する要素値が等しい場合に等しくなります。 要素は昇順インデックス順に比較され、2つの要素値が異なるとすぐに比較が停止します(またはすべての要素が比較された場合)。
型パラメータは、厳密に比較可能(以下を参照)である場合に比較可能です。
2つの同一の動的型を持つインターフェース値の比較は、その型が比較可能でない場合に実行時パニックを引き起こします。この動作は、直接的なインターフェース値の比較だけでなく、インターフェース値の配列やインターフェース値のフィールドを持つ構造体を比較する場合にも適用されます。
スライス、マップ、および関数型は比較できません。
ただし、特別なケースとして、スライス、マップ、または関数値は、定義済み識別子nil
と比較できます。
ポインタ、チャネル、およびインターフェース値のnil
との比較も許可されており、上記の一般的なルールに従います。
const c = 3 < 4 // cは型付けされていないブール定数true
type MyBool bool
var x, y int
var (
// 比較の結果は型付けされていないブールです。
// 通常の代入ルールが適用されます。
b3 = x == y // b3はbool型
b4 bool = x == y // b4はbool型
b5 MyBool = x == y // b5はMyBool型
)
型は、比較可能でありインターフェース型でもインターフェース型で構成されていない場合、厳密に比較可能です。 具体的には:
ブール、数値、文字列、ポインタ、およびチャネル型は厳密に比較可能です。
構造体型は、そのすべてのフィールド型が厳密に比較可能である場合に厳密に比較可能です。
配列型は、その配列要素型が厳密に比較可能である場合に厳密に比較可能です。
型パラメータは、その型セット内のすべての型が厳密に比較可能である場合に厳密に比較可能です。
論理演算子
論理演算子はブール値に適用され、オペランドと同じ型の結果を生成します。 左オペランドが評価され、条件が必要とする場合は右オペランドも評価されます。
&& 条件付きAND p && q は "pならqそうでなければfalse"
|| 条件付きOR p || q は "pならtrueそうでなければq"
! NOT !p は "pでない"
アドレス演算子
型T
のオペランドx
に対して、アドレス操作&x
はx
への型*T
のポインタを生成します。
オペランドはアドレス可能でなければなりません。
つまり、変数、ポインタ逆参照、またはスライスのインデックス操作;アドレス可能な構造体オペランドのフィールドセレクタ;アドレス可能な配列のインデックス操作のいずれかである必要があります。
アドレス可能性要件の例外として、x
は(おそらく括弧で囲まれた)複合リテラルでもかまいません。
x
の評価が実行時パニックを引き起こす場合、&x
の評価も同様です。
ポインタ型*T
のオペランドx
に対して、ポインタ逆参照*x
はx
が指す型T
の変数を示します。
x
がnil
の場合、*x
を評価しようとすると実行時パニックが発生します。
&x
&a[f(2)]
&Point{2, 3}
*p
*pf(x)
var x *int = nil
*x // 実行時パニックを引き起こす
&*x // 実行時パニックを引き起こす
受信演算子
チャネル型のオペランドch
に対して、受信操作<-ch
の値はチャネルch
から受信した値です。
チャネルの方向は受信操作を許可する必要があり、受信操作の型はチャネルの要素型です。
式は値が利用可能になるまでブロックします。
nil
チャネルからの受信は永遠にブロックします。
閉じられたチャネルでの受信操作は、以前に送信された値がすべて受信された後に、要素型のゼロ値を生成して即座に進行できます。
v1 := <-ch
v2 = <-ch
f(<-ch)
<-strobe // クロックパルスを待ち、受信した値を破棄
オペランド型が型パラメータである場合、その型セット内のすべての型は受信操作を許可するチャネル型でなければならず、それらはすべて同じ要素型を持つ必要があります。これが受信操作の型となります。
特別な形式の代入文または初期化で使用される受信式
x, ok = <-ch
x, ok := <-ch
var x, ok = <-ch
var x, ok T = <-ch
は、通信が成功したかどうかを報告する追加の型付けされていないブール結果を生成します。ok
の値は、受信した値がチャネルへの成功した送信操作によって配信された場合はtrue
、チャネルが閉じられて空であるために生成されたゼロ値の場合はfalse
です。
型変換
型変換は式の型を変換で指定された型に変更します。 変換はソースコード内に明示的に記述されるか、式が現れるコンテキストによって暗黙的に行われる場合があります。
明示的な変換は T(x)
という形式の式であり、T
は型、x
は型 T
に変換できる式です。
Conversion = Type "(" Expression [ "," ] ")" .
型が演算子 *
または <-
で始まる場合、あるいは型がキーワード func
で始まり結果リストを持たない場合、曖昧さを避けるために必要に応じて括弧で囲む必要があります:
*Point(p) // *(Point(p)) と同じ
(*Point)(p) // p は *Point に変換される
<-chan int(c) // <-(chan int(c)) と同じ
(<-chan int)(c) // c は <-chan int に変換される
func()(x) // 関数シグネチャ func() x
(func())(x) // x は func() に変換される
(func() int)(x) // x は func() int に変換される
func() int(x) // x は func() int に変換される(明確)
定数値 x
は、x
が T
の値によって表現可能である場合、型 T
に変換できます。
特別なケースとして、整数定数 x
は非定数の x
と同じルールを使用して文字列型に明示的に変換できます。
型パラメータでない型に定数を変換すると、型付き定数が生成されます。
uint(iota) // uint型のiota値
float32(2.718281828) // float32型の2.718281828
complex128(1) // complex128型の1.0 + 0.0i
float32(0.49999999) // float32型の0.5
float64(-1e-1000) // float64型の0.0
string('x') // string型の"x"
string(0x266c) // string型の"♬"
myString("foo" + "bar") // myString型の"foobar"
string([]byte{'a'}) // 定数ではない: []byte{'a'}は定数ではない
(*int)(nil) // 定数ではない: nilは定数ではない、*intはブール値、数値、または文字列型ではない
int(1.2) // 不正: 1.2はintとして表現できない
string(65.0) // 不正: 65.0は整数定数ではない
定数を型パラメータに変換すると、その型の非定数値が生成されます。 この値は、型パラメータが実体化される型引数の値として表されます。 例えば、次の関数が与えられた場合:
func f[P ~float32|~float64]() {
… P(1.1) …
}
変換 P(1.1)
は型 P
の非定数値を生成し、値 1.1
は f
の型引数に応じて float32
または float64
として表されます。
従って、f
が float32
型で実体化される場合、式 P(1.1) + 1.2
の数値は、対応する非定数 float32
加算と同じ精度で計算されます。
非定数値 x
は、以下のいずれかの場合に型 T
に変換できます:
x
はT
に代入可能である。- 構造体タグを無視した場合(以下参照)、
x
の型とT
は型パラメータではないが、同一の基底型を持つ。 - 構造体タグを無視した場合(以下参照)、
x
の型とT
は名前付き型ではないポインタ型であり、そのポインタのベース型は型パラメータではなく、同一の基底型を持つ。 x
の型とT
は両方とも整数型または浮動小数点型である。x
の型とT
は両方とも複素数型である。x
は整数かバイトまたはルーンのスライスであり、T
は文字列型である。x
は文字列であり、T
はバイトまたはルーンのスライスである。x
はスライスであり、T
は配列 [Go 1.20] または配列へのポインタ [Go 1.17] であり、スライスと配列の型は同一の要素型を持つ。
さらに、T
または x
の型 V
が型パラメータである場合、以下の条件のいずれかが適用されると、x
は型 T
に変換することもできます:
V
とT
の両方が型パラメータであり、V
の型セット内の各型の値がT
の型セット内の各型に変換できる。V
のみが型パラメータであり、V
の型セット内の各型の値がT
に変換できる。T
のみが型パラメータであり、x
がT
の型セット内の各型に変換できる。
変換目的で同一性を比較する際、構造体タグは無視されます:
type Person struct {
Name string
Address *struct {
Street string
City string
}
}
var data *struct {
Name string `json:"name"`
Address *struct {
Street string `json:"street"`
City string `json:"city"`
} `json:"address"`
}
var person = (*Person)(data) // タグを無視すると、基底型は同一
数値型間または文字列型との間の(非定数)変換には、特定のルールが適用されます。
これらの変換は x
の表現を変更し、実行時コストが発生する場合があります。
他のすべての変換は型を変更するだけで、x
の表現は変更しません。
ポインタと整数の間を変換する言語的メカニズムはありません。 パッケージ unsafe は限定された状況でこの機能を実装しています。
数値型間の変換
非定数数値の変換には、以下のルールが適用されます:
整数型間の変換では、値が符号付き整数の場合、暗黙的な無限精度まで符号拡張されます。そうでない場合はゼロ拡張されます。 その後、結果型のサイズに収まるように切り捨てられます。 例えば、
v := uint16(0x10F0)
の場合、uint32(int8(v)) == 0xFFFFFFF0
となります。 変換は常に有効な値を生成し、オーバーフローの表示はありません。浮動小数点数を整数に変換する場合、小数部は破棄されます(ゼロ方向への切り捨て)。
整数または浮動小数点数を浮動小数点型に変換する場合、または複素数を別の複素数型に変換する場合、結果値は宛先型で指定された精度に丸められます。 例えば、
float32
型の変数x
の値は、IEEE 754 32ビット数値の精度を超えて追加精度で格納される場合がありますが、float32(x) はx
の値を32ビット精度に丸めた結果を表します。 同様に、x + 0.1
は32ビット以上の精度を使用する場合がありますが、float32(x + 0.1)
はそうではありません。
浮動小数点または複素数値を含むすべての非定数変換において、結果型が値を表現できない場合、変換は成功しますが、結果値は実装依存です。
文字列型との間の変換
バイトのスライスを文字列型に変換すると、スライスの要素が連続するバイトである文字列が生成されます。
string([]byte{'h', 'e', 'l', 'l', '\xc3', '\xb8'}) // "hellø" string([]byte{}) // "" string([]byte(nil)) // "" type bytes []byte string(bytes{'h', 'e', 'l', 'l', '\xc3', '\xb8'}) // "hellø" type myByte byte string([]myByte{'w', 'o', 'r', 'l', 'd', '!'}) // "world!" myString([]myByte{'\xf0', '\x9f', '\x8c', '\x8d'}) // "🌍"
ルーンのスライスを文字列型に変換すると、個々のルーン値を文字列に変換して連結した文字列が生成されます。
string([]rune{0x767d, 0x9d6c, 0x7fd4}) // "\u767d\u9d6c\u7fd4" == "白鵬翔" string([]rune{}) // "" string([]rune(nil)) // "" type runes []rune string(runes{0x767d, 0x9d6c, 0x7fd4}) // "\u767d\u9d6c\u7fd4" == "白鵬翔" type myRune rune string([]myRune{0x266b, 0x266c}) // "\u266b\u266c" == "♫♬" myString([]myRune{0x1f30e}) // "\U0001f30e" == "🌎"
文字列型の値をバイトのスライス型に変換すると、文字列のバイトが連続する要素である非nilスライスが生成されます。 結果のスライスの容量は実装固有であり、スライスの長さより大きい場合があります。
[]byte("hellø") // []byte{'h', 'e', 'l', 'l', '\xc3', '\xb8'} []byte("") // []byte{} bytes("hellø") // []byte{'h', 'e', 'l', 'l', '\xc3', '\xb8'} []myByte("world!") // []myByte{'w', 'o', 'r', 'l', 'd', '!'} []myByte(myString("🌏")) // []myByte{'\xf0', '\x9f', '\x8c', '\x8f'}
文字列型の値をルーンのスライス型に変換すると、文字列の個々のUnicodeコードポイントを含むスライスが生成されます。 結果のスライスの容量は実装固有であり、スライスの長さより大きい場合があります。
[]rune(myString("白鵬翔")) // []rune{0x767d, 0x9d6c, 0x7fd4} []rune("") // []rune{} runes("白鵬翔") // []rune{0x767d, 0x9d6c, 0x7fd4} []myRune("♫♬") // []myRune{0x266b, 0x266c} []myRune(myString("🌐")) // []myRune{0x1f310}
最後に、歴史的な理由から、整数値を文字列型に変換することができます。 この形式の変換は、指定された整数値を持つUnicodeコードポイントの(複数バイトの可能性がある)UTF-8表現を含む文字列を生成します。 有効なUnicodeコードポイントの範囲外の値は
"\uFFFD"
に変換されます。string('a') // "a" string(65) // "A" string('\xf8') // "\u00f8" == "ø" == "\xc3\xb8" string(-1) // "\ufffd" == "\xef\xbf\xbd" type myString string myString('\u65e5') // "\u65e5" == "日" == "\xe6\x97\xa5"
注意: この形式の変換は最終的に言語から削除される可能性があります。
go vet
ツールは、特定の整数から文字列への変換を潜在的なエラーとしてフラグ付けします。 代わりにutf8.AppendRune
やutf8.EncodeRune
などのライブラリ関数を使用すべきです。
スライスから配列または配列ポインタへの変換
スライスを配列に変換すると、スライスの基底配列の要素を含む配列が生成されます。 同様に、スライスを配列ポインタに変換すると、スライスの基底配列へのポインタが生成されます。 どちらの場合も、スライスの長さが配列の長さより小さい場合、実行時パニックが発生します。
s := make([]byte, 2, 4)
a0 := [0]byte(s)
a1 := [1]byte(s[1:]) // a1[0] == s[1]
a2 := [2]byte(s) // a2[0] == s[0]
a4 := [4]byte(s) // パニック: len([4]byte) > len(s)
s0 := (*[0]byte)(s) // s0 != nil
s1 := (*[1]byte)(s[1:]) // &s1[0] == &s[1]
s2 := (*[2]byte)(s) // &s2[0] == &s[0]
s4 := (*[4]byte)(s) // パニック: len([4]byte) > len(s)
var t []string
t0 := [0]string(t) // nil スライス t でも問題なし
t1 := (*[0]string)(t) // t1 == nil
t2 := (*[1]string)(t) // パニック: len([1]string) > len(t)
u := make([]byte, 0)
u0 := (*[0]byte)(u) // u0 != nil
定数式
定数式には定数オペランドのみを含めることができ、コンパイル時に評価されます。
型付けされていないブール値、数値、および文字列定数は、それぞれブール型、数値型、または文字列型のオペランドを使用することが合法な場所ならどこでもオペランドとして使用できます。
定数の比較は常に型付けされていないブール定数を生成します。 定数のシフト式の左オペランドが型付けされていない定数の場合、結果は整数定数になります。 それ以外の場合は、左オペランドと同じ型の定数になり、左オペランドは整数型でなければなりません。
型付けされていない定数に対する他の操作は、同じ種類の型付けされていない定数を生成します。 つまり、ブール値、整数、浮動小数点、複素数、または文字列定数です。 (シフト以外の)二項演算の型付けされていないオペランドが異なる種類の場合、結果は次のリストで後に現れるオペランドの種類となります:整数、ルーン、浮動小数点、複素数。 例えば、型付けされていない整数定数を型付けされていない複素数定数で割ると、型付けされていない複素数定数が生成されます。
const a = 2 + 3.0 // a == 5.0 (型付けされていない浮動小数点定数)
const b = 15 / 4 // b == 3 (型付けされていない整数定数)
const c = 15 / 4.0 // c == 3.75 (型付けされていない浮動小数点定数)
const Θ float64 = 3/2 // Θ == 1.0 (型 float64、3/2は整数除算)
const Π float64 = 3/2. // Π == 1.5 (型 float64、3/2.は浮動小数点除算)
const d = 1 << 3.0 // d == 8 (型付けされていない整数定数)
const e = 1.0 << 3 // e == 8 (型付けされていない整数定数)
const f = int32(1) << 33 // 不正 (定数 8589934592 が int32 をオーバーフロー)
const g = float64(2) >> 1 // 不正 (float64(2) は型付き浮動小数点定数)
const h = "foo" > "bar" // h == true (型付けされていないブール定数)
const j = true // j == true (型付けされていないブール定数)
const k = 'w' + 1 // k == 'x' (型付けされていないルーン定数)
const l = "hi" // l == "hi" (型付けされていない文字列定数)
const m = string(k) // m == "x" (型 string)
const Σ = 1 - 0.707i // (型付けされていない複素数定数)
const Δ = Σ + 2.0e-4 // (型付けされていない複素数定数)
const Φ = iota*1i - 1/1i // (型付けされていない複素数定数)
組み込み関数 complex
を型付けされていない整数、ルーン、または浮動小数点定数に適用すると、型付けされていない複素数定数が生成されます。
const ic = complex(0, c) // ic == 3.75i (型付けされていない複素数定数)
const iΘ = complex(0, Θ) // iΘ == 1i (型 complex128)
定数式は常に正確に評価されます。中間値および定数自体は、言語内の事前宣言型でサポートされているものよりも大幅に大きな精度を必要とする場合があります。以下は合法な宣言です:
const Huge = 1 << 100 // Huge == 1267650600228229401496703205376 (型付けされていない整数定数)
const Four int8 = Huge >> 98 // Four == 4 (型 int8)
定数の除算または剰余演算の除数はゼロであってはなりません:
3.14 / 0.0 // 不正: ゼロによる除算
型付き定数の値は、常に定数型の値によって正確に表現可能でなければなりません。以下の定数式は不正です:
uint(-1) // -1 は uint として表現できない
int(3.14) // 3.14 は int として表現できない
int64(Huge) // 1267650600228229401496703205376 は int64 として表現できない
Four * 300 // オペランド 300 は int8 (Four の型) として表現できない
Four * 100 // 積 400 は int8 (Four の型) として表現できない
単項ビット単位補数演算子 ^
で使用されるマスクは、非定数のルールに合わせています:マスクは符号なし定数の場合はすべて1、符号付きおよび型付けされていない定数の場合は-1です。
^1 // 型付けされていない整数定数、-2と等しい
uint8(^1) // 不正: uint8(-2)と同じ、-2はuint8として表現できない
^uint8(1) // 型付きuint8定数、0xFF ^ uint8(1) = uint8(0xFE)と同じ
int8(^1) // int8(-2)と同じ
^int8(1) // -1 ^ int8(1) = -2と同じ
実装制限:コンパイラは型付けされていない浮動小数点または複素数定数式を計算するときに丸めを使用する場合があります。 定数のセクションの実装制限を参照してください。 この丸めにより、無限精度で計算した場合に整数になるはずの浮動小数点定数式が整数コンテキストで無効になる場合があります(その逆も同様)。
評価順序
パッケージレベルでは、初期化依存関係が変数宣言内の個々の初期化式の評価順序を決定します。 それ以外の場合、式、代入、またはreturn文のオペランドを評価する際、すべての関数呼び出し、メソッド呼び出し、受信操作、および二項論理演算は語彙的に左から右の順序で評価されます。
例えば、(関数ローカルな)代入
y[f()], ok = g(z || h(), i()+x[j()], <-c), k()
では、関数呼び出しと通信は f()
、h()
(z
が false と評価される場合)、i()
、j()
、<-c
、g()
、k()
の順序で行われます。
ただし、これらのイベントの順序と x
の評価とインデックス付け、および y
と z
の評価の順序は、語彙的に必要な場合を除いて指定されていません。
例えば、g
はその引数が評価される前に呼び出すことはできません。
a := 1
f := func() int { a++; return a }
x := []int{a, f()} // xは[1, 2]または[2, 2]の可能性がある: aとf()の間の評価順序は指定されていない
m := map[int]int{a: 1, a: 2} // mは{2: 1}または{2: 2}の可能性がある: 2つのマップ代入間の評価順序は指定されていない
n := map[int]int{a: f()} // nは{2: 3}または{3: 3}の可能性がある: キーと値の間の評価順序は指定されていない
パッケージレベルでは、初期化依存関係は個々の初期化式に対する左から右のルールを上書きしますが、各式内のオペランドに対しては上書きしません:
var a, b, c = f() + v(), g(), sqr(u()) + v()
func f() int { return c }
func g() int { return a }
func sqr(x int) int { return x*x }
// 関数uとvは他のすべての変数と関数から独立しています
関数呼び出しは u()
、sqr()
、v()
、f()
、v()
、g()
の順序で行われます。
単一の式内の浮動小数点演算は、演算子の結合性に従って評価されます。
明示的な括弧はデフォルトの結合性を上書きすることで評価に影響を与えます。
式 x + (y + z)
では、加算 y + z
が最初に実行され、その後 x
が加算されます。