Go学習記録

メソッドのレシーバをポインタにするか値にするか
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

for range
文で使用できる型【5種類】
【Go1.21】配列(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)
}

internal
パッケージ
Goパッケージは、基本的には外部のモジュールに公開される。
パッケージ内のメソッドなどを公開するかは、先頭文字が大文字or小文字で決まるが、パッケージにはその機能がない。
そこで役立つのがinternal
パッケージ
internal
パッケージの親パッケージ、その子パッケージ以下からしか参照ができず、 外部公開されない 。
文字通り internal(内部) パッケージとなる。
パッケージA
├── パッケージB
│ ├── パッケージD
│ └── internal
│ └── パッケージE // AやCからは参照できない
├── パッケージC
└── go.mod
internal
パッケージは、プロジェクト内でのコードの構造化と整理を行うことができ、プライバシーやモジュールのバージョン管理に役立つ。
一方で、適切に使用することが重要で、不必要に多くのコードをinternalに置くことは避ける。
外部のユーザーに提供する必要がないコードだけをinternal
パッケージに配置することが一般的。

go test
-
_test.go
と接尾語が付いたファイルを対象にテストを実行する。 -
テストのときのみビルドの対象
-
テストコードをテスト対象のパッケージと異なるパッケージに配備することが可能。
-
テスト対象のパッケージとテストコードのパッケージが同じである必要はない。
-
通常、テスト対象のパッケージ名の末尾に
_test
を追加して命名される。 -
テストはエクスポートされた関数しか実行できない。(大文字始まりのメソッド)
非公開関数までテストを行うと過剰なテストであることが多い。 -
各テスト関数はデフォルトで個別のゴルーチンで実行される(テストが同時に実行)
(*testing.T).Parallel
メソッドは、テスト関数内でさらに並行性を制御したい場合に使用する
func TestConcurrentStuff(t *testing.T) {
// テスト関数内での同時実行数を制限
t.Parallel()
// テストのロジック
}

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
}

PANIC
package main
func main() {
// 1.
defer func() {recover()}()
// 2.
ch := make(chan struct{})
go func() {
// 4.
panic("PANIC")
close(ch)
}()
// 3.
<-ch
}
-
main 関数内で defer ステートメントを使用して匿名関数を登録
この匿名関数は recover() を呼び出すために使用するが、recover() の戻り値が無視される。 -
ch という型が struct{} のチャネルを作成。
-
チャネル ch から値を受信しようとするが、この受信はゴルーチン内で発生する前に実行される。
-
バックグラウンドで新しいゴルーチンが作成され、panic("PANIC") というパニックを引き起こす。
-
パニックがゴルーチン内で処理されていないため、プログラム全体がパニックとなる。
プログラムがパニックすると、実行が停止し、パニックの詳細が表示されることがあります。また、recover() 関数が main 関数内の defer ステートメントで呼び出されていますが、その戻り値が無視されているため、パニックの回復は行われません。
このコードを実行すると、ランタイムエラーとしてプログラムが終了し、"PANIC" メッセージが表示される。

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
}

uintptr
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

モジュール管理におけるMVS(Minimal Version Selection)
依存するモジュールのバージョンを選択する際、最小バージョンを優先的に選択する。
- 現在のビルド リストを作成
- すべてのモジュールを最新バージョンにアップグレード
- 1 つのモジュールを特定の新しいバージョンにアップグレード
- 1 つのモジュールを特定の古いバージョンにダウングレード
*ビルドリスト:特定のビルドで使用するモジュールとバージョン(依存関係)
Go 1.11から導入

struct{}
型と [0]int
型の共通点
struct{}
:フィールドを持たない構造体
[0]int
:要素数0の配列
共通点
- データを保持するためのメモリ領域を確保する必要がない

コンテキストを用いてゴールーチンのキャンセル処理
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
関数を使用してコンテキストをキャンセルすることにより、ゴールーチン内の処理を制御。
これによりキャンセルが発生した場合に正しく終了するような処理が設計ができる
コンテキストを使用することで、ゴールーチンのキャンセルやタイムアウト、エラーハンドリングなど、非同期処理を安全に制御できるようになる

ジェネリクス
ジェネリクスとは
型に依存しない汎用的なメソッドを作れる。
特定の方に対するメソッドをジェネリクスでひとつに集約できる。
// 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) }
}

Go モジュールの編成
原則ということではなく、あくまでも参考