🐣

Goの1.18を触る

2022/03/26に公開

1.18での目玉機能であるgenericsとfuzzを軽く触っていきます

準備

generics

概要

  • 一言でいうと総称型
    • 実際に利用されるまで型が確定しない
    • Go特有のものではないので目新しさとかは特にない

やってみる

  • intとfloatで型が違うだけで処理は同じ関数をそれぞれ作成する
    main.go
    // SumInts adds together the values of m.
    func SumInts(m map[string]int64) int64 {
        var s int64
        for _, v := range m {
            s += v
        }
        return s
    }
    
    // SumFloats adds together the values of m.
    func SumFloats(m map[string]float64) float64 {
        var s float64
        for _, v := range m {
            s += v
        }
        return s
    }
    
    func main() {
        // Initialize a map for the integer values
        ints := map[string]int64{
            "first":  34,
            "second": 12,
        }
    
        // Initialize a map for the float values
        floats := map[string]float64{
            "first":  35.98,
            "second": 26.99,
        }
    
        fmt.Printf("Non-Generic Sums: %v and %v\n",
            SumInts(ints),
            SumFloats(floats))
    }
    
  • genericsを使って共通化できる
    main.go
    // SumIntsOrFloats sums the values of map m. It supports both int64 and float64
    // as types for map values.
    func SumIntsOrFloats[K comparable, V int64 | float64](m map[K]V) V {
        var s V
        for _, v := range m {
            s += v
        }
        return s
    }
    

fuzz

概要

  • いわゆるfuzzテスト
    • 異常系のテストを自動化して確認する

やってみる

  • 文字列を反対にする関数を作る
    main.go
    func Reverse(s string) (string, error) {
        b := []byte(s)
        for i, j := 0, len(b)-1; i < len(b)/2; i, j = i+1, j-1 {
        	b[i], b[j] = b[j], b[i]
        }
        return string(b)
    }
    
    func main() {
        input := "The quick brown fox jumped over the lazy dog"
        rev := Reverse(input)
        doubleRev := Reverse(rev)
        fmt.Printf("original: %q\n", input)
        fmt.Printf("reversed: %q\n", rev)
        fmt.Printf("reversed again: %q\n", doubleRev)
    }
    
  • go test -fuzz=Fuzzでfuzzing
    • 文字列をバイトで逆順にするとマルチバイトが無効になってしまうのでruneで処理する
      main.go
      func Reverse(s string) (string, error) {
          r := []rune(s)
          for i, j := 0, len(r)-1; i < len(r)/2; i, j = i+1, j-1 {
          	r[i], r[j] = r[j], r[i]
          }
          return string(r)
      }
      
    • メモ
      • テストがfailした場合、次にfuzzフラグを外せば同様のテストケースが実行できる
        • テストケースがtestdata/fuzz/${FuzzTestName}に保存されている
      • t.Logf()でテストがfailしたときかvオプションをつけたときのみ標準出力することができる
  • 再度go test -fuzz=Fuzzでfuzzing
    • UTF-8以外の文字が入るとエラーになるのでバリデーションを組み込む
      main.go
      func Reverse(s string) (string, error) {
          if !utf8.ValidString(s) {
          	return s, errors.New("input is not valid UTF-8")
          }
          r := []rune(s)
          for i, j := 0, len(r)-1; i < len(r)/2; i, j = i+1, j-1 {
          	r[i], r[j] = r[j], r[i]
          }
          return string(r), nil
      }
      
    • メモ
      • go test -run ${FuzzTestName}/${filename}で特定のファイルにあるテストケースを実行できる
  • 再度go test -fuzz=Fuzzでfuzzing
    • failはしていないが処理が止まらない
    • fuzzingは明示的に停止しない限り、failするまで実行される
      • -fuzztime 30sのように時間を指定する
        ❯ go test -fuzz=Fuzz -fuzztime 5s
        fuzz: elapsed: 0s, gathering baseline coverage: 0/43 completed
        fuzz: elapsed: 0s, gathering baseline coverage: 43/43 completed, now fuzzing with 12 workers
        fuzz: elapsed: 3s, execs: 757072 (252347/sec), new interesting: 2 (total: 45)
        fuzz: elapsed: 5s, execs: 1242894 (228431/sec), new interesting: 2 (total: 45)
        PASS
        ok  	example/fuzz	5.251s
        

感想

今回Go最大のアップデートということで主要な2つの機能を触りましたが、実際のプロダクトで即戦力として扱えるレベルだと感じました。

Go公式のチュートリアルはわかりやすくていいですね。また新機能がきたら触りたいと思います。

Discussion