いろいろ備忘録日記

主に .NET とか Go とか Flutter とか Python絡みのメモを公開しています。

Goメモ-593 (Go 1.25 interactive tour)

関連記事

GitHub - devlights/blog-summary: ブログ「いろいろ備忘録日記」のまとめ

概要

以下、自分用のメモです。

もうすぐ、Go 1.25がリリースされることになっていますが、Anton Zhiyanovさんのブログで恒例のインタラクティブツアーの記事がアップされていました。いつも新機能を学ぶときにお世話になっています。

antonz.org

1.24で実験的機能として含まれた testing/synctest が無事stableとなったみたいですね。使い方覚えないと。。

synctest.Runからsynctest.Testに関数名が変わったことに注意。

参考情報

tip.golang.org

antonz.org

antonz.org

antonz.org

Goのおすすめ書籍


過去の記事については、以下のページからご参照下さい。

サンプルコードは、以下の場所で公開しています。

Goメモ-592 (Go 1.25でgo vet コマンドに net.Dial 用のチェックが追加)(Go 1.25 rc1)

関連記事

GitHub - devlights/blog-summary: ブログ「いろいろ備忘録日記」のまとめ

概要

以下、自分用のメモです。前回の続きです。

devlights.hatenablog.com

devlights.hatenablog.com

devlights.hatenablog.com

devlights.hatenablog.com

Go 1.25 のドラフトリリースノートは以下。

tip.golang.org

上記を見ると、Go 1.25 で go vet コマンドに net.Dial 向けのチェックが追加されるみたいですね。

which reports uses of fmt.Sprintf("%s:%d", host, port) to construct addresses for net.Dial, as these will not work with IPv6; instead it suggests using net.JoinHostPort.

ってあるので、net.Dial時のエンドポイント指定時に、fmt.Sprintf("%s:%d", host, port) って指定してるとIPv6の場合はうまくいかないから警告してくれるようになったみたいですね。

一応、ちゃんと警告されるか試してみました。

試してみた

go version

$ go version
go version go1.25rc1 linux/arm64

main.go

package main

import (
        "fmt"
        "net"
)

func main() {
        const (
                HOST = "127.0.0.1"
                PORT = 8888
        )
        conn, _ := net.Dial("tcp", fmt.Sprintf("%s:%d", HOST, PORT))
        defer conn.Close()
}

go vet

go vet するとちゃんと警告でますね。

$ go vet .
# app
# [app]
./main.go:13:41: address format "%s:%d" does not work with IPv6

main.go (2)

正しい形に直して

package main

import (
        "net"
        "strconv"
)

func main() {
        const (
                HOST = "127.0.0.1"
                PORT = 8888
        )
        conn, _ := net.Dial("tcp", net.JoinHostPort(HOST, strconv.Itoa(PORT)))
        defer conn.Close()
}

vetすると警告は消えました。

$ go vet .

参考情報

pkg.go.dev

pkg.go.dev

Goのおすすめ書籍


過去の記事については、以下のページからご参照下さい。

サンプルコードは、以下の場所で公開しています。

Goメモ-591 (Go 1.25でgo vet コマンドに sync.WaitGroup 用のチェックが追加)(Go 1.25 rc1)

関連記事

GitHub - devlights/blog-summary: ブログ「いろいろ備忘録日記」のまとめ

概要

以下、自分用のメモです。前回の続きです。

devlights.hatenablog.com

devlights.hatenablog.com

devlights.hatenablog.com

Go 1.25 のドラフトリリースノートは以下。

tip.golang.org

上記を見ると、Go 1.25 で go vet コマンドに sync.WaitGroup 向けのチェックが追加されるみたいですね。

waitgroup, which reports misplaced calls to sync.WaitGroup.Add;

ってあるので、sync.WaitGroupの間違った使い方があった場合に警告してくれるとのこと。

では、どんなパターンのときに警告してくれるとかというと

waitgroup package - golang.org/x/tools/go/analysis/passes/waitgroup - Go Packages

にあるみたいに

// WRONG
var wg sync.WaitGroup
go func() {
        wg.Add(1) // "WaitGroup.Add called from inside new goroutine"
        defer wg.Done()
        ...
}()
wg.Wait() // (may return prematurely before new goroutine starts)

って感じで、ゴルーチン内で wg.Add() してしまっているパターンのみみたいですね。

このパターン、コンパイルは当然通るし、実行も出来るのですが正しく待ち合わせが出来ない可能性が発生します。(Waitに入る前に動くことが出来たゴルーチンがいた場合は、その数だけ待機してくれるけど、全ゴルーチン分がWait前にAdd(1)を呼び出せるかどうかは不定。普通は速攻でWaitが呼ばれるので、Addしているものが一つも存在せずに即終了してしまう)

一応、ちゃんと警告されるか試してみました。

試してみた

go version

$ go version
go version go1.25rc1 linux/arm64

main.go

package main

import (
        "fmt"
        "sync"
)

func main() {
        const (
                NumWorkers = 3
        )
        var (
                wg sync.WaitGroup
        )
        for range NumWorkers {
                // 本来はゴルーチン外でAddしないと駄目
                go func() {
                        wg.Add(1) // 間違えてる
                        defer wg.Done()

                        fmt.Println("hello world")
                }()
        }

        wg.Wait()
}

go vet

go vet するとちゃんと警告でますね。

$ go vet .
# app
# [app]
./main.go:18:10: WaitGroup.Add called from inside new goroutine

main.go (2)

正しい形に直して

package main

import (
        "fmt"
        "sync"
)

func main() {
        const (
                NumWorkers = 3
        )
        var (
                wg sync.WaitGroup
        )
        for range NumWorkers {
                wg.Add(1)
                go func() {
                        defer wg.Done()

                        fmt.Println("hello world")
                }()
        }

        wg.Wait()
}

vetすると警告は消えました。

$ go vet .

参考情報

pkg.go.dev

Goのおすすめ書籍


過去の記事については、以下のページからご参照下さい。

サンプルコードは、以下の場所で公開しています。

Goメモ-590 (Go 1.25でgo doc コマンドに -http オプションが追加)(Go 1.25 rc1)

関連記事

GitHub - devlights/blog-summary: ブログ「いろいろ備忘録日記」のまとめ

概要

以下、自分用のメモです。前回の続きです。

devlights.hatenablog.com

devlights.hatenablog.com

Go 1.25 のドラフトリリースノートは以下。

tip.golang.org

上記を見ると、Go 1.25 で go doc コマンドに -http オプションが追加されるみたいですね。

このオプションを付与すると内部でHTTPサーバが起動してブラウザでドキュメントが見れるようになります。こっちの方が見やすいことも多いので、このオプションは嬉しい。

$ go version
go version go1.25rc1 linux/amd64

# オプション無し
$ go doc sync.waitgroup.go
package sync // import "sync"

func (wg *WaitGroup) Go(f func())
    Go calls f in a new goroutine and adds that task to the WaitGroup. When f
    returns, the task is removed from the WaitGroup.

    The function f must not panic.

    If the WaitGroup is empty, Go must happen before a WaitGroup.Wait.
    Typically, this simply means Go is called to start tasks before Wait is
    called. If the WaitGroup is not empty, Go may happen at any time. This means
    a goroutine started by Go may itself call Go. If a WaitGroup is reused to
    wait for several independent sets of tasks, new Go calls must happen after
    all previous Wait calls have returned.

    In the terminology of the Go memory model, the return from f "synchronizes
    before" the return of any Wait call that it unblocks.

[the Go memory model]: https://go.dev/ref/mem

# オプション有り
$ go doc -http sync.waitgroup.go
go: downloading golang.org/x/pkgsite/cmd/internal/doc v0.0.0-20250608123103-82c52f1754cd
go: downloading golang.org/x/pkgsite v0.0.0-20250608123103-82c52f1754cd
go: downloading golang.org/x/pkgsite v0.0.0-20250530170220-274f41854e53
go: downloading golang.org/x/mod v0.24.0
go: downloading github.com/google/safehtml v0.0.3-0.20211026203422-d6f0e11a5516
go: downloading golang.org/x/net v0.40.0
go: downloading golang.org/x/text v0.25.0
go: downloading rsc.io/markdown v0.0.0-20231214224604-88bb533a6020
go: downloading golang.org/x/sync v0.14.0
go: downloading golang.org/x/tools v0.33.0
go: downloading github.com/google/licensecheck v0.3.1
doc: Documentation server listening on addr http://localhost:42249

初回はモジュールダウンロードが入ります。その後にローカルにリスナーが起動して、そのままブラウザも起動してくれます。

参考情報

Goのおすすめ書籍


過去の記事については、以下のページからご参照下さい。

サンプルコードは、以下の場所で公開しています。

Goメモ-589 (Go 1.25でsync.WaitGroupにGoメソッドが追加)(Go 1.25 rc1)

関連記事

GitHub - devlights/blog-summary: ブログ「いろいろ備忘録日記」のまとめ

概要

以下、自分用のメモです。前回の続きです。

devlights.hatenablog.com

Go 1.25 のドラフトリリースノートは以下。

tip.golang.org

上記を見ると、Go 1.25 で sync.WaitGroup に Go メソッドが追加されるみたいですね。

$ go version
go version go1.25rc1 linux/amd64

$ go doc sync.waitgroup.go
package sync // import "sync"

func (wg *WaitGroup) Go(f func())
    Go calls f in a new goroutine and adds that task to the WaitGroup. When f
    returns, the task is removed from the WaitGroup.

    The function f must not panic.

    If the WaitGroup is empty, Go must happen before a WaitGroup.Wait.
    Typically, this simply means Go is called to start tasks before Wait is
    called. If the WaitGroup is not empty, Go may happen at any time. This means
    a goroutine started by Go may itself call Go. If a WaitGroup is reused to
    wait for several independent sets of tasks, new Go calls must happen after
    all previous Wait calls have returned.

    In the terminology of the Go memory model, the return from f "synchronizes
    before" the return of any Wait call that it unblocks.

[the Go memory model]: https://go.dev/ref/mem

WaitGroupを使う場合に大抵記載する以下の処理

wg.Add(1)
go func() {
    defer wg.Done()
    処理
}()

を便利関数として用意してくれた感じ。少し記載が楽になるのと見やすくなりますね。

試してみた

package main

import (
        "log"
        "math/rand"
        "sync"
        "time"
)

func main() {
        log.SetFlags(log.Lmicroseconds)

        if err := run(); err != nil {
                panic(err)
        }
}

func run() error {
        const (
                COUNT = 10
        )
        var (
                wg sync.WaitGroup
        )
        for i := range COUNT {
                wg.Go(func() {
                        delay := time.Duration(rand.Float64()*1000) * time.Millisecond
                        time.Sleep(delay)
                        log.Printf("[%02d] hello world (%v)\n", i+1, delay)
                })
        }

        wg.Wait()

        return nil
}

実行すると以下のようになります。

$ go run .
19:05:07.684969 [10] hello world (300ms)
19:05:07.689136 [08] hello world (304ms)
19:05:07.715336 [07] hello world (330ms)
19:05:07.830717 [01] hello world (445ms)
19:05:07.954246 [06] hello world (569ms)
19:05:08.003209 [05] hello world (618ms)
19:05:08.024737 [09] hello world (639ms)
19:05:08.087019 [04] hello world (702ms)
19:05:08.313381 [02] hello world (928ms)
19:05:08.375850 [03] hello world (990ms)

参考情報

Goのおすすめ書籍


過去の記事については、以下のページからご参照下さい。

サンプルコードは、以下の場所で公開しています。

Goメモ-588 (Go 1.25 rc1)(Go 1.25 is not yet released, rc版などを一時的インストールして遊ぶ)

関連記事

GitHub - devlights/blog-summary: ブログ「いろいろ備忘録日記」のまとめ

概要

以下、自分用のメモです。

Go 1.25 rc1 がいつのまにか出てました。

tip.golang.org

少し試してみたいけど、自分の環境を完全にアップデートするのが嫌な場合、以下のようにすると元の環境を維持したまま遊んだりできます。

# 既にGoがインストールされている場合は以下のコマンドで好きなバージョンをインストールできます。
$ go install golang.org/dl/go1.25rc1@latest

# GOPATH/binにインストールされます
$ ls -1 $(go env GOPATH)/bin | grep 1.25
go1.25rc1

# downloadサブコマンドを付けてダウンロードします
$ go1.25rc1 download
Downloaded   0.0% (   16384 / 59752313 bytes) ...
Downloaded  12.8% ( 7618512 / 59752313 bytes) ...
Downloaded  51.7% (30883616 / 59752313 bytes) ...
Downloaded  92.0% (54951520 / 59752313 bytes) ...
Downloaded 100.0% (59752313 / 59752313 bytes)
Unpacking /home/dev/sdk/go1.25rc1/go1.25rc1.linux-amd64.tar.gz ...
Success. You may now run 'go1.25rc1'

# ちゃんと動くか確認
$ go1.25rc1 version
go version go1.25rc1 linux/amd64

# GOTOOLCHAIN環境変数を設定して go コマンドを一時的に go1.25rc1 に変更しておく
$ go version
go version go1.24.4 linux/amd64

$ export GOTOOLCHAIN=go1.25rc1
$ go version
go version go1.25rc1 linux/amd64

# 元に戻す
$ export GOTOOLCHAIN=auto

これで、このセッション上では go build とかしても go1.25rc1 が利用されます。

参考情報

Goのおすすめ書籍


過去の記事については、以下のページからご参照下さい。

サンプルコードは、以下の場所で公開しています。

Goメモ-587 (Go言語におけるエラーハンドリングの冗長性についてのGoチームの回答)([ On | No ] syntactic support for error handling)

関連記事

GitHub - devlights/blog-summary: ブログ「いろいろ備忘録日記」のまとめ

概要

以下、自分用のメモです。

Go本家ブログにて以下の記事がポストされました。

go.dev

要約すると、Goにて長年議論されているエラーの扱い方である

if err != nil {
    ...
}

というハンドリング方法について、Goチームからの回答がポストされたという記事ですね。

結論として以下のようになっています。

For the foreseeable future, the Go team will stop pursuing syntactic language changes for error handling. We will also close all open and incoming proposals that concern themselves primarily with the syntax of error handling, without further investigation.

当面の間、Go チームはエラー処理に関する言語構文の変更を中止します。また、エラー処理の構文に主眼を置く、現在進行中および新規の提案はすべて、さらなる調査を行わずにクローズします。

この方針に至るまで、本当に長い間議論が交わされてきましたが、どれも妥当なコンセンサスを得ることが出来なかったとのこと。

なので、今後もGoのエラーハンドリングは今まで通りのやり方が正当な方法となると言うことですね。

色々な意見があると思いますが、個人的な意見でいうと私自身はこの決定に「賛同」です。私自身、実務では業務アプリケーションを開発してお客様にご納品する仕事形態のエンジニアであるのもあるかもしれませんが、エラー処理というのは「省略」するものでも、「楽」するものでも無いという立ち位置です。何らかの呼び出しを行った結果、エラーが発生した場合は「その場所」で捉えて処置し、必要であれば情報を付与して上位に連携するという認識。なので、Goのこのエラーハンドリングスタイルは、そもそも変だとか面倒だとか感じていませんし、古臭いとも感じていません。

個人的にはちょっとホッとしたというのが正直な感想。このエラーハンドリングについての思想はGoらしい部分と思いますので、変わってほしくないですね。シンプルなままが良い。

参考情報

go.googlesource.com

github.com

seankhliao.com

Goのおすすめ書籍


過去の記事については、以下のページからご参照下さい。

サンプルコードは、以下の場所で公開しています。

Goメモ-586 (デバッグビルドとリリースビルド時によく利用するフラグ)

関連記事

GitHub - devlights/blog-summary: ブログ「いろいろ備忘録日記」のまとめ

概要

以下、自分用のメモです。たまに、使いたいときに忘れているのでここにメモメモ。。。

似た内容を以前にも記載しているのかもしれませんが、、。

ガッチリとデバッグしたい場合は「最適化」と「インライン化」を無効にしてビルドするのが定石です。

ガッチリとリリースビルドしたい場合は、「シンボル」と「DWARF(デバッグ情報)」を除去してビルドするのが定石です。

試してみる

ソースコード

実装内容に意味はないです。

package main

import (
    "io"
    "log"
    "sync"
    "time"
)

func main() {
    log.SetFlags(0)
    log.SetOutput(io.Discard)

    if err := run(); err != nil {
        panic(err)
    }
}

// この関数は最適化が有効な場合、直接 v*2 になる可能性があり
// インライン化が有効な場合、インライン化される可能性がある
func calc(v int) int {
    v1 := v
    v2 := 2
    return v1 * v2
}

func run() error {
    const (
        COUNT   = 10000000
        WORKERS = 4
    )
    var (
        ch = make(chan int)
        wg sync.WaitGroup
    )

    // producer
    wg.Add(1)
    go func(ch chan<- int) {
        defer wg.Done()
        defer close(ch)

        for i := range COUNT {
            ch <- calc(i)
        }
    }(ch)

    time.Sleep(10 * time.Millisecond)

    // consumer
    for range WORKERS {
        wg.Add(1)
        go func(ch <-chan int) {
            for v := range ch {
                log.Println(v)
            }
        }(ch)
    }

    wg.Done()

    return nil
}

デバッグ

go build -gcflags "all=-N -l" -o debug main.go
オプションの意味
      # gcflags の all=-N -l の意味 (goコンパイラに対しての指示) (go tool compile -help)
      #   all= は全てのパッケージが対象という意味
      #   -N   は最適化無効という意味 (No optimization)
      #   -l   はインライン化無効という意味 (No inlining)

リリース

go build -ldflags "-s -w" -o release main.go
オプションの意味
      # ldflags の -s -w の意味 (リンカに対しての指示) (go tool link -help)
      #    -s   はシンボルテーブル削除という意味
      #    -w   はDWARF情報削除という意味(デバッグ情報)

Taskfile.yml

# https://taskfile.dev

version: '3'

vars:
  RE1: main\.go.*inlining call.*$

tasks:
  default:
    cmds:
      # gcflags の all=-m -N -l の意味 (goコンパイラに対しての指示) (go tool compile -help)
      #   all= は全てのパッケージが対象という意味
      #   -m   はビルド時のコンパイラの詳細情報を出力せよという意味
      #   -N   は最適化無効という意味 (No optimization)
      #   -l   はインライン化無効という意味 (No inlining)
      - cmd: go build -gcflags "all=-m -N -l" -o debug main.go 2>&1 | grep "{{.RE1}}"
        ignore_error: true
      - cmd: go build -gcflags "all=-m" -o normal main.go 2>&1 | grep "{{.RE1}}"
        ignore_error: true
      # ldflags の -s -w の意味 (リンカに対しての指示) (go tool link -help)
      #    -s   はシンボルテーブル削除という意味
      #    -w   はDWARF情報削除という意味(デバッグ情報)
      - cmd: go build -gcflags "all=-m" -ldflags "-s -w" -o release main.go 2>&1 | grep "{{.RE1}}"
        ignore_error: true
      - ls -l {normal,debug,release} | awk 'NF>1 {print $5, $NF}'

実行すると以下のような感じです。

task: [default] go build -gcflags "all=-m -N -l" -o debug main.go 2>&1 | grep "main\.go.*inlining call.*$"
task: [default] go build -gcflags "all=-m" -o normal main.go 2>&1 | grep "main\.go.*inlining call.*$"
./main.go:55:16: inlining call to log.Println
./main.go:60:9: inlining call to sync.(*WaitGroup).Done
./main.go:44:14: inlining call to calc
./main.go:40:16: inlining call to sync.(*WaitGroup).Done
./main.go:57:4: inlining call to run.func2
./main.go:57:4: inlining call to log.Println
./main.go:11:14: inlining call to log.SetFlags
./main.go:12:15: inlining call to log.SetOutput
./main.go:11:14: inlining call to log.(*Logger).SetFlags
./main.go:11:14: inlining call to atomic.(*Int32).Store
task: [default] go build -gcflags "all=-m" -ldflags "-s -w" -o release main.go 2>&1 | grep "main\.go.*inlining call.*$"
./main.go:55:16: inlining call to log.Println
./main.go:60:9: inlining call to sync.(*WaitGroup).Done
./main.go:44:14: inlining call to calc
./main.go:40:16: inlining call to sync.(*WaitGroup).Done
./main.go:57:4: inlining call to run.func2
./main.go:57:4: inlining call to log.Println
./main.go:11:14: inlining call to log.SetFlags
./main.go:12:15: inlining call to log.SetOutput
./main.go:11:14: inlining call to log.(*Logger).SetFlags
./main.go:11:14: inlining call to atomic.(*Int32).Store
task: [default] ls -l {normal,debug,release} | awk 'NF>1 {print $5, $NF}'
2279432 debug
2313124 normal
1503416 release

デバッグビルド用のフラグを設定した場合、インライン化が実施されていませんね。

何故か、通常時よりデバッグビルド用のフラグを設定した方が少しファイルサイズが減るのは謎。

参考情報

Goのおすすめ書籍


過去の記事については、以下のページからご参照下さい。

サンプルコードは、以下の場所で公開しています。

Goメモ-585 (main関数のテストをしたい場合に使えるTips)(標準出力を差し替え)

関連記事

GitHub - devlights/blog-summary: ブログ「いろいろ備忘録日記」のまとめ

概要

以下、自分用のメモです。たまに、使いたいときに忘れているのでここにメモメモ。。。

以下のコンテキストはコマンドラインアプリの場合とします。

たまに、main関数自体のテストを通したいときがあります。Exampleテストでも良いのですが、例としてのテストでは無いのでExampleと名前が付くのがちょっと嫌。

Testほにゃららのような形でmain関数の出力を検証したい。本来であれば、中で呼び出される関数群を適切に設計・実装し、それらをテストすれば事足りるのですが、現実そんなにうまくいかない場合も多いです。

そういうときは、テストコード内で一時的に標準出力をパイプなりに差し替えてしまって、関数実行後に出力内容を検証すれば、あまり凝ったことせずにmainの出力をテスト出来ます。

今回は、os.Pipe を利用した版です。

サンプル

main.go

実装に特に意味はありません。

package main

import (
    "flag"
    "fmt"
    "strings"
)

type (
    vars []string
)

func (me *vars) String() string {
    return fmt.Sprint(*me)
}

func (me *vars) Set(v string) error {
    *me = append(*me, v)
    return nil
}

var (
    _ flag.Value = (*vars)(nil)
)

func main() {
    var (
        vs vars
    )
    flag.Var(&vs, "v", "values")
    flag.Parse()

    fmt.Println(strings.Join(vs, ","))
}

main_test.go

テストの実装。ここで実行時に標準出力のファイルディスクリプタを差し替えて処理を実行させます。

完了したら、元に戻す。

package main

import (
    "bytes"
    "io"
    "os"
    "sync"
    "testing"
)

func TestMainOutput(t *testing.T) {
    // 元の標準出力を退避させ、パイプのWriter側に差し替え
    old := os.Stdout
    r, w, _ := os.Pipe()
    os.Stdout = w
    defer func() {
        // 元に戻す。このプログラムはこのまま終了するので別にしなくて良いが習慣として。
        os.Stdout = old
    }()

    // コマンドライン引数
    os.Args = append(os.Args, "-v", "hello", "-v", "world", "-v", "へろー", "-v", "ワールド")

    var wg sync.WaitGroup
    wg.Add(1)

    // パイプはノンバッファリングなので非同期処理が必須
    go func() {
        defer wg.Done()
        defer w.Close()
        main()
    }()

    wg.Wait()

    // 出力内容を確認
    //   出力量が分かっている場合は以下でも良いが
    //   不明な場合は [io.Reader.Read()] をちゃんとループ処理して
    //   読み込む処理にする必要がある。
    want := []byte("hello,world,へろー,ワールド\n")
    got, _ := io.ReadAll(r)
    if !bytes.Equal(want, got) {
        t.Errorf("want: %s\tgot: %s", want, got)
    }
}

実行

$ task
task: [default] go build -o app .
task: [default] ./app -v "hello" -v "world" -v "へろー" -v "ワールド"
hello,world,へろー,ワールド
task: [default] go test .
ok      github.com/devlights/try-golang/examples/singleapp/main_stdout_ospipe   0.002s

参考情報

Goのおすすめ書籍


過去の記事については、以下のページからご参照下さい。

サンプルコードは、以下の場所で公開しています。

Goメモ-584 (flagパッケージ再入門)(08-複数回指定可能なパラメータを用意)

関連記事

GitHub - devlights/blog-summary: ブログ「いろいろ備忘録日記」のまとめ

概要

以下、自分用のメモです。

前にflagパッケージについてのメモをいろいろ書いてたのですが

そういえば、複数回指定できるパラメータの作成についてはメモしていないなと思ったので、メモ残しておくことにしました。

複数回指定可能なパラメータを実現するには、flag.Var() を使います。flag.Valueインターフェースを実装した型を定義してそこに追加していく感じです。

サンプル

main.go

package flags

import (
    "flag"
    "fmt"

    "github.com/devlights/gomy/output"
)

type (
    varsFlag []string
)

func (me *varsFlag) String() string {
    return fmt.Sprint(*me)
}

func (me *varsFlag) Set(v string) error {
    *me = append(*me, v)
    return nil
}

var (
    // varsFlag は flag.Value を実装している
    _ flag.Value = (*varsFlag)(nil)
)

// Var2 は、flagパッケージのflag.Var()のサンプルです。
//
// コマンドライン引数にて同じオプションを複数回指定された場合に対応できる
// カスタムフラグを定義して、値を読み取っています。
//
// # REFERENCES
//   - https://pkg.go.dev/flag@go1.24.3
func Var2() error {
    var (
        fs   = flag.NewFlagSet("vars", flag.ExitOnError)
        vars varsFlag
    )
    fs.Var(&vars, "v", "文字列値。複数指定可能。")

    var (
        opts = []string{
            "-v", "hello",
            "-v", "world",
            "-v", "へろー",
            "-v", "ワールド",
        }
        err error
    )
    err = fs.Parse(opts)
    if err != nil {
        return err
    }

    var (
        ch = make(chan string)
    )
    go func(ch chan<- string, vars []string) {
        defer close(ch)
        for _, item := range vars {
            ch <- item
        }
    }(ch, vars)

    for item := range ch {
        output.Stdoutl("[vars]", item)
    }

    return nil
}

実行

$ task
task: [build] go build -o "/workspaces/try-golang/try-golang" .
task: [run] ./try-golang -onetime

ENTER EXAMPLE NAME: flags_var2

[Name] "flags_var2"
[vars]               hello
[vars]               world
[vars]               へろー
[vars]               ワールド


[Elapsed] 128.76µs

参考情報

Goのおすすめ書籍


過去の記事については、以下のページからご参照下さい。

サンプルコードは、以下の場所で公開しています。