- 投稿日:2020-02-07T23:23:11+09:00
【errorgroup】Golangの並行処理内でerrorハンドリングをする実装で詰まった
概要
Golangの並行処理をしつつerrorハンドリングをしたい場合は、errorgroupパッケージを使うことができます。
GoDocはこちら
https://godoc.org/golang.org/x/sync/errgroupGoDocに書いてあることが全てです。
この記事で言いたきことは、とにかくドキュメントを読め、の1点です。とりあえず実装してみる(期待していない値が返ってくる例)
import "golang.org/x/sync/errgroup" var g errgroup.Group for _, url := range urls { g.Go(func() error { // Fetch the URL. resp, err := http.Get(url) if err == nil { resp.Body.Close() } return err }) }こんな感じで書くと、urlsの末尾の要素のみ参照してしまい、期待通りに動作しません。なぜなのか。
GoDocを読むと以下のようにサンプルコードが書いてあります。期待通りに動作する例
import "golang.org/x/sync/errgroup" var g errgroup.Group for _, url := range urls { // Launch a goroutine to fetch the URL. url := url // https://golang.org/doc/faq#closures_and_goroutines <- この代入の意味なんぞ :thinking_face: g.Go(func() error { // Fetch the URL. resp, err := http.Get(url) if err == nil { resp.Body.Close() } return err }) }コメントに記載したリンク( https://golang.org/doc/faq#closures_and_goroutines )に飛ぶと以下の文とサンプルコード発見
Even easier is just to create a new variable, using a declaration style that may seem odd but works fine in Go:
このように記述するとより簡単に書くことができます。
This behavior of the language, not defining a new variable for each iteration, may have been a mistake in retrospect. It may be addressed in a later version but, for compatibility, cannot change in Go version 1.
新しい値をイテレーションの中で定義するのは、振り返ると言語仕様として間違っていたが、Goの1系では直せない。
まとめ
errorgroupではこのイテレーションの方法を取るしかなさそう。
ということで、イテレーションの中でerrorgroupを利用する場合はご注意下さい。
- 投稿日:2020-02-07T23:23:11+09:00
【errorgroup】Golangの並行処理中にerrorハンドリングをする実装で詰まった
概要
Golangの並行処理をしつつerrorハンドリングをしたい場合は、errorgroupパッケージを使うことができます。
GoDocはこちら
https://godoc.org/golang.org/x/sync/errgroupGoDocに書いてあることが全てです。
この記事で言いたきことは、とにかくドキュメントを読め、の1点です。とりあえず実装してみる(期待していない値が返ってくる例)
import "golang.org/x/sync/errgroup" var g errgroup.Group for _, url := range urls { g.Go(func() error { // Fetch the URL. resp, err := http.Get(url) if err == nil { resp.Body.Close() } return err }) }こんな感じで書くと、urlsの末尾の要素のみ参照してしまい、期待通りに動作しません。なぜなのか。
GoDocを読むと以下のようにサンプルコードが書いてあります。期待通りに動作する例
import "golang.org/x/sync/errgroup" var g errgroup.Group for _, url := range urls { // Launch a goroutine to fetch the URL. url := url // https://golang.org/doc/faq#closures_and_goroutines <- この代入の意味はなんだろう :thinking_face: g.Go(func() error { // Fetch the URL. resp, err := http.Get(url) if err == nil { resp.Body.Close() } return err }) }コメントに記載したリンク( https://golang.org/doc/faq#closures_and_goroutines )に飛ぶと以下の文とサンプルコード発見
Even easier is just to create a new variable, using a declaration style that may seem odd but works fine in Go:
このように記述するとより簡単に書くことができます。
This behavior of the language, not defining a new variable for each iteration, may have been a mistake in retrospect. It may be addressed in a later version but, for compatibility, cannot change in Go version 1.
新しい値をイテレーションの中で定義するのは、振り返ると言語仕様として間違っていたが、Goの1系では直せない。
まとめ
errorgroupではこのイテレーションの方法を取るしかなさそう。
ということで、イテレーションの中でerrorgroupを利用する場合はご注意下さい。
- 投稿日:2020-02-07T21:43:25+09:00
GoとGinをプロジェクトで使ってみて思ったこと
はじめに
k.s.ロジャースの西谷です。
今回はGo言語とGinを使ってプロジェクトの開発を行いました。
実務利用は初めてで、このときに感じた便利な機能や躓いた箇所について書いてみようと思います。もし、間違いやより良い実装があればコメントにて教えて頂けたらと思います。
開発環境
- 言語: Go 1.13.3
- フレームワーク: Gin
便利だと思った所
リクエストのバリデーション
Ginではリクエストオブジェクトの定義に
binding
を書くことでバリデーションを実施できます。
特殊パターン以外はリクエストに直接書くことができるので、直感的だと思います。以下のように書くと、
req
はID
の値は必ず存在し、Age
は0以上となります。request.gotype CreateUser struct { ID string `json:"id" binding:"required"` Name string `json:"name"` Age int `json:"age" binding:"min=0"` }controller.gofunc (h *HogeController) Create(c *gin.Context) { var req request.CreateUser // このタイミングでバリデーションが実行される err := c.ShouldBindJSON(&req) if err != nil { // リクエストが間違っている時の処理 return } }バリデーションをカスタマイズしたい場合は以下のようにします。
// 日付がyyyy-mm-ddのフォーマットになっているか func DatetimeValidator(field validator.FieldLevel) bool { value := field.Field().String() _, err := time.Parse("2006-01-02", value) if err != nil { return false } return true } func main() { if v, ok := binding.Validator.Engine().(*validator.Validate); ok { // is_dateのタグでDatetimeValidatorを登録 v.RegisterValidation("is_date", DatetimeValidator) } }request.gotype CreateUser struct { ID string `json:"id" binding:"required"` Name string `json:"name"` Age int `json:"age" binding:"min=0"` Date time.Time `json:"date" binding:"is_date"` }詳細はこちらを参照ください。
エラー処理周り
Goでは必ず処理直後にエラー処理を行います。
こちらはエラー処理が煩雑など賛否両論ではありますが、私の意見としては複雑なtry, catch
が乱立するよりはいいかなと思いました。err := HogeFunc() if err != nil { log.Fatal(err) }コード品質を気にしているチームであれば
try, catch
が乱立することはないかなと思います。
しかし、プロジェクトによってはコードを一切管理しておらず、例外が理解できないほど複雑になることもあるので、Goを利用していれば最悪ケースを防げるかなと思います。躓いたところ
APIリクエストでのnullの扱い
REST APIのリクエストで未入力と初期値(0や空文字など)を区別したい場合があります。
これを実装する場合は変数をポインタにしてnil
を持たせる必要があります。実際に起きたバグとしては、PATCH関連のAPIでリクエストオブジェクトを通常の型で定義したところ、未入力フィールドがnullで上書きされる問題でした。
例えば次の定義に対して、ユーザ名だけを変更しようとしてリクエストを行うと
Age
が0で上書きされます。request.gotype UpdateUser struct { ID string `json:"id"` Name string `json:"name"` Age int `json:"age"` }PATCH_USER{ "id": "xxxxxxxxx", "name": "hogehoge" }これに対して、nullが投げられる可能性のあるフィールドをポインタで定義することで解決できます。
request.gotype UpdateUser struct { ID string `json:"id"` Name *string `json:"name"` Age *int `json:"age"` }これで未入力の場合は
nil
となりますので、サーバ側で保存対象を区別できます。
しかし、リクエストでnullで上書きしたい場合は、nullをリクエストで投げても更新されなくなります。
(定義済みのデータを未定義
に更新出来ることが特殊だと思うので、一部の言語が特殊だと考えています)ジェネリクスがない
型は違うが同じような処理をしたい場合はたまにあります。
現状ですと、interface{}
で頑張るしかないようです。 1func AddOne(v interface{}) interface{} { switch v.(type) { case int: return v.(int) + 1 case float64: return v.(float64) + 1.0 default: return nil } } func main() { a := AddOne(1.0) fmt.Printf("%f", a.(float64)) }おわりに
今回はGoを実際にプロダクトで使ってみて感じたことについて説明しました。
設計や工夫次第で解消できる箇所もあるかなと思うので、これからも色々試していきたいと思います。間違い等あればご指摘頂けたらと思います。
Wantedlyでもブログ投稿してます
Techブログに加えて会社ブログなどもやっているので、気になった方はぜひ覗いてみてください。
https://www.wantedly.com/companies/ks-rogers
- 投稿日:2020-02-07T19:22:55+09:00
geth(go-ethereum)のdifficultyを固定にする
記事の内容
ethereumをプライベートで使用する際にdifficultyを固定にする方法の紹介
※運用ベースでのテストはしてません環境
OS:CentOS7.4
golang:1.13.3
geth:1.9.11-unstablegoやgit、g++などコンパイルに必要となるツールはインストールされている前提で進めます。
difficultyの固定と動作検証(簡易)
gethの修正&ビルド
まずはgitからコードを落として、早速対象のコードを修正します。
ディレクトリは任意の場所で作業をします。$ cd /home $ git clone https://github.com/ethereum/go-ethereum.git $ vi consensus/ethash/consensus.go454行目から
consensus/ethash/consensus.go// calcDifficultyFrontier is the difficulty adjustment algorithm. It returns the // difficulty that a new block should have when created at time given the parent // block's time and difficulty. The calculation uses the Frontier rules. func calcDifficultyFrontier(time uint64, parent *types.Header) *big.Int { diff := new(big.Int) adjust := new(big.Int).Div(parent.Difficulty, params.DifficultyBoundDivisor) bigTime := new(big.Int) bigParentTime := new(big.Int) bigTime.SetUint64(time) bigParentTime.SetUint64(parent.Time) if bigTime.Sub(bigTime, bigParentTime).Cmp(params.DurationLimit) < 0 { diff.Add(parent.Difficulty, adjust) } else { diff.Sub(parent.Difficulty, adjust) } if diff.Cmp(params.MinimumDifficulty) < 0 { diff.Set(params.MinimumDifficulty) } periodCount := new(big.Int).Add(parent.Number, big1) periodCount.Div(periodCount, expDiffPeriod) if periodCount.Cmp(big1) > 0 { // diff = diff + 2^(periodCount - 2) expDiff := periodCount.Sub(periodCount, big2) expDiff.Exp(big2, expDiff, nil) diff.Add(diff, expDiff) diff = math.BigMax(diff, params.MinimumDifficulty) } // return diff return big1 }修正箇所はreturnで返却する値を計算結果からbig1(定数 = 1)に変更する。
コードを見る限り、この関数内を「return big1」だけにしても問題無いかなと思います。ビルドします。
$ make geth env GO111MODULE=on go run build/ci.go install ./cmd/geth >>> /usr/lib/golang/bin/go install -ldflags -X main.gitCommit=976a0f5558e20ed7cb7ba2cd68d7429d1ef01db9 -X main.gitDate=20200205 -v ./cmd/geth Done building. Run "./build/bin/geth" to launch geth.この様な結果になればビルド成功です。
gethの起動準備
ここからはほぼ、以下のサイトの内容になります。
https://book.ethereum-jp.net/first_use/connect_to_private_net.html
まずは、ジェネシスファイル(JSON)を作成します。
$ mkdir /home/eth_private_net $ cd /home/eth_private_net $ vi myGenesis.jsonmyGenesis.json{ "config": { "chainId": 15 }, "nonce": "0x0000000000000042", "timestamp": "0x0", "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", "extraData": "", "gasLimit": "0x8000000", "difficulty": "0x0000", "mixhash": "0x0000000000000000000000000000000000000000000000000000000000000000", "coinbase": "0x3333333333333333333333333333333333333333", "alloc": {} }ここではdifficultyを0にしています。
gethを動かしてみる
ジェネシスファイルの準備が出来たら、gethを動かしてみます。
$ cd /home/go-ethereum/build/bin $ ./geth --datadir /home/eth_private_net init /home/eth_private_net/myGenesis.json (略)問題なければ以下のログが出力されるはず INFO [02-07|00:26:16.907] Successfully wrote genesis state database=lightchaindata hash=9f02b9…c34631 $ ./geth --networkid "15" --nodiscover --datadir "/home/eth_private_net" console 2>> /home/eth_private_net/geth_err.log Welcome to the Geth JavaScript console! instance: Geth/v1.9.11-unstable-976a0f55-20200205/linux-amd64/go1.13.3 at block: 0 (Thu Jan 01 1970 00:00:00 GMT+0000 (UTC)) datadir: /home/eth_private_net modules: admin:1.0 debug:1.0 eth:1.0 ethash:1.0 miner:1.0 net:1.0 personal:1.0 rpc:1.0 txpool:1.0 web3:1.0マイニングを開始する
これからは「Geth Java Script console」の操作になります。
アカウントの作成、マイニング開始→マイニング終了までを操作してみます。
> eth.account undefined > eth.accounts [] > personal.newAccount("hogehoge01") "0x6918ef5c82e837fed798af3d61f62fda54151729" > miner.start() null > eth.blockNumber 15 > miner.stop() null >マイニング開始後、最新のブロック番号を取得すると「15」となっており、ブロックが作成されていることを確認できました。
※私は「miner.start()」をやってもマイニングが最初出来なかったのですが、仮想マシンに割り当てているメモリが不足していることが原因でした。
1GBだとメモリ不足となりマイニングされず、4GBに増やすとマイニングされました。
調べてみるとマイニングには2GB以上必要というような情報を見つけました。ブロック情報を確認する
ジェネシスブロックからブロックの情報をいくつか確認してみます。
> eth.getBlock(0) { difficulty: 0, extraData: "0x", gasLimit: 134217728, gasUsed: 0, hash: "0x9f02b99a864192b04587a44b20a9776cabf49bc664829cc7886c297155c34631", logsBloom: "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", miner: "0x3333333333333333333333333333333333333333", mixHash: "0x0000000000000000000000000000000000000000000000000000000000000000", nonce: "0x0000000000000042", number: 0, parentHash: "0x0000000000000000000000000000000000000000000000000000000000000000", receiptsRoot: "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", sha3Uncles: "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", size: 505, stateRoot: "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", timestamp: 0, totalDifficulty: 0, transactions: [], transactionsRoot: "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", uncles: [] } > eth.getBlock(1) { difficulty: 1, extraData: "0xd88301090b846765746888676f312e31332e33856c696e7578", gasLimit: 134086657, gasUsed: 0, hash: "0xcde3272999885b223cfe03555098ded3695b8f40f305bcd571340b938478feec", logsBloom: "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", miner: "0x6918ef5c82e837fed798af3d61f62fda54151729", mixHash: "0x8ee76bebc7e8831a892bc5bfc624b5b4c94078b6a47c7f4870faf19ba3be2b0b", nonce: "0x25778c99f3377e77", number: 1, parentHash: "0x9f02b99a864192b04587a44b20a9776cabf49bc664829cc7886c297155c34631", receiptsRoot: "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", sha3Uncles: "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", size: 534, stateRoot: "0x4b9572efef79e46c6779da6432a2fda287e9ed8557c9ecc6c417eba7b925c039", timestamp: 1581035813, totalDifficulty: 1, transactions: [], transactionsRoot: "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", uncles: [] } > eth.getBlock(2) { difficulty: 1, extraData: "0xd88301090b846765746888676f312e31332e33856c696e7578", gasLimit: 133955714, gasUsed: 0, hash: "0x163e0ddd27e8c84465ea4012db0677737493cacee9871ff4db97c6ce3c3a8dff", logsBloom: "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", miner: "0x6918ef5c82e837fed798af3d61f62fda54151729", mixHash: "0xdf576b908fff00f745e87bb756aa8951d4edc841b55233087e484ccf78b8f4f2", nonce: "0x1b7726eebe60e5b1", number: 2, parentHash: "0xcde3272999885b223cfe03555098ded3695b8f40f305bcd571340b938478feec", receiptsRoot: "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", sha3Uncles: "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", size: 534, stateRoot: "0xe1d55911fc5cc66e79e899d76a12f85907d4d2d3096076764c1008231b08681a", timestamp: 1581035815, totalDifficulty: 2, transactions: [], transactionsRoot: "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", uncles: [] }ジェネシスブロックはdifficultyを「0」に指定していましたが、マイニングよって作られたブロックはdifficultyの値が「1」固定になっています。
参考
gethの古いバージョンだと修正するソースコードの場所が異なるようです。
geth の difficulty をあまり上げない(or 一定にする)方法
感想
思いの外、簡単に出来ました。
次回は処理待ちトランザクションがないとブロックを作らないなどの改造にチャレンジしようと思います。
- 投稿日:2020-02-07T11:22:47+09:00
gRPC の使い方 (Go)
参考ページ
Go Quick Start設定ファイル、サーバープログラム、クライアントプログラムの3つが必要です。
. ├── greeter_client │ └── main.go ├── greeter_server │ └── main.go └── helloworld └── helloworld.proto設定ファイル
helloworld/helloworld.proto
こちらと同じ
gRPC の使い方 (python)サーバープログラム
greeter_server/main.gopackage main import ( "context" "log" "net" "google.golang.org/grpc" pb "../helloworld" ) const ( port = ":50051" ) // server is used to implement helloworld.GreeterServer. type server struct { pb.UnimplementedGreeterServer } // SayHello implements helloworld.GreeterServer func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) { log.Printf("SayHello Received: %v", in.GetName()) return &pb.HelloReply{Message: "Test Hello " + in.GetName()}, nil } func (s *server) SayHello2(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) { log.Printf("SayHello2 Received: %v", in.GetName()) return &pb.HelloReply{Message: "Test2 Hello again " + in.GetName()}, nil } func main() { lis, err := net.Listen("tcp", port) if err != nil { log.Fatalf("failed to listen: %v", err) } s := grpc.NewServer() pb.RegisterGreeterServer(s, &server{}) if err := s.Serve(lis); err != nil { log.Fatalf("failed to serve: %v", err) } }クライアントプログラム
greeter_client/main.gopackage main import ( "context" "log" "os" "time" "google.golang.org/grpc" pb "../helloworld" ) const ( address = "localhost:50051" defaultName = "Tom" ) func main() { // Set up a connection to the server. conn, err := grpc.Dial(address, grpc.WithInsecure(), grpc.WithBlock()) if err != nil { log.Fatalf("did not connect: %v", err) } defer conn.Close() c := pb.NewGreeterClient(conn) // Contact the server and print out its response. name := defaultName if len(os.Args) > 1 { name = os.Args[1] } ctx, cancel := context.WithTimeout(context.Background(), time.Second) defer cancel() name = "John" r, err := c.SayHello(ctx, &pb.HelloRequest{Name: name}) if err != nil { log.Fatalf("could not greet: %v", err) } log.Printf("Greeting: %s", r.GetMessage()) name = "Tom" r, err = c.SayHello2(ctx, &pb.HelloRequest{Name: name}) if err != nil { log.Fatalf("could not greet: %v", err) } log.Printf("Greeting: %s", r.GetMessage()) }gRPC のコードを作成します。
スクリプト
protoc -I helloworld/ helloworld/helloworld.proto --go_out=plugins=grpc:helloworld実行後
$ tree . ├── greeter_client │ └── main.go ├── greeter_server │ └── main.go └── helloworld ├── helloworld.pb.go └── helloworld.protoサーバープログラムの起動
go run greeter_server/main.goクライアントプログラムの実行
$ go run greeter_client/main.go 2020/02/07 11:21:59 Greeting: Test Hello John 2020/02/07 11:21:59 Greeting: Test2 Hello again Tomサーバーのコンソールには次のようなメッセージが出ます。
$ go run greeter_server/main.go 2020/02/07 11:21:59 SayHello Received: John 2020/02/07 11:21:59 SayHello2 Received: TomArch Linux でのコマンドのインストール方法
sudo pacman -S protobuf
- 投稿日:2020-02-07T00:41:59+09:00
最近ちょっとGoを触った感想とか振り返りとか1
仕事で使いそうだったので勉強してみた感想とか。振り返りとか。
学習履歴
- Tour of go をやって Go を完全に理解する。
- いろいろ作った。
- みんなの Go 言語を読む。
- Effective Go を読んでさらに理解を深める。← いまここ
触ってみて
コンパイルが早い
Scala を書いてるときのいわゆる 待ち みたいなのを感じなくなったかも(コンパイルとかユニットテストとか)。Mac も大きな声で泣き叫んで発熱することもなくなった感。
組み込みのAPIが充実
用意されているものが優秀なので、簡易なHTTPサーバーを作るのにフレームワークなどを使う必要はなさげ(個人差ありそう)。
実行環境を選ばない
go build
で生成したバイナリはランタイムに依存しないので配布しやすい。コンテナを作るときはマルチステージビルドでバイナリだけ入れとくとか巷で良く見かける。FROM golang:1.13-buster as builder RUN mkdir /app WORKDIR /app COPY src ./src RUN go build -o /main ./src/cmd/*.go FROM debian:stretch-slim COPY --from=builder /main . ENTRYPOINT ["./main"]軽量のコンテナは、CA証明書が入ってなかったりするから Image に固めるアプリケーションが HTTP Client 実装してたりしたときは注意。
構文に関して
迷いのなきエラーハンドリング
変数に入れて nil かどうかを愚直に評価していく。とにかくこれな印象。
if err := file.Chmod(0664); err != nil { log.Print(err) return err }いわゆるガード節を書いていくので else とかは書かないほうが良い。
f, err := os.Open(name) if err != nil { return err } d, err := f.Stat() if err != nil { f.Close() return err } codeUsing(f, d)たまにはモナドが恋しいときもある。
命名でパッケージ外からの可視性が決まる
先頭文字を大文字にすることで他パッケージからの参照が可能となる。
小文字だと参照不可。最初これ知らなくてハマった。t_horikoshi.gopackage model type T_horikoshi struct { Name string } type t_horikoshi struct { name string }main.gopackage main func Ref() { t := domain.T_horikoshi{Name:"t_horikoshi"} //t := domain.t_horikoshi{name:"unexported..."} fmt.Print(t) }リソースのクローズとかやってくれる Defer が便利
リソースのクローズ処理のため Defer という仕組みが用意されている。
関数内に宣言しておくと最後まで処理を行った後に自動的に実行してくれるのでので便利。type ExchangeRate struct { Quotes []Quote } type Quote struct { High string Open string Bid string CurrencyPairCode string Ask string Low string } func GetExchangeRatePerYen() (rate *ExchangeRate, err error) { resp, err := http.Get("https://www.gaitameonline.com/rateaj/getrate") if err != nil { return nil, err } defer func() { if err := resp.Body.Close(); err != nil { log.Fatal("close resources", err) } }() bytes, err := ioutil.ReadAll(resp.Body) if err != nil { return nil, err } rate = new(ExchangeRate) if err := json.Unmarshal(bytes, rate); err != nil { return nil, err } return rate, nil }
resp.Body.Close()
がerror
を返すので無名関数で実装しなければならない。interface を実装する
Scala脳に染まってたので extends どうやるんやとか思ってたら、interface に定義したメソッドを実装したらその時点で継承が完了するみたい。
世間でダックタイピングと呼ばれているもの。↑これわたし勉強不足だったのですが、Structural typing と呼ばれるもので構造の宣言や定義によって型の互換性や透過性を保証し、型チェックを行わない Duck typing とは全くもって対をなすというものでした。
https://golang.org/doc/faq#implements_interface
@c-yan さんコメントありがとうございます。
main.gopackage main import "fmt" type Human interface { Call() string } func VoiceChecker(h Human) { fmt.Print(h.Call()) } type Animal struct {} func (a Animal) Call() string { return "bau!bau!" } type Insect struct {} func (i Insect) Call() string { return "paru!paru!" } func main() { VoiceChecker(Animal{}) VoiceChecker(Insect{}) }つづく...
そのうちに書きます。