2022/10/01


errors, fmt: add support for wrapping multiple errors · golang/go@4a0a2b3 · GitHub

An error which implements an "Unwrap() []error" method wraps all the non-nil errors in the returned ...

https://github.com/golang/go/commit/4a0a2b33dfa3c99250efa222439f2c27d6780e4a

Go でエラーを扱う際に、複数のエラーを束ねたい事があります。例えば複数のタスクを実行し、1つでもエラーになれば中断するのではなく、一通りタスクを実施し終えた結果を返したい様なニーズです。

package main

import (
    "errors"
    "log"
    "os"
    "sync"
)

func doMultiTasks(files []stringerror {
    var mu sync.Mutex
    var wg sync.WaitGroup
    var errs []error
    for _, file := range files {
        wg.Add(1)
        go func(file string) {
            defer wg.Done()

            f, err := os.Open(file)
            if err != nil {
                mu.Lock()
                errs = append(errs, err)
                mu.Unlock()
            } else {
                defer f.Close()
                // do something
            }
        }(file)
    }
    wg.Wait()

    return errors.Join(errs...)
}

func main() {
    err := doMultiTasks([]string{"not-found1""not-found2"})
    if err != nil {
        if errs, ok := err.(interface{ Unwrap() []error }); ok {
            for _, e := range errs.Unwrap() {
                log.Println(e)
            }
        } else {
            log.Println(err)
        }
    }
}

このコードは doMultiTasks に処理対象のファイル名を渡し、一通り実施した結果を返します。エラーを束ねるのに errors.Join を使います。束ねたエラーは通常踊り error として扱えます。ただし束ねたエラーを戻す関数は現状用意されていませんが、error を複数返す Unwrap という関数で型アサーションしてやる事で複数のエラーに戻せます。

また fmt.Errorf を使い書式フォーマットに %w を加える事でメッセージとエラーの両方を埋め込む事が出来ますが、本修正により複数の %w を埋め込む事ができる様になりました。

package main

import (
    "fmt"
    "log"
    "os"
)

func main() {
    err := fmt.Errorf("%w and %w", os.ErrNotExist, os.ErrClosed)
    if err != nil {
        if errs, ok := err.(interface{ Unwrap() []error }); ok {
            for _, e := range errs.Unwrap() {
                log.Println(e)
            }
        } else {
            log.Println(err)
        }
    }
}

個人的にはそれほど多いニーズとは思っていませんが、無くはない程度に感じています。

Posted at by



2022/09/07


Go ではバイト列と文字列は異なる内部データとして扱っています。[]byte から string へ変換したり、またその逆を行う際にはキャストが必要になります。ですので string はイミュータブルになります。

しかしイミュータブルなのは理解しつつもバイト列を文字列にする為に無駄なアロケートをしたくない場合もあります。これまで Go ではドキュメントに明文化していなかった為に色々な作法が生まれてしまっていました。その代表的な物が以下です。

s := *(*string)(unsafe.Pointer(&b))

本来は、Go のバイト列の内部は SliceHeader という構造体により管理されています。

type SliceHeader struct {
    Data uintptr
    Len  int
    Cap  int
}

また string は以下の StringHeader で管理されています。

type StringHeader struct {
    Data uintptr
    Len  int
}

上記の unsafe.Pointer を使ったコードはこの struct の先頭2フィールドを無理やり参照する事で実現しています。実際に Go のコードでも使われており、これが公式の方法だと思って多くの人が使ってしまっていました。

go/builder.go at 3058d38632aea679c96cd41156b2751c97578a2d · golang/go · GitHub
https://github.com/golang/go/blob/3058d38632aea679c96cd41156b2751c97578a2d/src/strings/builder.go#L30
Sign in to GitHub · GitHub
https://github.com/search?l=Go&q=reflect.StringHeader&type=Code
Sign in to GitHub · GitHub
https://github.com/search?l=Go&q=reflect.SliceHeader&type=Code

しかしながらこの実装は、今後 Go の文字列とバイト列に関する最適化の妨げになり得ます。例えば StringHeader と SliceHeader の交換を最適化する事ができなくなります。

そこで今回、unsafe.StringData、unsafe.String、unsafe.SliceData が入りました。これによりバイト列から文字列、またはその逆がミュータブルに変換できる様になりました。当然ですがこれらはバイト列の破壊的な変更により意図しない文字列の変更が行われる為、慎重に取り扱わなければなりません。

func StringToBytes(s string) []byte {
    return unsafe.Slice(unsafe.StringData(s), len(s))
}

func BytesToString(b []bytestring {
    return unsafe.String(&b[0], len(b))
}

unsafe.StringData は string が持っているバイト列へのポインタが返されます。

fmt.Println(unsafe.StringData("Hello"))
fmt.Println(unsafe.StringData("Hello"))
fmt.Println(unsafe.StringData("Hello1"))

これを実行すると以下の様に表示され、Go のコンパイラが文字列リテラル Hello の2つをまとめて保持しているのが分かります。

0xc67760
0xc67760
0xc6790b

また文字列がスタックに取られている時にはメモリ破壊が発生しますのでランタイムでクラッシュします。

s := "hello"
b := StringToBytes(s)
b[0] = 'h'
fmt.Println(s)

以下の様にヒープにメモリが確保されている場合には期待通りに動作します。

s := string([]byte("hello"))
b := StringToBytes(s)
b[0] = 'h'
fmt.Println(s)

動的なアロケーションが発生しないため高速な変換が可能です。おそらく今後、Go の内部実装の多くがこれらで置き換わっていき、幾らか高速化が見込まれます。

Posted at by



2022/08/18


Go言語で画像ファイルか確認してみる - Qiita

Go言語で書かれたサーバーサイドでアップロードされたファイルが画像ファイルか確認する必要があったため今後の備忘録として記載します。

https://qiita.com/tebakane/items/b7a47379659d42364c8d

実は Go にはそれ専用の関数が用意されています。

http package - net/http - Go Packages

Package http provides HTTP client and server implementations. Get, Head, Post, and PostFo...

https://pkg.go.dev/net/http#DetectContentType

バイト列を指定して以下の様に呼び出すだけです。

ct := http.DetectContentType(b)
if strings.HasPrefix(ct, "image/") {
    // 画像ファイル
}

簡単ですね。image.Decode を使い画像を一定のサイズにリサイズしてディスクに保存するといったケースであれば引用元の方法でも良いですが、そうでないならば画像のマジック部分だけで判定できるの DetectContentType を使った方が良いでしょう。

Posted at by