Go
melangの各noteで他言語と比較した、Goの設計をまとめたページ。ここに書かれている内容は、各noteの比較表から自動で集めたものです。
型
型推論はあるが、明示する場面も多い。
var age int = 20var age int = 20
name := "Alice" // := による型推論`:=` を使った短い変数宣言では型推論が効く一方、関数シグネチャなどでは型を明示するのが基本。
型注釈の位置
型は名前の後ろに置くpostfix方式。シグネチャに推論はない。
var age int = 20var age int = 20
name := "Alice" // :=で推論
func greet(name string) string { // 引数・戻り値は常に明示
return "Hello, " + name
}`var name Type`という一貫したpostfix構文を採用しており、Cの`Type name`とは逆順。`:=`によるローカルの型推論はあるが、関数シグネチャでは常に型を書く必要があり、公開APIの可読性を型推論より優先する設計。
型変換
型名を関数のように使って変換し、パースは(値, error)で返す。
y := int64(x)var x int32 = 10
y := int64(x) // 型名を使った明示的な変換
var big int64 = 3_000_000_000
z := int32(big) // 黙って切り捨てられる
n, err := strconv.Atoi("42")
if err != nil {
// 失敗
}数値型間の変換は`T(x)`の形で、縮小変換でも失敗を検出せず値を切り捨てる。文字列からのパースは`strconv`パッケージの関数が`(値, error)`を返し、呼び出し側が`err`を確認する。失敗を返り値で表す点はmelangの`Result`に近いが、確認を忘れてもコンパイルは通る。
暗黙の型変換
異なる数値型間の変換は常に明示が必要。
var x int32 = 10var x int32 = 10
var y int64 = x // コンパイルエラー
var y int64 = int64(x) // OKint32からint64のような安全な拡大変換であっても暗黙には行われず、必ず`T(x)`という形の明示的な変換を書く。
Enum / Sum Type
組み込みのenumがなく、named type + constで代替する。
type Status int
const ( Pending Status = iota; Running; Completed )type Status int
const (
Pending Status = iota
Running
Completed
)
switch s {
case Pending:
// ...
case Running:
// ...
} // Completedの分岐を書き忘れてもコンパイルは通る言語組み込みのenumがなく、named type + `const` / `iota`で整数定数を列挙する方式を取る。Go公式Wikiも「Go does not support enums」と明記している。`switch`に網羅性チェックはなく、variant追加時の対応漏れをコンパイル時に検出できない。
値の不変性
struct・slice・mapは変更でき、不変にする仕組みはない。
const n = 1 / var x = 1const limit = 10 // 数値・文字列などのコンパイル時定数だけ
type User struct{ Name string; Tags []string }
u := User{"alice", []string{"a"}}
u = User{"bob", nil} // 再代入
u.Name = "bob" // フィールドの変更
u.Tags = append(u.Tags, "admin")
u.Tags[0] = "x" // sliceの要素の変更`const`は数値・文字列・真偽値などのコンパイル時定数にしか使えず、struct・slice・mapを不変にする手段はない。文字列は不変だが、他のデータは変更できる。sliceは同じ配列を共有するため、別の変数から見た値が変わることがある。
名前付き引数
名前付き引数はなく、オプション値は構造体やFunctional Optionsで表現する。
func Foo(width, height int)func Foo(width, height int) {}
Foo(10, 20) // 引数名は呼び出し側に現れない
type FooOptions struct { Width, Height int }
func Foo2(opts FooOptions) {}
Foo2(FooOptions{Width: 10, Height: 20}) // フィールド名で意味を明示名前付き引数はなく、オプションが多い関数では構造体を渡すか、Functional Optionsパターンで意味を持たせるのが一般的。
関数の引数
すべて値渡し。参照的に扱うにはポインタを明示的に渡す。
func f(x int) / func f(p *int)func a(n int) { n = 1 } // 呼び出し側には影響しない
func b(p *int) { *p = 1 } // ポインタ経由で書き換える
n := 0
a(n)
b(&n) // 呼び出し側に & が付く
// スライスやmapは、ヘッダ自体がコピーされるが中身は共有される引数はすべて値渡しで、参照的に扱いたければポインタを渡す。ポインタの受け渡しは呼び出し側にも`&`が付くため、書き換えられる可能性が読み取れる。ただしスライスやmapは値渡しでも中身を共有するため、渡し方が型によって分かりにくくなる。
if 文/式
`if`はstatementで、三項演算子もない。`else`を避けたearly returnが慣習になっている。
if cond { ... } else { ... }// 値の分岐はstatementで書く
var x int
if cond {
x = 1
} else {
x = 2
}
// 慣習: returnした後にelseを書かない
if err != nil {
return err
}
process()`if`は式ではなく、三項演算子もない。値の分岐は変数を宣言してからstatementで代入する。一方で、`return`で終わるbranchのあとに`else`を書かないという慣習があり、linterも指摘する。melangが目指す「手続きはearly return」に近い文化を持つが、言語としては強制していない。
I/O の明示
すべての関数が同じ形で書け、I/Oも区別されない。
func ReadFile(path string) (string, error)func ReadFile(path string) (string, error) {
b, err := os.ReadFile(path)
return string(b), err
}
func Double(x int) int {
ReadFile("x") // 戻り値を無視して呼べる
return x * 2
}`(T, error)`という戻り値の形はGoの慣用句だが、これは失敗しうることを示す規約であり、I/Oかどうかは示さない。goroutineの起動やチャネル操作も、通常の関数呼び出しと同じ構文で書ける。
言語名
一般名詞で検索性が低く、`Golang`という別名が定着した。
`Go`は日常英単語であり、検索エンジンで言語のドキュメントに絞り込みにくい。この問題を避けるため、コミュニティは`golang`という別名(ドメインも`golang.org`)を早期から用意し、事実上の第二の名前として定着させた。一般名詞をそのまま言語名にすると、名前の一意性を別の手段(別名・ドメイン)で補う必要が生じる例。