「このメソッドを持っていること」という条件の一覧。
type RowSink interface {
Write(row string) error
}
RowSink を実装する」という宣言は書かない。// Goらしくない:ファイル名を受け取り、自分で開ける
func CountRowsFile(path string) (int, error)
// Goらしい:「読めるもの」を受け取る
func CountRows(r io.Reader) (int, error)
type StdoutSink struct{}
func (s *StdoutSink) Write(row string) error {
fmt.Println(row)
return nil
}
var _ RowSink = (*StdoutSink)(nil) // Write が足りなければ、ここでコンパイルエラー
メソッドを1つも要求しないインタフェース interface{} の別名。どんな値でも入る。
map[string]any で受けるany やインタフェースの箱から、中身を「これは〇〇のはず」と言って取り出す書き方。
var box any = "カレー"
s, ok := box.(string) // 当たり:s = "カレー", ok = true
n, ok := box.(int) // 外れ:n = 0, ok = false。止まらずに進む
var box any = "カレー"
n := box.(int) // 1つで受けると、外れたら panic
// panic: interface conversion: interface {} is string, not int
外から届くデータは、ok 付きで受けて外れたときの処理を書く。
n, ok := box.(int)
if !ok {
return fmt.Errorf("int ではない: %T", box)
}
候補が何種類もあるときは型スイッチ。各 case の中では、x がその型として使える。
func describe(v any) string {
switch x := v.(type) {
case string:
return "文字列:" + x
case float64:
return fmt.Sprintf("数値:%g", x)
default:
return fmt.Sprintf("その他:%T", x)
}
}
describe(7) // その他:int
Minutes(n) のような型変換とは別物。型変換は値を作り変える。型アサーションは何も作り変えず、中身が本当にその型か確かめて取り出すだけ。
自分で設計するコードなら、ほとんど書かない。書くのは、相手が any やインタフェースの箱で渡してくるとき。
| 場面 | 頻度 |
|---|---|
自分の設計で any を使う | まれ。避ける |
ライブラリが any を返してくる(BigQuery の bigquery.Value、形の決まらない JSON、ctx.Value) | ときどき |
エラーの種類で処理を分ける(errors.As) | よくある |
var syntaxErr *json.SyntaxError
if errors.As(err, &syntaxErr) {
// JSON の書き方が壊れていたときだけの処理
}
errors.As は「この error の中身は *json.SyntaxError か」を確かめる関数で、やっていることは型アサーションと同じ。書く側に回ることは少ないので、読んだときに意味がわかれば足りる。
インタフェースの中に別のインタフェース名を書くと、両方のメソッドを要求するインタフェースになる。
type Fetcher interface {
Fetch(table string) ([]string, error)
}
type Loader interface {
Load(table string, rows []string) error
}
type FetchLoader interface { // 合成
Fetcher
Loader
}
func Preview(f Fetcher, table string) string // 読むだけ → Fetcher だけ要求する
func Copy(fl FetchLoader, from, to string) error // 読んで書く → 合成したものを要求する
同じ名前のメソッドを持つ型を何種類か作っておき、どれを使うかを外で選んで渡す。使う側は、どの型が来たかを知らないまま動く。
type fakeSink struct{
got []string
}
func (f *fakeSink) Write(row string) error {
f.got = append(f.got, row) // 受け取ったものを取っておく
return nil
}
// 使う側は RowSink しか知らない
func Ingest(sink RowSink, rows []string) error {
for _, r := range rows {
err := sink.Write(r)
if err != nil {
return err
}
}
return nil
}
Ingest(&StdoutSink{}, rows) // 本番:本当に書く
f := &fakeSink{}
Ingest(f, rows) // テスト:f.got に渡された行が残る
このサイトのノートから、知りたいことが書いてあるページを探して案内するよ。
AI の案内はまちがうことがあるよ。中身はリンク先のノートで確かめてね。