表題の通りです。
and
, or
の右辺値で副作用を起こすテンプレートを書いていた場合、Go1.17と1.18で挙動が変わります。Go本体と同じ仕様になったので分かりやすいですね。
{{$d := dict}}
{{or 1 (set $d "msg" "right value is evaluated!")}}
{{$d}}
go1.17: or
の右辺値も評価されるので、 $d
にキーが追加されている
1
map[msg:right value is evaluated!]
go1.18: or
の左辺値がtruthyなので短絡評価され、 $d
にキーが追加されない
1
map[]
挙動は何によって変わる?
Go本体のバージョンで決まります。 go.mod
ファイルのGoディレクティブは関係ありませんでした1。
そのため、go install
で同じバージョンのバイナリを落としてきても、Go本体のバージョンによって挙動が変わります。
$ go version
go version go1.18 linux/amd64
$ echo | go run github.com/syuparn/tmplscript@v0.6.0 '{{$d := dict}}{{$_ := or 1 (set $d "msg" "right value is evaluated!")}}{{$d}}'
map[]
$ go version
go version go1.17.6 linux/amd64
$ echo | go run github.com/syuparn/tmplscript@v0.6.0 '{{$d := dict}}{{$_ := or 1 (set $d "msg" "right value is evaluated!")}}{{$d}}'
map[msg:right value is evaluated!]
(kubectl
や helm
等、ビルドに使ったGoのバージョン情報も表示するツールはこのような挙動の違いを意識しているのでしょうか?)
とはいえ、Goは標準パッケージ含めv1系の後方互換を保証しているので、実装の穴をついて変なことをしない限りバージョンを意識する必要は無さそうです2。
おわりに
変な挙動やバグに依存したコードは書かないようにしよう!
-
公式リファレンスでは「ディレクティブより新しいバージョンの言語仕様が使われたらコンパイルエラーにする」と書かれていますが、標準パッケージについての記述はありませんでした。また、標準パッケージは
go.mod
でもバージョン管理されないので、純粋に本体バージョンに紐づくと考えてよさそうです。 ↩ -
短絡評価が非互換の変更に当たるかどうかはissueでも長い間議論されていたようです。最終的に、右辺値を評価することはgodocに仕様として書かれていないので、nil pointer dereferenceを回避できる利点のほうが大きいと判断され採用されました。 ↩