Zenn
Open65

Goメモ

RerrahRerrah

モジュール/パッケージ

1リポジトリが1モジュールに対応する.リポジトリのルート階層(モジュールのトップディレクトリ)にはgo.modが配置される.以下のコマンドで作成される.

go mod init モジュール名(リポジトリのアドレス)
RerrahRerrah
  • 1モジュールに1つ以上のパッケージが存在する.
  • 1モジュールには必ず一つは"main"パッケージが存在し,mainパッケージにはmain関数が存在する.main関数はこのモジュールのエントリーポイントとなる.
  • パッケージはpackage パッケージ名のように同じパッケージ名で宣言したファイル群で構成される.
  • 同じパッケージに属するファイルは同じフォルダーにまとめて配置する.
RerrahRerrah
  • パッケージのインポートはimportで行う.importの後は括弧で複数のパッケージを同時にインポートできる.インポートするパッケージは"モジュール名/(フォルダー階層に応じた)パッケージ名"で指定する.
  • パッケージ名の前に任意の名前を設定することで,パッケージの別名を設定できる(Pythonのasと同じ).
  • パッケージの別名を.にすると,パッケージ名を省略してインポートできる.
import (
    "fmt"    // 標準パッケージ
    "my-module/subdir/hoge"  // そのほかのパッケージ
    fuga "my-module/subdir/fuga"    // "fuga"という別名をつける
    . "my-module/subdir/piyo"    // "piyo"パッケージ内のpublicな変数,関数はパッケージ名を省略して使用できる
)
RerrahRerrah

mainパッケージにはmain関数以外にinit関数がある.これはmain関数の実行前に呼び出される.
パッケージインポート時にパッケージ内の変数の初期化などに使われる.

RerrahRerrah

変数・関数の公開範囲

Goではパッケージ単位で公開するか (exported) どうかを制御する.制御は変数・関数名の先頭文字のキャピタライズで決まる.

文字種 制御
Hoge 大文字 パッケージ内外で参照可能.
fuga 小文字 同一パッケージのみ参照可能.

https://zenn.dev/msksgm/articles/20220527-go-package-scope

RerrahRerrah

配列

固定長配列.初期化後に要素数を変更することはできない.

// 初期化
a := [2]int
b := [2]int{1, 2}
c := [...]int{1, 2, 4}   // 要素数を...で指定すると数を自動で推定する

len(c)    // len()で長さを取得
RerrahRerrah

スライス

可変長配列.要素を追加・削除できる.変数の値はメモリの実体への参照値.
参照値なので,スライスを操作すると,元のスライスの値も変更される.
実際の長さと容量の概念がある(C++のstd::vectorと同じ).

// 初期化
a := []int
b := []int{1, 2}
c := make([]int, 2)    // make() で作成できる
d := make([]int, 3, 4)   // lenは3, capは4
RerrahRerrah

インデックスのスライス指定で要素の一部をviewとして取得可能.
スライスで先頭を削った時のlenとcapは,それぞれ削られた分だけ少なくなる.後ろを削った時はlenのみ減りcapは変わらない.

d[1:]    // 先頭から最後まで.lenは1,capは2
d[:2]    // 先頭から2番目の直前まで.lenは2,capは4
d[:]    // 全要素の参照.
RerrahRerrah

スライスの要素を追加するときはappend()を使う.このときcapが足りない場合は新しいインスタンスとして作成される.
スライスをディープコピーするときはcopy()を使う.

s := []int{1, 2, 3}
s = append(s, 4, 5)    // [1, 2, 3, 4, 5]

l := make([]int, len(s))
copy(s, l)    // sの要素をlにコピー
RerrahRerrah

pack/unpack演算子

...型でpack,変数...でunpack.

// 可変長のintの引数をargvという変数に[]intでpack
func Hoge(argv ...int) {
s := []int{}
append(s, argv...)   // スライスsの末尾にargvの要素をunpackして追加
}
RerrahRerrah

マップ

連想配列.参照型.値の取得時には第2戻り値が存在フラグのboolとなっている.

// 初期化
a := map[int]int{ 1: 1, 2: 2}
b := make(map[int]int)

// 値の登録
b[1] = 1
b[2] = 2

// 値の取得
v1 = b[1]
v2, ok := b[0]    // 第2戻り値は存在確認フラグ
if ok {
    fmt.Println(v2)
}

// 値の削除
delete(b, 1)
RerrahRerrah

変数/定数

変数はvar 変数名 型で行う.一応型名は省略できる.
セイウチ演算子:=を使うとvarや型指定を省略できる.ただし:=はグローバルスコープで使用できない.

var hoge int    // 初期値は0
// piyo := 0  グローバルスコープでは ":=" が使えない
func huga() {
    piyo := 0
}
RerrahRerrah

複数の変数を同時に宣言できる.代入も一緒にできる.

var a, b int = 1, 2
c, d := "aaa", "bbb"
var (
    e = 1
    f = "wow"
)
RerrahRerrah

constを使うと定数を宣言できる.
constを連続で定義し,一部の定義の値を省略すると,その直前に定義した値が設定される.

const (
    A = 1
    B    // 1
    C    // 1
    D = "Hi"
    E    // "Hi"
)

iotaを使うと,連番が設定できる.

const (
    A = iota    // 0
    B    // 1
    C    // 2
)
RerrahRerrah

関数

関数の戻り値は複数設定できる.受け取る側は_で値を捨てられる.

func test() (int, int) { return 1, 2 }
_, b := test()    // bは2

戻り値の個数よりも受け取る変数が少ない場合は,あふれる値を破棄する.

a := test()    // aは1

戻り値の宣言にそれ用の変数を設定することができる.このときreturnは必須だが値を返さなくても,戻り値の変数に値が設定されていれば問題ない.

func test() (a, b int) {
    a = 1
    b = 2
    return
}
RerrahRerrah

クロージャー

TypeScriptのように,関数を返す関数で,返す関数と同じレベルで宣言された変数を,返す関数内で使用すると,その変数は外に出された関数に参照として生存する.

func test(v int) func (int) int {
    x := v + 1
    return func (y int) int {
        x++;    // クロージャーを実行するごとにxは1増える
        return y + x;
    }
}
RerrahRerrah

クロージャーを使うとジェネレーターも作れる.

func generator(init, add int) func() int {
    val := init
    return func() int {
        defer func() { val += add }()
        return val
    }
}
 
gen := generator(0, 2)
fmt.Println(gen())    // 0
fmt.Println(gen())    // 2
fmt.Println(gen())    // 4
RerrahRerrah

if文

ifは初期化と条件式を一緒に書ける.

if a := 1; a + b < 9 {
    /* ... */
}
RerrahRerrah

for文

初期化式と事後処理を省略できる(いわゆるwhile).
そのうえ条件式までも省略できる(いわゆるwhile true).

for i := 1; i < 10; i++ {}
for i < 10 {}  // while (i < 10) {} と同じ
for {} // while (true) {} と同じ
RerrahRerrah

rangeを使うと,インデックスと値を列挙して取得できる.(pythonのenumerateと同じ)

for index, value := range []int{1 , 2, 3} {
    fmt.Printf("[%v]: %v\n", index, value)
}
RerrahRerrah

ネストしたforから特定のレベルのforまで出たいときは,その対象となるforにラベルを付け,breakやcontinue時にそのラベルを引数に指定する.(C++のgoto ラベルのようなもの)

for {
   LABEL:
    for {
        for {
            break LABEL
        }
    }
}
RerrahRerrah

switch文

  • switchのcaseの最後にbreakは不要.
  • switchでも初期化式を設定できる. (switch v := 2; v { ... })
  • caseには複数の条件を同時に設定できる (case 1, 2:)
  • caseは条件式もあてはめられる (case a < 10). このときはswitchの条件式も省略できる.
  • switch内で値の完全一致caseと条件式caseは混在できない.
RerrahRerrah

any/interface{}

C++のstd::anyと同じ.あらゆる値が代入できる.初期値はnil

interface{}型を.(T)で特定の型にキャストする方法を型アサーションと呼ぶ(swiftのas?と同じ).戻り値の第2でアサーション結果が真偽値で取得できる.

var any interface{}
any = 1
v, res = any.(int)    // v == 1. res == true
_, res = any.(string)    // res == false
RerrahRerrah

switch文ではinterface{}.typeを使って型マッチングできる.

func TypeMatch(any interface{}) {
    switch v: = any.(type) {
        case int:
            return fmt.Sprintf("int %v", v)
        case string:
            return fmt.Sprintf("string %v", v)
        defaulr:
            return "???"
    }
}
RerrahRerrah

defer文

  • そのスコープの終了時に実行したい関数をdefer文で登録できる.
  • 1つのdefer文では1関数のみ登録できる.
  • defer文は複数登録できる.実行順序はFILO.
  • 関数以外のものや複数の関数をまとめて登録する場合は即時関数で記述する.
defer fmt.Println("DEF")
defer func() { fmt.Println("ABC") }() 
// 呼び出されるのは"ABC" "DEF"の順番
RerrahRerrah

構造体

構造体は値型.
関数の引数で渡すときはそのままだとコピーとなるため,ポインターとして生成する方法が推奨される.

type Hoge struct {
   Fuga, Piyo int
}
a := Hoge{Fuga: 1}
b := &Hoge{}    // 関数の引数に渡すときにポインター渡しとする
RerrahRerrah
  • 関数宣言でレシーバーを追加することで,その構造体やinterfaceのメソッドを実装できる.
  • レシーバーには値レシーバーとポインターレシーバーが存在する.
    • 値レシーバーは構造体のコピーがselfやthisとして渡されるイメージ
    • ポインターレシーバーは構造体の参照がselfやthisとして渡されるイメージ
  • 基本的にはポインターレシーバーを使う.
type Hoge struct {}
func (Hoge)Fuga() int { return 1 }    // 値レシーバー
func (*Hoge)Piyo() int { return 2 }  // ポインターレシーバー
RerrahRerrah

構造体のコンストラクターはいわゆるファクトリー関数で実装する.

// ポインターを返すことでメモリを節約する
func NewT(arg1 int, arg2 int arg3 int) *T {
    return &T{Arg1: arg1, Arg2: arg2, Arg3: arg3}
}
RerrahRerrah

Goでは継承ではなく包含関係で移譲として表現する.つまりPolymorphではない.

構造体のなかに別の構造体のフィールドを設定するとき,変数名を入れずに型名だけ指定すると,親構造体のフィールドに子構造体のフィールドが展開されて宣言される.

type Hoge struct { Hogehoge int }
type Fuga struct { Hoge }
a := Hoge{}
a.Hoge.Hogehoge
a.Hogehoge    // 変数名を省略すると透過的に参照可能
RerrahRerrah

独自型

リテラル型などをtypeで別名をつけたもの.
元のリテラル型からの代入は可能だが,それ以外は元リテラル型とは別の型として扱われる.
またリテラル型ではメソッドを生やせないが,独自型にすることでメソッドを実装できる.

type Id uint
var id Id = 0    // uintからの代入可能
// id = id + 1 はIdとuintの計算なので型の不一致で実行不可
 
// 独自型にはメソッドを実装できる
func (id Id)Stringer() string { return fmt.Printf("%v", id) }
RerrahRerrah

インターフェース

Rustでいうプロトコル.構造体のメソッド宣言のみ定義したもの.
その実態はインスタンスの型情報(.type)とインスタンスへのアドレスを保持した構造体.
構造体なので値型.

interface Person {
    func Say() string
}

構造体には明示的にどのインターフェースを実装したかを記載せず,同名のメソッドを実装する.

struct Hi {}
func (Hi)Say() string { return "yay" } 
struct Yo {}
func (*Yo)Say string { return "yo" }
 
persons := []Person{ &Hi{}, &Yo{} }

インターフェースへの代入はその代入する値の実体(ポインターなら参照先)がインターフェースで定義したメソッド群(メソッドセット)に一致したときに代入可能と判定する.

RerrahRerrah

関数の引数にinterfaceを受け取るときは,通常値型とする.

func Wow(person Person) {
   // ...
}

インターフェースのメソッド呼び出しは,内部歴にはインスタンスとして渡された実体のメソッドを.演算子で呼び出す形になる.
しかしインターフェースのポインターを引数とした場合,.演算子はポインターの参照先(インターフェース構造体の実体)に実装されているメソッドを探す.
そのためインターフェースが参照する実体のメソッドに辿り着けずエラーとなる

RerrahRerrah

エラー処理

失敗が発生しうる関数は,その戻り値をいわゆるResult型のような構成にする.
第2戻り値をerror型とし,その値がnilであれば処理は失敗したと判定する.

if v, err := something(); err != nil {
    /* vが有効値 */
}
RerrahRerrah

独自エラー型を作成する場合は標準インターフェースerrorを実装したエラー構造体を作成する.

type error interface {
    func Error() string
}

関数の戻り値として渡すときはerror型を返すようにし,実際はカスタムエラーのインスタンスの参照を返すのが良い.

type MyError struct {
    ErrorCode int
}
func (MyError)Error() string { return "ah!" }
 
func Something() (int, error) {
    return 0, &MyError{ErrorCode: 1}    // ポインターを返す
}
 
_, err := Something()
err.(*MyError)    // 型アサーションでインスタンスの値を取り出す.インターフェースの実体は*MyError型なのでそれにキャストする
RerrahRerrah

テスト

  • 単体テストは対象となる処理が記述されるファイルに対して,"対象ファイル名_test.go"というファイルに記述する.
  • テストファイルはテスト対象ファイルと同じパッケージにする.
  • テストファイルではimport "testing"をしておく.
  • テストケースの関数名はfunc TestXXX(t *testng.T)としておく.
  • テストの実行はgo test 対象ディレクトリ/...
    • "..."でサブディレクトリのテストも実行する.
main.go
package main

func IsOdd(v int) bool {
   return v % 2 == 0
}

func main() {
   _ = IsOdd(1)
}
main_test.go
package main

import "testing"

func TestMain(t *testing.T) {
    conditions := []struct {
        input int
        output bool
    }{
        {1, false},
        {2, true}
    }

    for _, test := range conditions {
        res := IsOdd(test.input)
        if res != test.output {
            t.Errorf("Expected %v, but actual %v\n", test.output, res)
        }
    }    
}
RerrahRerrah

チャネル

goroutine間でデータをやり取りするためのソケットのようなもの.参照型.
チャネルに対してデータの送受信が完了されるまでスレッドはブロックされる.
チャネルのバッファイサイズはスライスと同じ要領で調べられる.
バッファはFIFOキュー.

var c1 chan int    // intの値をやり取りする双方向チャネル
var c2 <-chan int // intの値をやり取りする受信専用チャネル
var c3 chan<- int // intの値をやり取りする送信専用チャネル
ch4 := make(chan int)  // バッファサイズ1,セマフォ1
ch5 := make(chan int, 2) // バッファサイズ2,セマフォ2
 
c1 <- 1  // データ送信
v, ok := <-c1  // データ受信.第2戻り値は受信できたかどうか
len(c1) 現在バッファされている要素数
cap(c1) バッファサイズ
RerrahRerrah

チャネルでの送受信が終了したときは,チャネルリークを防ぐためcloseを行う.

  • チャネルをclose後にチャネルへ送信しようとするとpanicになる.
  • チャネルのclose後は常にチャネルから受信できるが,第1戻り値は型のデフォルト値,第2戻り値はfalseとなる.

この機能を用いてgoroutineの終了操作を行う,

// groutine 1
ch <- 1
close(ch)

// groutine 2
for {
    if v, ok := <-ch; !ok {
        // 終了操作
        break
    }
}

forのrangeでチャネルを受け取るようにしていれば,より簡略化できる.

// あるいは
for v := range ch {
    // something
}
RerrahRerrah

select文

複数のチャネルの送受信に対する処理をswitchのようにチャネル操作ごとにケースわけして記載できる.
caseの検証順序は実行に偏りが起こらないようランダムに選択される.
通常のチャネルの受信では,値がない場合は処理をブロックするが,selectではdefaultを使って処理を続けることができる.

select {
    case v := <-ch1:
        // ch1から値をとれた時の処理
    case v:= <-ch2:
        // ch2から値をとれた時の処理
   default:
        // ch1,ch2のいずれからも値が取れなかった時の処理
}
RerrahRerrah

syncパッケージ

非同期処理のユーティリティパッケージ.

https://pkg.go.dev/sync

sync.Mutex

mutex.Mutex.Lock()Mutex.Unlock()で排他制御ができる.

mutex := new(syc.Mutex)

// Mutex自体は値型なので,関数の引数で渡すときはポインターで渡す
func Routine1(mutex *sync.Mutex) {
    mutex.Lock()
    res.modify()
    mutex.Unlock()
}
RerrahRerrah

sync.WaitGroup

複数のgoroutuneがすべて終わるまでメインスレッドの処理をwaitさせる.

wg := new(sync.WaitGroup)
wg.Add(2)   // カウンターを2にする

go func() {
    wg.Done()    // 完了したらカウンターを減らす
}()

go func() {
    wg.Done()    // 完了したらカウンターを減らす
}()

wg.Wait()    // カウンターが0になるまでメインスレッドを待機させる
RerrahRerrah

そのほか一度しか複数のgoroutineからコールされても一度しか実行されないOnceFunc(C++のstd::call_onceと同じ),MutexなMapなどが用意されている.

RerrahRerrah

jsonパッケージ

JSON操作のユーティリティーパッケージ.

https://pkg.go.dev/encoding/json

import (
    "json"
    "fmt"
)

type Data struct {
    // `json:キー名`でタグをつける
    Id int `json:"id"`
    Name string `json:"name"`
}

// マーシャリング
data := Data{Id: 3, Name: "Thuler"}
if bs, err := json.Marshal(data); err == nil {
    fmt.Println(string(bs))    // "{"id":3, "name":"Thuler"}"
}

// アンマーシャリング
data2 := new(Data)
if err := json.Unmarshal(bs, data2); err == nil {
    ...
}

MarshalJSONUnmarshalJSONをデータの構造体のメソッドとして実装すると,マーシャリング・アンマーシャリング時に,フィールドの値を整形するミドルウェアとして設定できる.

RerrahRerrah

contextパッケージ

ある処理によって複数のgoroutine間で処理の終了を制御するための構造体context.Contextを提供するパッケージ.

https://pkg.go.dev/context

通常データ問い合わせやデータ編集など様々な手続きをgoroutineとして実行していくと,goroutineから別のgoroutineを呼び出すことになる.よってgoroutineの呼び出し関係は木構造となる.このとき,あるgoroutineでのキャンセル処理で子孫goroutineを終了させるのか続行させるのかそれぞれ制御する必要がある.チャネルで制御するには複雑なため,goroutine間で共通の「コンテキスト」を作成し,それの状態によって書くgoroutineの続行・終了条件を各自が参照して判定できるようにする.

https://zenn.dev/hsaki/books/golang-context

RerrahRerrah

ContextWithCancelによるキャンセル通知やWithDeadlineWithTimeoutによるタイムアウト処理ができる.制御されるgoroutineはselectなどでContext.Done()によって返される受信チャネルからの応答を補足することで,終了タイミングを補足できる.

Contextは別のContextと依存関係を持たせることができ,親がキャンセルされると子もキャンセルされる.

RerrahRerrah

url/netパッケージ

URL文字列をパース・作成するユーティリティ関数群を提供するパッケージ.
クエリ文字列やフラグメントも取得できる.

https://pkg.go.dev/net/url

RerrahRerrah

url/httpパッケージ

クライアント・サーバーモデルでの通信をを扱うパッケージ.

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

RerrahRerrah

クライアント操作

http.Get

REST APIのGETメソッド.

// GETメソッドで取得
res, err := http.Get("url")

res.StatusCode   // HTTPステータスコード
res.Proto   // プロトコル
res.Header  // ヘッダー情報 (map[string]string)
res.Request.Method    // リクエストRSETメソッド
res.Request.URL    // リクエスト先URL

body, _ := ioutil.ReadAll(res.Body)    // レスポンスボディを取得
res.Body.Close()    // レスポンスボディ取得したらクローズする

http.Post

REST APIのPOSTメソッド.リソースタイプとリソースを指定できる.

jsonText := `{"year":2023,"rank":1}`

res, err := http.Post("url", "application/json", bytes.NewBuffer([]byte(jsonText)))
body, _ := ioutil.ReadAll(res.Body)
res.Body.Close()

http.PostForm

REST APIのPOSTメソッド.ボディにパラメーターを指定できる.

// パラメーターの設定
values := url.Values{}
values.Add("name", "Yoshito")
values.Add("number", 13)
 
// POSTメソッドで取得
res, err := http.PostForm("url", values)
body, _ := ioutil.ReadAll(res.Body)
res.Body.Close()

http.Client

クライアントを作成し,任意のREST APIメソッドを指定してリクエストの送受信を行う.コンテクストを渡してタイムアウトを指定できる.

client := &http.Client{}
res1, _  client.Get("url")    // GETメソッド

// リクエストを作成
ctx := context.withTimeout(context.Background(), 3 * time.Second)
req, err := http.NewRequestWithContext(ctx, http.MethodDelete, "url?id=0", nil)

// リクエスト送受信
res, _ := client.Do(req)
res.Body.Close()
RerrahRerrah

サーバー操作

// エンドポイント"/get"にアクセスしたときのハンドラーを登録
http.HandleFunc("/get", func(w http.ResponseWriter, r *hrrp.Request) {
    // レスポンスを書き込む
    fmt.Fprint(w, "Hi")
})
 
// 8080番ポート,デフォルトマルチプレクサ
http.ListenAndServe(":8080", nil)

またhttp.Serverによるサーバー設定を行った後にServer.ListenAndServeを使ってサーバー立ち上げを行える.

RerrahRerrah

情報をクライアントのブラウザーに保存する仕組み.ログイン時のセッションIDなどを記憶させるためなどに使用する.

https://developer.mozilla.org/ja/docs/Web/HTTP/Guides/Cookies

httpパッケージにてレスポンスに付与してクライアントに送信することが可能.

// クッキー作成時
cookie := &http.Cookie{
    Name: "token",
    Value: "something",  // JWTなど
    HttpOnly: true,    // XSS対策
}
// w http.ResponseWriter
http.SetCookie(w, cookie)
 
// クッキー確認時
// r *http.Request
token, err := r.Cookie("token")
if roken == "ssomething" {
    // Cookie有効
}

https://zenn.dev/chillout2san/articles/4868c825158729

RerrahRerrah

database/sqlパッケージ

SQLデータベースに接続するためのユーティリティー関数を提供するパッケージ.

https://pkg.go.dev/database/sql

RerrahRerrah

SQLの種類によってドライバーを別途インストールしておく必要がある.
PostgreSQLの場合はgithub.com/lib/pq.

go get github.com/lib/pq
PostgreSQLサーバーの設定

Docker Composeを使ってPostgreSQLサーバーを起動させておく.

docker-compose.yml
services:
  db:
    image: postgres:16.4-bullseye
    volumes:
      - ./data:/var/lib/postgresql/data
      - ./init:/docker-entrypoint-initdb.d
    ports:
      - "5432:5432"
    environment:
      POSTGRES_DB: mydb
      POSTGRES_USER: tomato
      POSTGRES_PASSWORD: penne
RerrahRerrah

接続

sql.Openでデータベースに接続する.

main.go
package main

import (
	"database/sql"
	"log"

	_ "github.com/lib/pq"
)

func main() {
	// 接続情報
	dataSource := "port=5432 user=tomato password=penne dbname=mydb sslmode=disable"

	// 接続
	DB, err := sql.Open("postgres", dataSource)
	log.Println("接続中...")
	if err != nil {
		log.Panicf("接続失敗: %v", err)
	}
	defer DB.Close()

	// 接続確認
	err = DB.Ping()
	if err != nil {
		log.Panicf("接続失敗: %v", err)
	}
}
RerrahRerrah

テーブル作成

DB.Execの引数にSQL文を渡すことでコマンドを実行する.

cmd := `
CREATE TABLE IF NOT EXISTS users (
	id SERIAL PRIMARY KEY,
	name VARCHAR(50) NOT NULL,
	created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);`
_, err = DB.Exec(cmd)
if err != nil {
	log.Panicf("テーブル作成失敗: %v", err)
}
RerrahRerrah

レコード挿入

DB.Execは第1引数に渡すSQL文は"$1", "$2", ...の形式で変数展開の指定ができる.展開する変数はDB.Execの第2引数以降に渡す.

names := []string{"Nilton", "Horvy", "Estiven"}
cmd = `INSERT INTO users (name) VALUES ($1)`
for _, name := range names {
    // スライスの各要素に対してINSERTを実行
	_, err = DB.Exec(cmd, name)
	if err != nil {
		log.Panicf("レコード挿入失敗: %v", err)
	}
}
RerrahRerrah

レコード取得

SELECTを実行するときはDB.QueryにSQL文を渡す.戻り値はレコードのイテレーターなので,for rows.Next()でイテレーターを進めながら,rows.Scanで変数に値を書き出す.

cmd = `SELECT id, name FROM users`
rows, err := DB.Query(cmd)
if err != nil {
	log.Panicf("レコード取得失敗: %v", err)
}
defer rows.Close()

for rows.Next() {
	var (
		id   int
		name string
	)
	err = rows.Scan(&id, &name)
	if err != nil {
		log.Panicf("レコード取得失敗: %v", err)
	}
	log.Printf("ID: %d, Name: %s", id, name)
}

// 業読み取り時のエラーチェック
if err = rows.Err(); err != nil {
	log.Panicf("エラー: %v", err)
}
RerrahRerrah

Generics

ジェネリクス.特定のインターフェースを満たす(型制約)という型パラメーターを型や関数に指定する.
インターフェースは型の和集合としても表現できる.

// 型に型パラメーターを指定
// MyType[T]はfmt.Stringerインターフェースを満たす型Tのスライス
type MyType[T fmt.Stringer] []T

// 関数に型パラメーターを指定
// any型つまり何でもOK
func MyFunc1[T any](v T) {
    // ....
}
// 呼び出し時には型パラメーターの具体型を指定する必要があるが型推論で省略できる
MyFunc[int](1)
MyFunc(1)

// 型パラメーターの"int | uint"は interface { int | uint } と同値
func MyFunc2[T int | uint](s []T) T {
    // ...
}

// 型パラメーターの制約に"~"をつけると,その型から派生した型も適合とする
func MyFunc3[T ~int](v T) {
    // ...
}
type MyInt int
MyFunc[](MyInt(1))

// 複数の型パラメーターを同時に宣言可能
// パラメーターAが決まればパラメーターBも決まる
type MyMap[A int | uint, B *A] map[A]B

https://zenn.dev/nobishii/articles/type_param_intro

RerrahRerrah

構造体のジェネリクスはメソッドで新たに型パラメーターを追加できないので,構造体の宣言の部分で先に定義しておく.

RerrahRerrah

いわゆるSet型を作るときなどは,値が比較可能でなければならない.そこでcomparable制約を型パラメーターに課すことができる.

// Tは比較可能な型
type Set[T comparable] map[T]struct{}
 
func NewSet[T comparable](vs ...T) Set[T] {
	s := make(Set[T])
	for _, v := range vs {
		s[v] = struct{}{}
	}
	return s
}
RerrahRerrah

pgx

PostgreSQLのドライバーpqがメンテナンスモードとなっているため,現在はpgxの使用が推奨されている.

https://github.com/jackc/pgx

これ一つでpqのみならずdatabase/sqlの役割も果たす.

RerrahRerrah

stringとrune

GoはUTF-8を問題なく扱えるため,通常はstringで保持して問題ない.しかし,文字数など文字単位で操作が必要な時はruneを扱う.

string[]byteのエイリアスで単なるバイト列だが,runeint32のエイリアスでUnicodeコードポイントとして表現される.そのためruneでは一文字の長さ(lenによるもの)が1であることが保証される.

https://zenn.dev/masafumi330/articles/3286ccbad98892

RerrahRerrah

タグ

変数の宣言時に型名の後に生文字リテラルでタグ名:"タグ値"の形式で「タグ」というメタ情報を変数に付与できる.これにより実行時にメタ情報をリフレクションで読み出して,プログラムの動作を制御することができる.

主なタグにjsonパッケージのjson:"keyname"やgo-playground/validatorのvalidate:"required"がある.

ログインするとコメントできます