ブログに戻る

Go の func キーワード:関数、メソッド、クロージャ

Go の func キーワードの仕組みを解説します。関数のシグネチャ、複数の戻り値と名前付きの結果、可変長引数の関数、値レシーバーとポインタレシーバー、関数値、クロージャとループ変数、functional options、ジェネリック関数、main と init を扱います。

Go の func キーワード:関数、メソッド、クロージャ

Go プログラムの振る舞いは、すべて func キーワードで宣言します。func parse(body []byte) (Request, error) は関数です。func (s *Server) Start() error は、名前の前にレシーバーがあるのでメソッドです。名前のない func(w http.ResponseWriter, r *http.Request) { ... } は関数リテラルです。関数リテラルは周囲の変数をキャプチャでき、Go ではこれでクロージャを書きます。1 つのキーワードで 3 つすべてを扱い、パラメータと結果のルールはどれでも同じです(Go spec)。

要約

  • 関数宣言は func name(params) results { body } の形です。同じ型のパラメータが続く場合は、clamp(n, lo, hi int) のように型を 1 回だけ書けます。
  • Go にはオーバーロードもデフォルト引数もありません。同じ名前の関数を 2 つ宣言すると redeclared in this block で失敗します。別の名前、オプション用の struct、または functional options を使います。
  • 関数は複数の値を返せます。Go は (T, error) の組で失敗を伝えます。不要な結果は _ で捨てます。
  • 名前付きの結果を使うと、関数が何を返すかがわかりやすくなります。defer したクロージャが、関数を抜けるときに err へ情報を付け加えることもできます。
  • ...T を使うと可変長引数の関数になります。関数の中ではそのパラメータは []T で、parts... と書けば slice を展開して渡せます。
  • メソッドはレシーバーを持つ関数です。メソッドが値を変更する場合や、型が sync.Mutex を持つ場合はポインタレシーバーを使います。
  • ポインタレシーバーのメソッドは、値型のメソッドセットに含まれません。そのため ServeHTTP を持つのが *API だけなら、API{} は http.Handler を満たしません。
  • 関数は値です。変数に格納したり、slices.SortFunc や http.HandleFunc に渡したりできます。比較できるのは nil とだけです。
  • 関数リテラルは、変数のコピーではなく変数そのものをキャプチャします。Go 1.22 からはループの反復ごとに独自のループ変数が作られるので、ループの中で起動した goroutine は期待どおりの値を参照します。
  • functional options(NewServer(addr, opts ...Option))を使うと、コンストラクターに省略可能な設定を持たせられます。Go 1.27 からは、func (c *Client) GetJSON[T any](path string) (T, error) のように、メソッドも独自の型パラメータを宣言できます。

Go で関数を宣言するには?

func、名前、パラメータリスト、結果の型の順に書きます。典型的な小さな関数として、トークンのパーサーを見てみます。

example.gogo
package main

import (
	"errors"
	"fmt"
	"strings"
)

func parseBearer(header string) (string, error) {
	token, ok := strings.CutPrefix(header, "Bearer ")
	if !ok || token == "" {
		return "", errors.New("missing bearer token")
	}
	return token, nil
}

func clamp(n, lo, hi int) int {
	return max(lo, min(n, hi))
}

func main() {
	token, err := parseBearer("Bearer eyJhbGciOi")
	fmt.Println(token, err)

	_, err = parseBearer("Basic dXNlcjpwYXNz")
	fmt.Println(err)

	fmt.Println(clamp(250, 1, 100))
}
example.texttext
eyJhbGciOi <nil>
missing bearer token
100

型は名前の後ろに書きます。パラメータでも結果でも同じです。同じ型のパラメータが続くときは型を 1 回だけ書けるので、clamp(n, lo, hi int) は 3 つの int を受け取ります。名前のない結果が 1 つだけなら括弧は不要です。結果が 2 つ以上なら括弧が必要です。

関数名は、Go のほかの識別子と同じ可視性のルールに従います。ParseBearer ならエクスポートされ、ほかのパッケージから呼び出せます。一方、parseBearer は自分のパッケージの中にとどまります。エクスポートされた名前については Go の package キーワードで詳しく解説しています。

Go に関数のオーバーロードやデフォルト引数はあるか?

どちらもありません。パラメータがどうであれ、1 つのパッケージには同じ名前の関数を 1 つしか置けません。

example.gogo
func dial(addr string) error { return nil }

func dial(addr string, timeout time.Duration) error { return nil }
example.texttext
./main.go:7:6: dial redeclared in this block
	./main.go:5:6: other declaration of dial

デフォルト値は、型チェックが始まるより前に、パーサーの段階で失敗します。

example.texttext
./main.go:5:46: syntax error: unexpected = in parameter list; possibly missing comma or )

標準ライブラリを見ると、Go が代わりにどうしているかがわかります。net.Dial と net.DialTimeout は、振る舞いごとに別の名前を付けています。http.Server は、ゼロ値のフィールドが「デフォルトを使う」を意味する struct なので、必要なフィールドだけを設定します。省略可能な設定が多いコンストラクターには、後で説明する functional options を使います。どの関数が実行されるかは、呼び出し箇所の名前を見ればわかります。

Go の複数の戻り値はどう使うのか?

関数はいくつでも値を返せます。呼び出し側は多値の代入で受け取ります。最もよくある形は、上の parseBearer のような、結果と error の組です。呼び出し側は結果に触れる前にエラーを確認します。結果を 2 つ返す呼び出しを値が 1 つしか入らない場所に書くと、コンパイラがエラーにします。

値の 1 つが不要なときは、ブランク識別子 _ に代入します。完全に省くことはできません。token := parseBearer(h) は assignment mismatch: 1 variable but parseBearer returns 2 values で失敗します。

名前付きの結果パラメータはいつ使うべきか?

名前付きの結果では、各結果に名前を付けて、ゼロ値で始まる変数として宣言します。値を書かない return は bare return と呼ばれ、その時点で変数に入っている値を返します。

example.gogo
func splitHostPort(addr string) (host, port string) {
	for i := len(addr) - 1; i >= 0; i-- {
		if addr[i] == ':' {
			host, port = addr[:i], addr[i+1:]
			return
		}
	}
	host = addr
	return
}

splitHostPort("api.levelupgo.dev:443") は api.levelupgo.dev と 443 を返します。ここで名前が役に立つのは、string の結果が 2 つ並ぶだけでは、どちらがどちらなのかまったくわからないからです。Go Code Review Comments では、bare return はこのような短い関数に限って使うよう勧めています。60 行ある関数では、bare return は何が返されるのかを隠してしまいます。

名前付きの結果がいちばん役に立つのは、defer した関数で、呼び出し側に返る前にエラーを書き換えたい場合です。

example.gogo
func loadConfig(path string) (cfg Config, err error) {
	defer func() {
		if err != nil {
			err = fmt.Errorf("load config %s: %w", path, err)
		}
	}()

	data, err := os.ReadFile(path)
	if err != nil {
		return Config{}, err
	}
	err = json.Unmarshal(data, &cfg)
	return cfg, err
}
example.texttext
load config /etc/levelupgo/config.json: open /etc/levelupgo/config.json: no such file or directory

どの return も、まず値を cfg と err に代入し、その後で defer した関数が実行されます。err は名前付きの結果なので、どの出口から抜けても、クロージャ 1 か所でラップできます。名前がなければ、defer した関数から戻り値に触れる方法はありません。

Go の可変長引数の関数とは?

最後のパラメータを ...T と書くと、型 T の引数を 0 個以上受け取れます。関数の中では、ただの []T です。

example.gogo
func buildURL(base string, segments ...string) string {
	return strings.TrimSuffix(base, "/") + "/" + strings.Join(segments, "/")
}

func main() {
	fmt.Println(buildURL("https://api.levelupgo.dev/", "v1", "users", "42"))
	fmt.Println(buildURL("https://api.levelupgo.dev"))

	parts := []string{"v1", "orders", "1001"}
	fmt.Println(buildURL("https://api.levelupgo.dev", parts...))
}
example.texttext
https://api.levelupgo.dev/v1/users/42
https://api.levelupgo.dev/
https://api.levelupgo.dev/v1/orders/1001

parts... は、既存の slice をコピーせずに可変長引数として渡します。2 つの形は混ぜられないので、buildURL(base, "v2", parts...) は失敗します。

example.texttext
./main.go:18:58: too many arguments in call to buildURL
	have (string, string, []string...)
	want (string, ...string)

標準ライブラリには可変長引数の関数がたくさんあります。fmt.Println(a ...any)、append(s, elems...)、errors.Join(errs ...error)、そして slog.Info(msg, args ...any) のキーと値の組は、どれもこの形です。

Go のメソッドはどう使うのか?

メソッドはレシーバーを持つ関数で、レシーバーは func と名前の間の括弧に書きます。レシーバーとして最も多いのは struct です。struct については、フィールドから埋め込みまで Go の struct キーワードで解説しています。レシーバーは型そのものでも、その型へのポインタでもかまいません。どちらを選ぶかによって、メソッドが呼び出し側の値を操作するのか、コピーを操作するのかが決まります。値レシーバーを使ったレートリミッターで違いを見てみます。

example.gogo
type RateLimiter struct {
	limit int
	used  int
}

func (r RateLimiter) Allow() bool {
	if r.used >= r.limit {
		return false
	}
	r.used++
	return true
}

func main() {
	limiter := RateLimiter{limit: 2}
	for range 4 {
		fmt.Print(limiter.Allow(), " ")
	}
	fmt.Println()
}
example.texttext
true true true true

このリミッターは何も止めません。呼び出しのたびに limiter の新しいコピーが渡され、メソッドはコピーの used を増やしてから捨てます。レシーバーを func (r *RateLimiter) Allow() bool に変えると、メソッドがもとの値を更新するようになり、出力は true true false false になります。(&limiter).Allow() と書く必要はありません。変数がアドレス指定可能であれば、Go がアドレスを取ってくれます。

ポインタレシーバーはいつ使うべきか?

次のいずれかに当てはまる場合は、ポインタレシーバーを使います。

  • 上の Allow のように、メソッドがレシーバーを変更する。
  • 型が sync.Mutex など、コピーしてはいけない値を含んでいる。
  • struct が大きく、呼び出しのたびにコピーするほうが、ポインタをたどるよりコストがかかる。
値レシーバー func (r T)ポインタレシーバー func (r *T)
呼び出し側の値を変更するいいえ、コピーを操作するはい
sync.Mutex フィールドがあっても安全いいえ、ロックがコピーされるはい
T の変数で呼び出せるはいはい、Go がアドレスを取る
T のメソッドセットに含まれるはいいいえ
*T のメソッドセットに含まれるはいはい

mutex の例は、go vet が検出するバグです。

example.gogo
type SessionCache struct {
	mu       sync.Mutex
	sessions map[string]string
}

func (c SessionCache) Get(token string) (string, bool) {
	c.mu.Lock()
	defer c.mu.Unlock()
	user, ok := c.sessions[token]
	return user, ok
}
example.texttext
main.go:13:9: Get passes lock by value: app.SessionCache contains sync.Mutex

呼び出しのたびに mutex のコピーをロックするので、Get を呼ぶ 2 つの goroutine が互いを待つことはなく、ロックは何も守っていません。time.Time や UserID のような小さなイミュータブルな型なら、値レシーバーで問題ありません。Code Review Comments は、1 つの型で 2 種類のレシーバーを混ぜないようにも求めています。ポインタレシーバーが必要なメソッドが 1 つでもあれば、すべてのメソッドをポインタレシーバーにします(Go wiki)。

型が interface を実装しないのはなぜか?

よくある原因はポインタレシーバーです。値型 T のメソッドセットには、レシーバーが T のメソッドだけが含まれます。*T のメソッドセットには両方が含まれます。そのため ServeHTTP がポインタレシーバーを持つ場合、http.Handler になるのは *API だけです。

example.gogo
type API struct {
	version string
}

func (a *API) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintln(w, a.version)
}

func main() {
	var h http.Handler = API{version: "v1"}
	_ = h
}
example.texttext
./main.go:17:23: cannot use API{…} (value of struct type API) as http.Handler value in variable declaration: API does not implement http.Handler (method ServeHTTP has pointer receiver)

&API{version: "v1"} と書けば直ります。このルールがあるのは、interface が値のコピーを保持できるからです。そのコピーに対してポインタメソッドを呼ぶと、呼び出し側からは決して見えないものを変更してしまいます。これをコンパイル時に確認する var _ http.Handler = (*T)(nil) パターンは Go の var キーワードで紹介しています。メソッドセットと暗黙的な実装については Go の interface キーワードで詳しく解説しています。

メソッド値とメソッド式とは?

括弧なしの srv.health はメソッド値です。レシーバーがすでに束縛された関数なので、ただの関数を受け取る場所にそのまま渡せます。

example.gogo
type Server struct {
	version string
}

func (s *Server) health(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "ok %s\n", s.version)
}

func main() {
	srv := &Server{version: "1.4.2"}

	mux := http.NewServeMux()
	mux.HandleFunc("GET /health", srv.health)

	rec := httptest.NewRecorder()
	mux.ServeHTTP(rec, httptest.NewRequest("GET", "/health", nil))
	fmt.Print(rec.Body.String())

	handle := (*Server).health
	fmt.Printf("%T\n", handle)
}
example.texttext
ok 1.4.2
func(*main.Server, http.ResponseWriter, *http.Request)

多くの Go サーバーは、この方法でルートを登録しています。ハンドラーはデータベース、ロガー、設定を保持する struct のメソッドで、mux.HandleFunc は束縛されたメソッド値を受け取ります。(*Server).health はメソッド式です。メソッド式ではレシーバーが普通の第 1 引数になるので、どのメソッドを呼ぶかを実行時にテーブルで選ぶ場合に便利です。

Go の関数値と関数型とは?

Go では関数は値です。変数に代入したり、struct のフィールドや map に格納したり、引数として渡したり、ほかの関数から返したりできます。関数値の型は、そのシグネチャです。

example.gogo
type Order struct {
	ID    int64
	Total int64
}

func main() {
	orders := []Order{{ID: 1, Total: 4999}, {ID: 2, Total: 1250}, {ID: 3, Total: 8900}}

	slices.SortFunc(orders, func(a, b Order) int {
		return cmp.Compare(b.Total, a.Total)
	})
	fmt.Println(orders)

	bigOrder := func(o Order) bool { return o.Total > 5000 }
	fmt.Println(slices.IndexFunc(orders, bigOrder))
	fmt.Printf("%T\n", bigOrder)
}
example.texttext
[{3 8900} {1 4999} {2 1250}]
0
func(main.Order) bool

slices.SortFunc は自分で定義した struct をどう並べればよいかを知らないので、比較関数を渡します。同じ考え方は標準ライブラリ全体に見られます。http.HandleFunc はハンドラー関数を、strings.FieldsFunc は区切り文字の判定関数を、sync.OnceValue は一度だけ実行する関数を受け取ります。

関数型のゼロ値は nil で、nil の関数を呼ぶと panic します。Go が関数に許す比較も、nil との比較だけです。

example.texttext
./main.go:9:5: invalid operation: health == ready (func can only be compared to nil)

2 つの関数を比較できないので、関数は map のキーにもなれません。ハンドラーの集合が必要なときは、代わりに名前をキーにします。

シグネチャには type で独自の名前を付けることもでき、名前付きの関数型はメソッドを持てます。http.HandlerFunc がただの関数を http.Handler に変えているのはこの仕組みです。Go の type キーワードでは、RetryPolicy の例と合わせてこれを解説しています。

Go のクロージャはどう動くのか?

関数リテラルは、それを囲む関数の変数を使えます。変数はコピーされません。変数そのものがキャプチャされるので、どちら側で変更しても両方から見えます。リトライ用のヘルパーで確認します。

example.gogo
var errUnavailable = errors.New("503 service unavailable")

func retry(attempts int, fn func() error) error {
	var err error
	for range attempts {
		if err = fn(); err == nil {
			return nil
		}
	}
	return err
}

func main() {
	calls := 0
	err := retry(3, func() error {
		calls++
		return errUnavailable
	})
	fmt.Println(calls, err)
}
example.texttext
3 503 service unavailable

calls は main の変数ですが、リテラルの中で増やしているので、後で main から見ると 3 になっています。キャプチャされた変数は、それを参照するクロージャがある限り、囲む関数が戻った後も生き続けます。必要な場合、コンパイラはそうした変数をヒープに移します。

HTTP ミドルウェアは、Go のコードで特によく見かけるクロージャの 1 つです。返されたハンドラーは、それを作った呼び出しの logger と next を保持します。

example.gogo
func logRequests(logger *slog.Logger, next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		next.ServeHTTP(w, r)
		logger.Info("request", "method", r.Method, "path", r.URL.Path)
	})
}

ハンドラーをラップして DELETE /sessions/42 を送ると、level=INFO msg=request method=DELETE path=/sessions/42 がログに出力されます。logRequests を呼ぶたびに、独自の next を持つ別々のクロージャができるので、ルートごとに異なるハンドラーをラップできます。このようなクロージャで組み立てる本番用のスタック全体(順序、panic からの復旧、テストを含む)については、Go ミドルウェアのベストプラクティスを参照してください。

短い書き方も 2 つ、よく目にします。defer func() { ... }() は、関数が戻るときにクロージャを実行します。loadConfig の例でエラーに情報を付け加えたのも、recover を呼ぶのもこの形です。go func() { ... }() は、新しい goroutine でクロージャを起動します。末尾の () がリテラルを呼び出します。これを省くと、defer や go の式は関数呼び出しでなければならない、とコンパイラに指摘されます。

Go 1.22 でループ内のクロージャは直ったのか?

はい。Go 1.22 より前は、for ループのループ変数は 1 つだけで、すべての反復で共有されていました。クロージャがキャプチャするのも、その 1 つの変数でした。次のヘルスチェッカーは 3 つの goroutine を起動しますが、多くの場合、すべてが最後の URL を出力していました。

example.gogo
urls := []string{"/health", "/ready", "/metrics"}

var wg sync.WaitGroup
for _, url := range urls {
	wg.Add(1)
	go func() {
		defer wg.Done()
		fmt.Println("checking", url)
	}()
}
wg.Wait()

go.mod に go 1.21 と書いた状態では、3 回実行してすべての行が checking /metrics と出力され、go vet は loop variable url captured by func literal と警告しました。go 1.22 以降では反復ごとに独自の url があるので、同じコードが 3 つのパスをすべてチェックします。出力の順番は goroutine が終わった順です(Go blog)。この変更は go.mod の go 行に結び付いているので、古いモジュールはバージョンを上げるまで古い振る舞いのままです。ループの中で url := url とコピーする以前の書き方はもう必要なく、go fix の forvar モダナイザーがこれを削除します。range の値を変更しても slice が変わらない理由など、ループ変数のほかのルールは Go の for キーワードで解説しています。

Go の functional options とは?

functional options は、可変長引数とクロージャを組み合わせて、オーバーロードやデフォルト引数を使わずに、コンストラクターに省略可能な設定を持たせる手法です。各オプションは、作成中の値を変更する関数です。

example.gogo
type Server struct {
	addr         string
	readTimeout  time.Duration
	maxBodyBytes int64
	logger       *slog.Logger
}

type Option func(*Server)

func WithReadTimeout(d time.Duration) Option {
	return func(s *Server) {
		s.readTimeout = d
	}
}

func WithMaxBodyBytes(n int64) Option {
	return func(s *Server) {
		s.maxBodyBytes = n
	}
}

func NewServer(addr string, opts ...Option) *Server {
	s := &Server{
		addr:         addr,
		readTimeout:  5 * time.Second,
		maxBodyBytes: 1 << 20,
		logger:       slog.Default(),
	}
	for _, opt := range opts {
		opt(s)
	}
	return s
}

func main() {
	a := NewServer(":8080")
	b := NewServer(":8080", WithReadTimeout(30*time.Second), WithMaxBodyBytes(10<<20))
	fmt.Println(a.readTimeout, a.maxBodyBytes)
	fmt.Println(b.readTimeout, b.maxBodyBytes)
}
example.texttext
5s 1048576
30s 10485760

NewServer(":8080") はすべての設定でデフォルトを使い、呼び出し側は変更するものだけを指定します。後からオプションを追加しても、既存の呼び出しは壊れません。gRPC の grpc.NewServer(opts ...ServerOption) や、多くのデータベースクライアントがこのパターンを使っています。設定が 2 つか 3 つしかない型なら、ただの config struct のほうが単純で読みやすいです。オプションは、設定の一覧が増え続ける場合に使います。

Go のジェネリック関数はどう使うのか?

関数は、名前とパラメータの間の角括弧に型パラメータを宣言できます。次の Map は、どんな入力型と出力型でも使えます。

example.gogo
func Map[T, U any](items []T, fn func(T) U) []U {
	out := make([]U, 0, len(items))
	for _, item := range items {
		out = append(out, fn(item))
	}
	return out
}

emails := Map(users, func(u User) string { return u.Email })

コンパイラは引数から T を User、U を string と推論するので、呼び出しに角括弧は不要です。標準ライブラリの slices、maps、cmp パッケージは、このようなジェネリック関数でできています。

Go 1.27 より前は、メソッドは独自の型パラメータを宣言できませんでした。使えたのはレシーバーの型が持つ型パラメータだけです。Go 1.27 ではこの制限が取り除かれ、呼び出し側が求める型にデコードするジェネリックメソッドをクライアントに持たせられるようになりました(Go 1.27 リリースノート)。

example.gogo
type Client struct {
	baseURL string
	http    *http.Client
}

func (c *Client) GetJSON[T any](path string) (T, error) {
	var out T
	resp, err := c.http.Get(c.baseURL + path)
	if err != nil {
		return out, err
	}
	defer resp.Body.Close()
	err = json.NewDecoder(resp.Body).Decode(&out)
	return out, err
}

u, err := c.GetJSON[User]("/users/1")

ユーザーを 1 人返すテストサーバーに対して実行すると、{1 [email protected]} <nil> と出力されます。ここで型引数を明示する必要があるのは、"/users/1" からは T が何なのかをコンパイラが判断できないからです。ジェネリックメソッドとその制限については、interface のメソッドを満たせない理由も含めて、Go 1.27 の新機能で解説しています。

Go の main と init とは?

特別な関数名が 2 つあります。package main の func main() はプログラムの開始地点で、これが戻るとプログラムは終了します。main は引数を取らず、何も返しません。

example.texttext
./main.go:9:6: func main must have no arguments and no return values

コマンドライン引数は os.Args か flag パッケージから受け取り、終了コードは os.Exit で指定します。package main については Go の package キーワードで詳しく解説しています。

func init() は、パッケージの変数が初期化された後、main より前に自動的に実行されます。1 つのパッケージに init 関数を複数置けます。1 つのファイルに複数あってもかまいません。実行されるのは記述された順です。init を自分で呼び出すことはできません。自分のコードで init() と書くと undefined: init で失敗します。init の用途の大半は登録で、たとえばデータベースドライバーが自身を database/sql に登録します。初期化の順序や、パッケージレベルの var のほうがわかりやすい場合については、Go の var キーワードで解説しています。

LevelUpGo で学ぶ

LevelUpGo では、ブラウザ上で本物の Go コードを実行する演習を通じて Go を学べます。Go Basics では、関数、複数の戻り値、値としてのエラーを学びます。Go Language Deep Dives では、可変長引数の関数、名前付きの結果、panic と recover をさらに掘り下げます。Training Ground には、シグネチャ、メソッド、クロージャについてコースの外で練習できる、短い単独の演習があります。残り 24 個の予約語については、Go のキーワード一覧:全 25 個の予約語を解説をご覧ください。

よくある質問

Go で func はキーワードですか?

はい。func は Go の 25 個の予約語の 1 つなので、名前には使えません。関数とメソッドを宣言するキーワードで、関数リテラルと関数型もこのキーワードで始まります。関数を保持する変数が必要なとき、Go のコードではたいてい fn という名前を付けます。

Go は関数のオーバーロードやデフォルト引数をサポートしていますか?

どちらもサポートしていません。1 つのパッケージで各名前は 1 回しか宣言できないので、2 つ目の func dial(...) は dial redeclared in this block で失敗します。また、パラメータリストにデフォルト値を書くと構文エラーになります。Go では代わりに、net.Dial と net.DialTimeout のような別々の名前、http.Server のようにゼロ値のフィールドが「デフォルトを使う」を意味する config struct、または functional options を使います。

Go では値レシーバーとポインタレシーバーのどちらを使うべきですか?

メソッドがレシーバーを変更する場合、型が sync.Mutex などを含む場合、struct が大きい場合はポインタレシーバーを使います。time.Time のように値として振る舞う小さな型には値レシーバーを使います。1 つの型のメソッドは、すべて同じ種類のレシーバーにそろえます。ポインタメソッドがメソッドセットに含まれるのは *T だけです。interface を満たすかどうかに関わるので注意してください。

Go の func() とは何ですか?

func() は、引数を取らず何も返さない関数の型です。func(context.Context) error のように、名前なしで書いたシグネチャはすべて関数型です。こうした型は、コールバック、struct のフィールド、sync.OnceFunc(f func()) のようなパラメータに使います。本体を持つ func() { ... } は、その型の関数リテラルです。

Go で関数どうしを比較できますか?

nil とだけ比較できます。health == ready は func can only be compared to nil で失敗し、同じ理由で関数型は map のキーになれません。ハンドラーを検索したいときは、名前をキーにした map[string]http.HandlerFunc に格納します。

出典

シニアエンジニアのように Go を書く

ブラウザで学べるインタラクティブなレッスン。最初のレッスンは無料です。

無料レッスンを試すまたは無料アカウントを作成