Open13

Go学習記録

kushidamkushidam

メソッドのレシーバをポインタにするか値にするか

https://go.dev/tour/methods/8

There are two reasons to use a pointer receiver.
The first is so that the method can modify the value that its receiver points to.
The second is to avoid copying the value on each method call. This can be more efficient if the
receiver is a large struct, for example.
ポインタ・レシーバーを使う理由は2つある。
1つ目は、メソッドがレシーバーが指す値を変更できるようにするため。
2つ目は、メソッド呼び出しのたびに値をコピーするのを避けるためです。これは
例えば、レシーバーが大きな構造体である場合などです。

Goにおけるレシーバ

  • メソッド(関数)がどの型に対して関連付けられているかを示す特別なパラメータ
  • メソッドの宣言で定義され、通常はメソッドの名前の前に来る
type MyStruct struct {
    Value int
}
// 例:(m MyStruct)
func (m MyStruct) GetValue() int {
    return m.Value
}

myVar := MyStruct{Value: 42}
result := myVar.GetValue()

レシーバの2タイプ

  • ポインタ渡し
    レシーバの値を変更可能。
    メソッド内で渡された値を書き換えることができる。
    メソッド呼び出し時に、コピーを作成せず、そのポインタを受け取る。
    メモリの節約、パフォーマンスが向上する場合がある。

  • 値渡し
    レシーバの値が変更不可。
    メソッド内で値を書き換えても、呼び出し元には影響しない。
    メソッド呼び出し時に、コピーが作られる。
    大きな構造体を渡す場合、メモリ使用量やパフォーマンス面でコストがかかる。


type MyStruct struct {
	Value int
}

func (m MyStruct) GetValue() int {
	return m.Value
}

// 値レシーバなので変更されない
func (m MyStruct) SetValue(newValue int) {
	m.Value = newValue
}

// ポインタレシーバなので変更される
func (m *MyStruct) SetValueP(newValue int) {
	m.Value = newValue
}
func main() {

	myVar := MyStruct{Value: 42}
	println("SET Before:", myVar.GetValue())
	myVar.SetValue(2) // 値は変わらない
	println("SetValue After:", myVar.GetValue())
	myVar.SetValueP(12345) // 値が更新される
	println("SetValueP After:", myVar.GetValue())
}
SET Before: 42
SetValue After: 42
SetValueP After: 12345
kushidamkushidam

【Go1.21】for range文で使用できる型【5種類】

配列(Array)
arr := [3]int{1, 2, 3}
for index, value := range arr {
    fmt.Printf("Index: %d, Value: %d\n", index, value)
}
スライス(Slice)
slice := []string{"apple", "banana", "cherry"}
for index, value := range slice {
    fmt.Printf("Index: %d, Value: %s\n", index, value)
}
文字列(String)
str := "Hello, Golang"
for index, char := range str {
    fmt.Printf("Index: %d, Char: %c\n", index, char)
}
マップ(Map)
m := map[string]int{"one": 1, "two": 2, "three": 3}
for key, value := range m {
    fmt.Printf("Key: %s, Value: %d\n", key, value)
}
チャネル(Channel)
ch := make(chan int)
go func() {
    ch <- 1
    ch <- 2
    close(ch)
}()
for value := range ch {
    fmt.Printf("Value from Channel: %d\n", value)
}
kushidamkushidam

internalパッケージ

Goパッケージは、基本的には外部のモジュールに公開される。
パッケージ内のメソッドなどを公開するかは、先頭文字が大文字or小文字で決まるが、パッケージにはその機能がない。
そこで役立つのがinternalパッケージ
internalパッケージの親パッケージ、その子パッケージ以下からしか参照ができず、 外部公開されない
文字通り internal(内部) パッケージとなる。

https://qiita.com/tenntenn/items/d8db61720a5ce7fbdeb6

パッケージA
├── パッケージB
│   ├── パッケージD
│   └── internal
│       └── パッケージE // AやCからは参照できない
├── パッケージC
└── go.mod

internalパッケージは、プロジェクト内でのコードの構造化と整理を行うことができ、プライバシーやモジュールのバージョン管理に役立つ。
一方で、適切に使用することが重要で、不必要に多くのコードをinternalに置くことは避ける。
外部のユーザーに提供する必要がないコードだけをinternalパッケージに配置することが一般的。

kushidamkushidam

go test

  • _test.goと接尾語が付いたファイルを対象にテストを実行する。

  • テストのときのみビルドの対象

  • テストコードをテスト対象のパッケージと異なるパッケージに配備することが可能。

  • テスト対象のパッケージとテストコードのパッケージが同じである必要はない。

  • 通常、テスト対象のパッケージ名の末尾に _test を追加して命名される。

  • テストはエクスポートされた関数しか実行できない。(大文字始まりのメソッド)
    非公開関数までテストを行うと過剰なテストであることが多い。

  • 各テスト関数はデフォルトで個別のゴルーチンで実行される(テストが同時に実行)
    (*testing.T).Parallel メソッドは、テスト関数内でさらに並行性を制御したい場合に使用する

func TestConcurrentStuff(t *testing.T) {
    // テスト関数内での同時実行数を制限
    t.Parallel()

    // テストのロジック
}
kushidamkushidam

interface

構造体(または構造体のポインタ):

package main

import "fmt"

type MyInterface interface {
    MyMethod()
}

type MyStruct struct{}

func (ms MyStruct) MyMethod() {
    fmt.Println("MyMethod Call")
}

func main() {
    var myValue MyInterface
    myValue = MyStruct{}    // MyStruct型をMyInterface型に代入

    // インターフェースを介してメソッドを呼び出す
    myValue.MyMethod()
}

具体的な型:

package main

import "fmt"

type MyInterface interface {
	MyMethod()
}

type MyInt int

func (mi MyInt) MyMethod() {
	// メソッドの実装
	fmt.Println("Hello, 世界")
}

func main() {
	var myValue MyInterface // MyInterface型の変数を宣言
	myValue = MyInt(0)      // MyInt型をMyInterface型に代入

	// インターフェースを介してメソッドを呼び出す
	myValue.MyMethod()
}

インタフェース:

type Interface1 interface {
    Method1()
}

type Interface2 interface {
    Method2()
}

// Interface1 と Interface2 を埋め込む新しいインタフェース
type CombinedInterface interface {
    Interface1
    Interface2
}

kushidamkushidam

PANIC

package main

func main() {
        // 1. 
        defer func() {recover()}()

        // 2. 
        ch := make(chan struct{})

        go func() {
                // 4. 
                panic("PANIC")
                close(ch)
        }()

        // 3.
        <-ch
}
  1. main 関数内で defer ステートメントを使用して匿名関数を登録
    この匿名関数は recover() を呼び出すために使用するが、recover() の戻り値が無視される。

  2. ch という型が struct{} のチャネルを作成。

  3. チャネル ch から値を受信しようとするが、この受信はゴルーチン内で発生する前に実行される。

  4. バックグラウンドで新しいゴルーチンが作成され、panic("PANIC") というパニックを引き起こす。

  5. パニックがゴルーチン内で処理されていないため、プログラム全体がパニックとなる。

プログラムがパニックすると、実行が停止し、パニックの詳細が表示されることがあります。また、recover() 関数が main 関数内の defer ステートメントで呼び出されていますが、その戻り値が無視されているため、パニックの回復は行われません。

このコードを実行すると、ランタイムエラーとしてプログラムが終了し、"PANIC" メッセージが表示される。

kushidamkushidam

err is not nil

package main

import (
	"fmt"
	"io/fs"
)

func main() {
	if err := f(); err == nil {
		fmt.Printf("%T\n", err)
	} else {
		fmt.Println("err is not nil")
	}
}

func f() error {
	var err *fs.PathError
	return err
}
kushidamkushidam

uintptr

https://pkg.go.dev/unsafe

uintptr は以下の目的に使用できます。
uintptr の目的の 1 つは、安全でないメモリ アクセスのために unsafe.Pointer とともに使用されることです。
unsafe.Pointer では算術演算を実行できません。
このような演算を実行するには安全ではない。ポインタは uintptr に変換されます
その後、uintptr で演算が実行されます。
uintptr は unsafe.Pointer に変換されて戻され、アドレスが指すオブジェクトにアクセスします。
https://golangbyexample.com/understanding-uintptr-golang/

package main
import (
    "fmt"
    "unsafe"
)
type sample struct {
    a int
    b string
}
func main() {
    s := &sample{a: 1, b: "test"}
    
   //構造体 s のフィールド b のアドレスの取得
    p := unsafe.Pointer(uintptr(unsafe.Pointer(s)) + unsafe.Offsetof(s.b))
    
    //文字列ポインタに型キャストし、その値を表示する。
    fmt.Println(*(*string)(p))
}
test

unsafe.Pointer を uintptr に変換して出力

package main

import (
	"fmt"
	"unsafe"
)

type sample struct {
	a int
	b string
}

func main() {
	s := &sample{
		a: 1,
		b: "test",
	}
	//uintptrとしてアドレスを取得
	startAddress := uintptr(unsafe.Pointer(s))
	fmt.Printf("Start Address of s: %d\n", startAddress)
}

アドレスなのでマシン依存

Start Address of s: 824633976600

https://golangbyexample.com/understanding-uintptr-golang/

kushidamkushidam

モジュール管理におけるMVS(Minimal Version Selection)

依存するモジュールのバージョンを選択する際、最小バージョンを優先的に選択する。

  1. 現在のビルド リストを作成
  2. すべてのモジュールを最新バージョンにアップグレード
  3. 1 つのモジュールを特定の新しいバージョンにアップグレード
  4. 1 つのモジュールを特定の古いバージョンにダウングレード

*ビルドリスト:特定のビルドで使用するモジュールとバージョン(依存関係)

Go 1.11から導入
https://pkg.go.dev/cmd/go/internal/mvs
https://research.swtch.com/vgo-mvs
https://zenn.dev/akatsuki/scraps/caeea4576de89a

kushidamkushidam

struct{}型と [0]int型の共通点

struct{}:フィールドを持たない構造体
[0]int:要素数0の配列

共通点

  • データを保持するためのメモリ領域を確保する必要がない
kushidamkushidam

コンテキストを用いてゴールーチンのキャンセル処理

https://docs.google.com/presentation/d/1O44HzAhqkGjAGBTsQ2bDrAtPB4VO1e2vFGB5q0ZY11I/edit#slide=id.g4fa5b5dfc5_0_844

context(コンテキスト):Go言語の標準ライブラリで提供される仕組みで、主に以下の目的で使用される

  • ゴールーチンのキャンセルやタイムアウトの管理
  • デッドラインの設定
  • 値のストレージと伝播
package main

import (
	"context"
	"fmt"
	"time"
)

func main() {
	// キャンセル用のコンテキストを作成
	ctx, cancel := context.WithCancel(context.Background())

	// ゴールーチン内でコンテキストを使用
	go process(ctx)

	// 3秒後にキャンセル
	time.Sleep(3 * time.Second)

	// ゴールーチンをキャンセル
	cancel()

	// メインゴルーチンが終了する前に待機
	time.Sleep(1 * time.Second)
}

func process(ctx context.Context) {
	for {
		select {
		case <-ctx.Done():
			fmt.Println("ゴールーチンがキャンセルされました")
			return
		default:
			// 何らかの処理
			fmt.Println("ゴールーチン実行中...")
			time.Sleep(1 * time.Second)
		}
	}
}

context.WithCancel関数を使用して、キャンセル可能なコンテキスト(ctx)と、キャンセル関数(cancel)を作成
context.Background() はルートコンテキストを表す

process関数をゴールーチン内で非同期に実行

ゴールーチンをキャンセルするために、cancel 関数を呼び出す。

process 関数: 以下の処理行う。

無限ループ内でselectステートメントを使用して、コンテキストのctx.Done()チャネルを監視する。
これにより、コンテキストがキャンセルされたかどうかを確認する。

  • ctx.Done()チャネルが閉じられた場合、終了時の処理を行い、ゴールーチンは終了する
  • ctx.Done()チャネルが閉じられていない場合、デフォルトのケースで 何らかの処理を行う。

メインゴルーチンが cancel関数を使用してコンテキストをキャンセルすることにより、ゴールーチン内の処理を制御。
これによりキャンセルが発生した場合に正しく終了するような処理が設計ができる

コンテキストを使用することで、ゴールーチンのキャンセルやタイムアウト、エラーハンドリングなど、非同期処理を安全に制御できるようになる
https://github.com/kushidam/go-context-cancel

kushidamkushidam

ジェネリクス

ジェネリクスとは

型に依存しない汎用的なメソッドを作れる。
特定の方に対するメソッドをジェネリクスでひとつに集約できる。

// Intに依存したメソッド
func PrintInts(s []int) {
    for _, v := range s { fmt.Print(v) }
}
// Stringに依存したメソッド
func PrintStrings(s []string) {
    for _, v := range s { fmt.Print(v) }
}

// 型に依存しないメソッド
func Print[T any](s []T) {
    for _, v := range s { fmt.Print(v) }
}

https://go.dev/doc/tutorial/generics