2023/02/20


2023/3/12 発売です。

Go言語プログラミングエッセンス エンジニア選書 Go言語プログラミングエッセンス エンジニア選書
mattn
技術評論社 Kindle版 / ¥3,450 (2023年03月09日)
 
発送可能時間:

Go が発表された当初から Go を見続けて来たので ずいぶん時間が経った気がします。僕なりにいろいろな活動をして来ました。Go 本体や周辺ツールへのコントリビュート、イベント登壇、雑誌への記事の投稿や「みんなのGo言語」の共著など。

あらゆる活動が Go づくめで刺激的でした。Go で得たものも沢山あります。そして Go を使う人も想像していた以上に沢山増えました。Go に関して書かれたブログ、Go の求人、今では大学で Go を教えているところもあるくらいです。Go を知った事で僕のプログラミング人生はずいぶん変わりました。それまでは何を作るにしてもC言語をメイン言語として使ってきましたが、今や新しいアプリケーションを書くのであればほぼほぼ Go で書くようになりました。(C言語/C++ は好きですよ)

クセが強いからか、なかなか理解され辛いプログラミング言語でもありますが、本質的には実用的なアプリケーションを効率的に開発する事ができ、それでいてマルチコア CPU でスケールするといった、とても高いポテンシャルを持った言語だと思っています。日頃からこの高いポテンシャルを持ったプログラミング言語「Go 言語」をもっと皆に正しく理解して欲しい、もっと沢山使って欲しいと思っていたところに、技術評論社さんから本書の執筆のお話を頂きました。

基本的な文法はほどほどに、並行処理の実装方法や、パフォーマンスを上げる為のテクニック、綺麗に書くための Tips、アプリケーションを順を追って解説混じりに実装する例、僕が Go で得た知識を沢山散りばめたつもりです。

Go はC言語を良くしたプログラミング言語でもある事から、いくらかC言語を意識した記述がされています。しかしC言語を習得していない人達でもなるべく理解頂ける様に心掛けたつもりです。Go というプログラミング言語がスクリプト言語の様な書き味を持ち、それでいて高速なアプリケーションが実装でき、さらには並行処理を扱うアプリケーションを簡単に設計・実装できるという事を、本書でご理解頂けると信じています。

ぜひ書店にて本書をお手に取ってご覧下さい。

Posted at by



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