- 投稿日:2020-11-20T16:51:11+09:00
Goのdeferの仕組みをきちんと理解する
deferって関数の最後で実行させたい処理に使うんでしょ、程度に考えていたが、きちんと理解をしようとdeferについて深堀りしてみた。
deferについてざっくり説明
- deferで処理を記述しておくとfuncの最後で実行してくれる。
- 以下の例だと、"今日は"が実行され、funcの終わりになるので、その後"金曜日です。"が実行される。
func hogehoge() { defer fmt.Println("金曜日です。") fmt.Println("今日は....") }今日は.... 金曜日です。ちょっと深堀り
Tour of Goの説明によると
- 囲まれているfunctionがreturnされるまで実行が見送られる。
- defer呼び出しの引数は即時に評価されるが、実行がreturnされるまで見送られる。
との説明がある。
1はなんとなく理解できるが、2の"即時に評価される"という記述が気になったのであとで詳しく解説したい。また、プログラミング言語Goの説明によると
- deferを使うことでリソースの開放などがきれいに記述できる。
- deferの正しい位置は資源の獲得に成功した直後
という旨の説明がある。
以下が例である。resp, err := http.Get(url) defer resp.Body.Close() //本当はdeferの前にエラー判定いれるべきJavaとかだと関数の最後にclose()を記述したりすることが多く、たまーにclose()を書き忘れたりすることもあるが、
goであればdefer使ってリソースを取得した直後に開放処理入れておくのが推奨されている。即時に評価される、とは?
実際にコードを見たほうが早いと思うので、以下のサンプルを見て欲しい。
func main() { defer today()() fmt.Println("今日は....") } func today() func() { fmt.Println("金曜日ですか?") return func() {fmt.Println("それとも土曜日ですか?")} }これを実行するとどうなるだろうか?
deferはmainが終わる時に呼び出されるから、今日は...
がまず呼び出され、次にtoday()内の金曜日ですか?
、最後にそれとも土曜日ですか?
という流れるになるかと思うかもしれない。しかし、実行してみると
金曜日ですか? 今日は.... それとも土曜日ですか?となるのである。さてなぜだろう?
ポイントはtoday()の返り値が関数型である点だ。前項のdefer呼び出しの引数は即時に評価されるが、という部分に注目して欲しい。
defer today()()
の記述の部分で評価が実施されるのである。(なんで括弧が2つあるのか?と一瞬思う方もいるかもしれないが、話がそれるので最後に解説したい)なので、
fmt.Println("金曜日ですか?")が実行される。その後、main内の処理に進み、
fmt.Println("今日は....")が実行される。
その後、遅延実行で
fmt.Println("それとも土曜日ですか?")という流れである。
defer today()()の括弧が2つあるのはなぜ??
括弧2ついらないんじゃない??と思うひともいるかもしれないが、これは必要である。ためしに括弧を1つで実施するとどうなるだろうか?
func main() { defer today() fmt.Println("今日は....") } func today() func() { fmt.Println("金曜日ですか?") return func() {fmt.Println("それとも土曜日ですか?")} }今日は.... 金曜日ですか?順番が変わった上に、土曜日が消えている。。。。
defer today() //1 defer today()() //2この2つの記述は根本的に違うのである。
1はあくまで、deferの処理でtoday関数を実行する。つまり、main関数の最後にtoday()が実行されるのである。この処理では無名関数がreturnされているが、無名関数は実行されないということなのである。
それに対し、2はtoday()でリターンされた関数をmain関数の最後に実施するのである。わかりやすく記述すると2はdeferの処理で無名関数
func() {fmt.Println("それとも土曜日ですか?")}
を実行しているのである。
- 投稿日:2020-11-20T16:18:22+09:00
制限時間付き標準入力
プログラムを書いていて一度は制限時間のある、標準入力を作りたいと思ったことはあると思います。因みに僕はありません。
以外と簡単に Go で実現できたのでメモがてら書きます。いつかの自分のためですね。方法
方法はいたって簡単で、標準入力を受け取るサブルーチンを作って、入力テキストをチャンネルで受け取るだけです。
in := make(chan string, 1) go func() { sc := bufio.NewScanner(os.Stdin) sc.Scan() in <- sc.Text() }()上記のチァンネルと
time.NewTimer
やtime.NewTicker
を組み合わせることで簡単に実現できます。timer := time.NewTimer(time.Second * 10) select { case text := <-in: // 入力を使った処理 case <-timer.C: // タイムアウト時の処理 }適当に作ったサンプル
上記の方法で適当に作ったサンプルです。ローカルで実行してみると雰囲気を掴めると思います。
package main import ( "bufio" "fmt" "os" "time" ) func main() { in := make(chan string, 1) go func() { sc := bufio.NewScanner(os.Stdin) sc.Scan() in <- sc.Text() }() ticker := time.NewTicker(time.Second) defer ticker.Stop() fmt.Println("制限時間は5秒。坊っちゃんの作者を入力して") for i := 0; i < 5; i++ { select { case text := <-in: if text == "夏目漱石" { fmt.Printf("入力できてすごい\n") } else { fmt.Printf("間違えているよ\n%s\n", text) } i = 5 case <-ticker.C: switch i { case 0: fmt.Println("制限時間あるので早めに") case 1: fmt.Println("はやくはやく") case 2: fmt.Println("邪魔だと思う?") case 3: fmt.Println("僕もそう思う") case 4: fmt.Println("ゲームオーバー(笑)") } } } }締め
簡単でしたが、いつ使うんでしょうね。
- 投稿日:2020-11-20T08:55:20+09:00
"go build"した時に何が起きているのか?
この記事は How “go build” Works の翻訳記事です。
go build
はどのようにして最も単純なGolangプログラムをコンパイルしているのでしょうか?この記事ではその疑問を解消することを目標にしています。
最も単純な以下のプログラムについて考えてみましょう。
// main.go package main func main() {}
go build main.go
を実行すると、1.1Mbの実行可能ファイルmain
が出力され、何も実行されません。 この何もしないバイナリを作成するために、go build
は何をしたのでしょうか?
go build
コマンドはいくつかの便利なオプションを提供しています。
-work
:go build
は、作業ファイル用の一時フォルダーを作成します。 この引数は、そのフォルダーの場所を出力し、ビルド後に削除しません-a
: Golangは、以前にビルドされたパッケージをキャッシュします。-a
はgo build
にキャッシュを無視させるので、ビルドはすべてのステップを出力します-p 1
: これにより、処理が単一スレッドで行われるように設定され、出力が線形にログに記録されます-x
:go build
は、compile
などの他のGolangツールのラッパーです。-x
は、これらのツールに送信されるコマンドと引数を出力します
go build -work -a -p 1 -x main.go
を実行するとmain
だけでなく大量のログが出て、build
でmain
を作成する際に行われていることを我々に教えてくれます。ログはまず以下の内容を出力します。
WORK=/var/folders/rw/gtb29xf92fv23f0zqsg42s840000gn/T/go-build940616988これは、構造が次のような作業ディレクトリです。
├── b001 │ ├── _pkg_.a │ ├── exe │ ├── importcfg │ └── importcfg.link ├── b002 │ └── ... ├── b003 │ └── ... ├── b004 │ └── ... ├── b006 │ └── ... ├── b007 │ └── ... └── b008 └── ...
go build
は、完了する必要のあるタスクのアクショングラフを定義します。このグラフの各アクションは、独自のサブディレクトリ(
NewObjdir
で定義)を取得します。グラフの最初のノード
b001
は、メインバイナリをコンパイルするためのルートタスクです。依存する各アクションの数は大きく、最後は
b008
です。 (b005
がどこに行ったのかわかりませんが問題ないと思われるので割愛します)b008
実行される最初のアクションは、グラフの末端の
b008
です。mkdir -p $WORK/b008/ cat >$WORK/b008/importcfg << 'EOF' # import config EOF cd /<..>/src/runtime/internal/sys /<..>/compile -o $WORK/b008/_pkg_.a -trimpath "$WORK/b008=>" -p runtime/internal/sys -std -+ -complete -buildid gEtYPexVP43wWYWCxFKi/gEtYPexVP43wWYWCxFKi -goversion go1.14.7 -D "" -importcfg $WORK/b008/importcfg -pack -c=16 ./arch.go ./arch_amd64.go ./intrinsics.go ./intrinsics_common.go ./stubs.go ./sys.go ./zgoarch_amd64.go ./zgoos_darwin.go ./zversion.go /<..>/buildid -w $WORK/b008/_pkg_.a cp $WORK/b008/_pkg_.a /<..>/Caches/go-build/01/01b...60a-d
b008
では
- アクションディレクトリを作成します(すべてのアクションがこれを行うため、以降ではこの記述を省略します)
- ツール
compile
で使用するimportcfg
ファイルを作成します(空です)- ディレクトリを
runtime/internal/sys
パッケージのソースフォルダに変更します。 このパッケージには、ランタイムで使用される定数が含まれています- パッケージをコンパイルします
buildid
を使用してメタデータをパッケージに書き込み(-w
)、パッケージをgo-build
キャッシュにコピーします(すべてのパッケージがキャッシュされるため、以降ではこの記述を省略します)これをツール
compile
に送信された引数に分解してみましょう(go tool compile --help
でも説明されています)。
-o
出力先のファイル-trimpath
ソースファイルのパスからprefix$WORK/b008=>
を取り除く-p
import
で使われるパッケージ名をセット-std
compiling standard library
(現時点ではよくわかりませんでした)-+
compiling runtime
(これもわかりませんでした)-complete
コンパイラはCまたはアセンブリではなく完全なパッケージを出力-buildid
メタデータにbuild idを付与する-goversion
コンパイルされたパッケージに必要なバージョン-D
ローカルなインポートで使う相対パスは""-importcfg
インポート設定ファイルは他のパッケージを参照-pack
パッケージをオブジェクトファイル.o
ではなくアーカイブ.a
として作る-c
ビルド時にどれだけ並列で処理するか- パッケージ内のファイルのリスト
これらの引数のほとんどはすべての
compile
コマンドで同じなので、以降ではこの記述を省略します。
b008
の出力はruntime/internal/sys
に対応している$WORK/b008/_pkg_.a
というアーカイブファイルです。buildid
buildid
とは何かを説明しましょう。
buildid
の形式は<actionid>/<contentid>
です。これは、パッケージをキャッシュして
go build
のパフォーマンスを向上させるためのインデックスとして使用されます。
<actionid>
は、アクション(すべての呼び出し、引数、および入力ファイル)のハッシュです。<contentid>
は、出力.a
ファイルのハッシュです。
go build
アクションごとに、同じ<actionid>
を持つ別のアクションによって作成されたコンテンツをキャッシュで検索できます。これは
buildid.go
に実装されています。
buildid
はメタデータとしてファイルに保存されるため、<contentid>
を取得するために毎回ハッシュする必要はありません。 このIDは、go tool buildid <file>
で確認できます(バイナリでも機能します)。上記の
b008
のログでは、buildID
はcompile
ツールによってgEtYPexVP43wWYWCxFKi/gEtYPexVP43wWYWCxFKi
として設定されています。これは単なるプレースホルダーであり、後でキャッシュされる前に、
go tool buildid -w
で正しいgEtYPexVP43wWYWCxFKi/b-rPboOuD0POrlJWPTEi
に上書きされます。b007
次は
b007
ですcat >$WORK/b007/importcfg << 'EOF' # import config packagefile runtime/internal/sys=$WORK/b008/_pkg_.a EOF cd /<..>/src/runtime/internal/math /<..>/compile -o $WORK/b007/_pkg_.a -p runtime/internal/math -importcfg $WORK/b007/importcfg ... ./math.go
packagefile runtime/internal/sys=$WORK/b008/_pkg_.a
と書かれたimportcfg
を作成しています これはb007
がb008
に依存していることを示しますruntime/internal/math
をコンパイルmath.go
の中身を覗いてみると確かにb008
で作られたruntime/internal/sys
をインポートしています
b007
の出力はruntime/internal/math
に対応している$WORK/b007/_pkg_.a
というアーカイブファイルです。b006
cat >$WORK/b006/go_asm.h << 'EOF' EOF cd /<..>/src/runtime/internal/atomic /<..>/asm -I $WORK/b006/ -I /<..>/go/1.14.7/libexec/pkg/include -D GOOS_darwin -D GOARCH_amd64 -gensymabis -o $WORK/b006/symabis ./asm_amd64.s /<..>/asm -I $WORK/b006/ -I /<..>/go/1.14.7/libexec/pkg/include -D GOOS_darwin -D GOARCH_amd64 -o $WORK/b006/asm_amd64.o ./asm_amd64.s cat >$WORK/b006/importcfg << 'EOF' # import config EOF /<..>/compile -o $WORK/b006/_pkg_.a -p runtime/internal/atomic -symabis $WORK/b006/symabis -asmhdr $WORK/b006/go_asm.h -importcfg $WORK/b006/importcfg ... ./atomic_amd64.go ./stubs.go /<..>/pack r $WORK/b006/_pkg_.a $WORK/b006/asm_amd64.oここで、通常の
.go
ファイルから抜け出し、低レベルのGoアセンブリ.s
ファイルの処理を開始します。
- ヘッダファイル
go_asm.h
を作成- 低レベルな関数が集まった
runtime/internal/atomic
パッケージに移動- ツール
go tool asm
(go tool asm --help
で説明)を実行して、symabis
"Symbol Application Binary Interfaces(ABI)ファイル" を作成し、次にオブジェクトファイルasm_amd64.o
を作成compile
を使用して、symabis
ファイルと-asmhdr
を含むヘッダーを含む_pkg_.a
ファイルを作成pack
コマンドでasm_amd64.o
を_pkg_.a
の中に加える
asm
ツールはここでは以下の引数を伴って呼び出されています。
-I
: アクションb007
およびlibexec/pkg/includes
フォルダーを含めます。includes
には3つのファイルasm_ppc64x.h
、funcdata.h
とtextflag.h
があり、すべて低レベルの関数定義があります。 例えば、FIXED_FRAMEは、スタックフレームの固定部分のサイズを定義します-D
: 事前定義されたシンボルを含める-gensymabis
:symabis
ファイルを作成する-o
: 出力先のファイル
b006
の出力はruntime/internal/atomic
に対応している$WORK/b006/_pkg_.a
というアーカイブファイルです。b004
cd /<..>/src/internal/cpu /<..>/asm ... -o $WORK/b004/symabis ./cpu_x86.s /<..>/asm ... -o $WORK/b004/cpu_x86.o ./cpu_x86.s /<..>/compile ... -o $WORK/b004/_pkg_.a ./cpu.go ./cpu_amd64.go ./cpu_x86.go /<..>/pack r $WORK/b004/_pkg_.a $WORK/b004/cpu_x86.o
b004
は対象がinternal/cpu
に変わった以外はb006
と同様です。最初に
symabis
とオブジェクトファイルをcpu_x86.s
をアセンブルして作成して、goファイルをコンパイルした後、それらを合わせて、アーカイブ_pkg_.a
を作成します。
b004
の出力はinternal/cpu
に対応している$WORK/b004/_pkg_.a
というアーカイブファイルです。b003
cat >$WORK/b003/go_asm.h << 'EOF' EOF cd /<..>/src/internal/bytealg /<..>/asm ... -o $WORK/b003/symabis ./compare_amd64.s ./count_amd64.s ./equal_amd64.s ./index_amd64.s ./indexbyte_amd64.s cat >$WORK/b003/importcfg << 'EOF' # import config packagefile internal/cpu=$WORK/b004/_pkg_.a EOF /<..>/compile ... -o $WORK/b003/_pkg_.a -p internal/bytealg ./bytealg.go ./compare_native.go ./count_native.go ./equal_generic.go ./equal_native.go ./index_amd64.go ./index_native.go ./indexbyte_native.go /<..>/asm ... -o $WORK/b003/compare_amd64.o ./compare_amd64.s /<..>/asm ... -o $WORK/b003/count_amd64.o ./count_amd64.s /<..>/asm ... -o $WORK/b003/equal_amd64.o ./equal_amd64.s /<..>/asm ... -o $WORK/b003/index_amd64.o ./index_amd64.s /<..>/asm ... -o $WORK/b003/indexbyte_amd64.o ./indexbyte_amd64.s /<..>/pack r $WORK/b003/_pkg_.a $WORK/b003/compare_amd64.o $WORK/b003/count_amd64.o $WORK/b003/equal_amd64.o $WORK/b003/index_amd64.o $WORK/b003/indexbyte_amd64.o
b003
もやることはb004
やb006
と同じです。このパッケージの主な問題は、多くのオブジェクトファイル
.o
を作成するために複数の.s
ファイルがあり、それぞれを_pkg_.a
ファイルに追加する必要があることです。
b003
の出力はinternal/bytealg
に対応している$WORK/b003/_pkg_.a
というアーカイブファイルです。b002
cat >$WORK/b002/go_asm.h << 'EOF' EOF cd /<..>/src/runtime /<..>/asm ... -o $WORK/b002/symabis ./asm.s ./asm_amd64.s ./duff_amd64.s ./memclr_amd64.s ./memmove_amd64.s ./preempt_amd64.s ./rt0_darwin_amd64.s ./sys_darwin_amd64.s cat >$WORK/b002/importcfg << 'EOF' # import config packagefile internal/bytealg=$WORK/b003/_pkg_.a packagefile internal/cpu=$WORK/b004/_pkg_.a packagefile runtime/internal/atomic=$WORK/b006/_pkg_.a packagefile runtime/internal/math=$WORK/b007/_pkg_.a packagefile runtime/internal/sys=$WORK/b008/_pkg_.a EOF /<..>/compile -o $WORK/b002/_pkg_.a ... -p runtime ./alg.go ./atomic_pointer.go ./cgo.go ./cgocall.go ./cgocallback.go ./cgocheck.go ./chan.go ./checkptr.go ./compiler.go ./complex.go ./cpuflags.go ./cpuflags_amd64.go ./cpuprof.go ./cputicks.go ./debug.go ./debugcall.go ./debuglog.go ./debuglog_off.go ./defs_darwin_amd64.go ./env_posix.go ./error.go ./extern.go ./fastlog2.go ./fastlog2table.go ./float.go ./hash64.go ./heapdump.go ./iface.go ./lfstack.go ./lfstack_64bit.go ./lock_sema.go ./malloc.go ./map.go ./map_fast32.go ./map_fast64.go ./map_faststr.go ./mbarrier.go ./mbitmap.go ./mcache.go ./mcentral.go ./mem_darwin.go ./mfinal.go ./mfixalloc.go ./mgc.go ./mgcmark.go ./mgcscavenge.go ./mgcstack.go ./mgcsweep.go ./mgcsweepbuf.go ./mgcwork.go ./mheap.go ./mpagealloc.go ./mpagealloc_64bit.go ./mpagecache.go ./mpallocbits.go ./mprof.go ./mranges.go ./msan0.go ./msize.go ./mstats.go ./mwbbuf.go ./nbpipe_pipe.go ./netpoll.go ./netpoll_kqueue.go ./os_darwin.go ./os_nonopenbsd.go ./panic.go ./plugin.go ./preempt.go ./preempt_nonwindows.go ./print.go ./proc.go ./profbuf.go ./proflabel.go ./race0.go ./rdebug.go ./relax_stub.go ./runtime.go ./runtime1.go ./runtime2.go ./rwmutex.go ./select.go ./sema.go ./signal_amd64.go ./signal_darwin.go ./signal_darwin_amd64.go ./signal_unix.go ./sigqueue.go ./sizeclasses.go ./slice.go ./softfloat64.go ./stack.go ./string.go ./stubs.go ./stubs_amd64.go ./stubs_nonlinux.go ./symtab.go ./sys_darwin.go ./sys_darwin_64.go ./sys_nonppc64x.go ./sys_x86.go ./time.go ./time_nofake.go ./timestub.go ./trace.go ./traceback.go ./type.go ./typekind.go ./utf8.go ./vdso_in_none.go ./write_err.go /<..>/asm ... -o $WORK/b002/asm.o ./asm.s /<..>/asm ... -o $WORK/b002/asm_amd64.o ./asm_amd64.s /<..>/asm ... -o $WORK/b002/duff_amd64.o ./duff_amd64.s /<..>/asm ... -o $WORK/b002/memclr_amd64.o ./memclr_amd64.s /<..>/asm ... -o $WORK/b002/memmove_amd64.o ./memmove_amd64.s /<..>/asm ... -o $WORK/b002/preempt_amd64.o ./preempt_amd64.s /<..>/asm ... -o $WORK/b002/rt0_darwin_amd64.o ./rt0_darwin_amd64.s /<..>/asm ... -o $WORK/b002/sys_darwin_amd64.o ./sys_darwin_amd64.s /<..>/pack r $WORK/b002/_pkg_.a $WORK/b002/asm.o $WORK/b002/asm_amd64.o $WORK/b002/duff_amd64.o $WORK/b002/memclr_amd64.o $WORK/b002/memmove_amd64.o $WORK/b002/preempt_amd64.o $WORK/b002/rt0_darwin_amd64.o $WORK/b002/sys_darwin_amd64.o
b002
を見ればこれまでのアクションがなぜ必要だったのかわかります。
b002
はGoのバイナリの実行に必要なruntime
パッケージ全てを含んでいます。例えば、b002
にはmgc.go
というGoのGCの実装も含まれています。これはb004
(internal/cpu
)とb006
(runtime/internal/atomic
)をインポートしています。
b002
はコアライブラリの中では最も複雑なパッケージかもしれませんが、ビルド自体は今までと同じ工程で行われます。つまりasm
、compile
して出力されたファイルをpack
して_pkg_.a
にしています。
b002
の出力はruntime
に対応している$WORK/b002/_pkg_.a
というアーカイブファイルです。b001
cat >$WORK/b001/importcfg << 'EOF' # import config packagefile runtime=$WORK/b002/_pkg_.a EOF cd /<..>/main /<..>/compile ... -o $WORK/b001/_pkg_.a -p main ./main.go cat >$WORK/b001/importcfg.link << 'EOF' packagefile command-line-arguments=$WORK/b001/_pkg_.a packagefile runtime=$WORK/b002/_pkg_.a packagefile internal/bytealg=$WORK/b003/_pkg_.a packagefile internal/cpu=$WORK/b004/_pkg_.a packagefile runtime/internal/atomic=$WORK/b006/_pkg_.a packagefile runtime/internal/math=$WORK/b007/_pkg_.a packagefile runtime/internal/sys=$WORK/b008/_pkg_.a EOF /<..>/link -o $WORK/b001/exe/a.out -importcfg $WORK/b001/importcfg.link -buildmode=exe -buildid=yC-qrh2sY_qI0zh2-NE7/owNzOBTqPO00FkqK0_lF/HPXqvMz_4PvKsQzqGWgD/yC-qrh2sY_qI0zh2-NE7 -extld=clang $WORK/b001/_pkg_.a mv $WORK/b001/exe/a.out mainFirst it builds an importcfg that includes runtime built in b002 to then compile main.go to pkg.a
- 最初に、
b002
のruntime
を含むようにしてimportcfg
を作成した後、main.go
をコンパイルして_pkg_.a
を作成します。- 以前に登場した全パッケージに加えて
command-line-arguments=$WORK/b001/_pkg_.a
を含むimportcfg.link
を作成したら、link
コマンドでそれらをリンクして実行ファイルを作成します。- 最後に
main
にリネームして出力先に移動します。
link
の引数の補足をしておきましょう。
-buildmode
: 実行ファイルをビルドする-extld
: 外部のリンカを参照するようやくお目当てのものが手に入りました。
b001
から生まれるのがmain
バイナリです。Bazelとの類似点
効率的なキャッシュを実現するためのアクショングラフの作成は、Bazelが高速ビルドに使用するビルドツールと同じアイデアです。
Golangの
actionid
とcontentid
は、Bazelがキャッシュで使用するactioncache
とcontent-addressable store(CAS)
に対応しています。BazelはGoogleの製品であり、Golangも同様です。 彼らがソフトウェアを迅速そして正確にビルドする方法について同様の哲学をもったことは非常に合理的と言えるでしょう。
Bazelの
rules_go
パッケージでは、builder
コードでgo build
を再実装する方法を確認できます。アクショングラフ、フォルダ管理、およびキャッシュはBazelによって外部で処理されるため、これは非常にクリーンな実装です。
次のステップへ
go build
は、今回のような何もしないプログラムでも、それをコンパイルするために多くのことを行っていました。ツール(
compile
asm
)やその入力ファイルと出力ファイル(.a
.o
.s
)については今回はあまり詳しく説明しませんでした。また、今回は最も基本的なプログラムをコンパイルしているだけです。
次のようにしてコンパイルをもっと複雑なものにできます。
- 他のパッケージをインポートする 例えば
Hello world
を出力するためfmt
をインポートするとさらに23個のアクションがアクショングラフに追加される- 外部パッケージを参照するために
go.mod
を使用するGOOS
とGOARCH
の値を変えて他のアーキテクチャ向けにビルドする 例えば、wasm
向けのコンパイルではアクションや引数の内容が全く異なってくる
go build
を実行してログを検査することは、Goコンパイラがどのように機能するかを学ぶためのアプローチとしてはトップダウンなアプローチです。 基礎から学びたい場合は次のようなリソースに飛び込むのに最適な出発点です。
- Introduction to the Go compiler
- Go: Overview of the Compiler
- Go at Google: Language Design in the Service of Software Engineering
- build.go
- compile/main.go
References