バックエンドでGo Langを使う案件や会社のお付き合いが最近ぐっと増えたため、Go Langの基礎文法を学びます。A Tour of Goをざっと回したので、メモ。
変数の型推論と初期化
:=を使うことで、変数の型推論と代入が可能。
「短変数宣言(short variable declaration)」と言うらしい。また、この宣言はグローバルにはできず、関数内部が最大のスコープとなる。
a,b,c := 10, "10", false;
簡単にdeferが使える
TypeScriptでは、async/awaitが必須のdeferがこれだけで実現できるのは驚き。意味合い的には、try-catchのfinallyに近いなぁ。
deferは後入れ先出しで、最後にdeferしたものから順番に実行される。
package main
import "fmt"
func main() {
fmt.Println("start")
defer fmt.Println("end")
fmt.Println("middle")
}
固定長の配列は、array。可変長はslice
a := [3]int{1, 2, 3}
s := make([]int, 3)
for文は、rangeを使う
for index, value := range items {
fmt.Println(index, value)
}
for _ , v := range nums {
fmt.Println(i, v)
}
m = make(map[string]int)
for key, value := range m {
fmt.Println(key, value)
}
for i, r := range "GoLang" {
fmt.Println(i, string(r))
}
func counter() func() int {
x := 0
return func() int {
x++
return x
}
}
func main() {
c := counter()
fmt.Println(c())
fmt.Println(c())
fmt.Println(c())
}
cはxの内容を常に記憶してるわけですな。
Struct
- Goにはクラスがない。それは全然OK。クラスに紐づけた関数を作る場合は、レシーバーつき関数を作ることで代替するらしい。謎い。
- 参照渡しをしないと、Structで定義した変数の中身が変わらない。値渡しをするケースがあまり想像できないな。
type User struct {
Name string
}
func (u User) ChangeByValue() {
u.Name = "Value"
}
func (u *User) ChangeByPointer() {
u.Name = "Pointer"
}
func main() {
user := User{Name: "Init"}
user.ChangeByValue()
fmt.Println(user.Name)
user.ChangeByPointer()
fmt.Println(user.Name)
}
interface
implementsがない。シグネチャが同じなら同じTypeだとみなされるようだ。へー。
type Greeter interface {
Greet() string
}
type User struct {
Name string
}
func (u User) Greet() string {
return "Hello, " + u.Name
}
func PrintGreeting(g Greeter) {
fmt.Println(g.Greet())
}
func main() {
u := User{Name: "Taro"}
PrintGreeting(u)
}
- 変数がどの型かを示す構文がちゃんとある。
- Switch文でパターンマッチ的なこともやれるが、TSほど厳密じゃない。
var x any = "hello"
s, ok := x.(string)
fmt.Println(s, ok)
type Printable interface {
~int | ~string | ~[]int
}
func printType[T Printable](x T) {
switch v := any(x).(type) {
case int:
fmt.Println("int:", v)
case string:
fmt.Println("string:", v)
case []int:
fmt.Println("slice:", v)
default:
fmt.Println("impossible (for now)")
}
}
GoRoutine
- これが一番Go言語の強みに見える。
- チャネルという機構を使って、
Pub/Subする。
- チャネルはスレッドセーフで、同期される。受信しないとルーチンの実行で止まる。
- selectを使うことで、チャネルのパターンマッチができる。
ch := make(chan int)
go func() {
ch <- 10
}()
v := <-ch
fmt.Println(v)
func fetch(ctx context.Context, ch chan string) {
select {
case ch <- "done":
case <-ctx.Done():
fmt.Println("canceled:", ctx.Err())
}
}
Error
- 例外機構がないから、throwもない。
- 例外を投げることを禁じることで、何かしらエラーを表現する型を返す文化。ええやん。
- interfaceだから
Error()をメソッドで実装して任意の型を表現するのが良さそう。
type error interface {
Error() string
}
type ValidationError struct {
Field string
Message string
}
func (e *ValidationError) Error() string {
return fmt.Sprintf("validation failed: %s - %s", e.Field, e.Message)
}
まとめ
- Goの思想、すこ。例外なんていらん。
- 関数型にかぶれている僕は全部immutableにしたいときがありますが、Goは並行処理も強いし、レシーバーの値渡しがそれに近いので、基本、ミュータブルでよさげ。
- Goroutineが最も特徴的な言語仕様だ。なるほどなぁ。並行処理がマストなバックエンドでよー使われるのがわかる。
- あとは実際にWebアプリケーション作ってみて、API/DBなどの非同期I/Oをどうさばいていくのか、それぐらいかな。
素晴らしいまとめ
このエントリのお陰で更に解像度あがった。
zenn.dev