Open22

Go言語で戦えるようになる!【つよつよエンジニアへの道】

ピン留めされたアイテム
KoderKoder

業務でGoを触ることになりそうだから、Goを泣きながら勉強するのだ

有識者の皆様、理解が間違ってたり、怪しい方向に進んでたら 温かい目でコメントにて袋叩きにしてください

KoderKoder

とりあえずこの記事を読んだ!めっちゃ分かりやすい!感謝🙏

KoderKoder

ハンズオンでこれを実装していく! 前見た気がするけど、手を動かさないと忘れる
https://youtu.be/lNd7XlXwlho?si=GEFUTVbU5_mKTNRg

KoderKoder

この動画はFiberを使ってるみたいなんだけど、どうなんだろうか? 他にもGinとかEchoとかあるけど違いとメジャー度合いがわからん

KoderKoder

なんで第一引数cでContextが入るかが謎 that's convention(慣習)言うてたけど分からん あとで調べよ

app.Get("/", func(c *fiber.Ctx) error {
		return c.Status(200).JSON(fiber.Map{
			"message": "Hello from Fiber!"})
	})```
KoderKoder

ありがとうチャッピー君


💬「[]Todo{}」ってのはな!

それ、空のスライス構文だッ!


🧙‍♀️解説すると:

  • []Todo{} は「Todo型のスライスを、空で初期化」してるって意味だ!
  • Todo は構造体とか何かしらの型だな!
  • [] は「スライス」、{} は「中身なし」だ!

🧪たとえば:

type Todo struct {
  Title string
  Done  bool
}

todos := []Todo{}

↑こんな感じで、「空っぽだけど、Todoの入れ物用意しといたぜ!」ってわけだ!


要するに!

🌀 「Todo型の空のリスト、爆誕!」
それが []Todo{} だッ!


他にも聞きたいことがあれば、ガンガン聞いてくれよなッ!

KoderKoder

うおー、なぜtodo周りをpointerにしたんだブロー 分からないぞ

app.Post("/api/todos", func(c *fiber.Ctx) error {
		todo := &Todo{}

		if err := c.BodyParser((todo)); err != nil {
			return err
		}

		if todo.Body == "" {
			return c.Status(400).JSON(fiber.Map{
				"error": "Todo Body is required",
			})
		}

		todo.ID = len(todos) + 1
		todos = append(todos, *todo)

		return c.Status(201).JSON(todo)
	})
KoderKoder

ありがとう、チャッピー


💥 todo := &Todo{} ってなんでポインタかって?それはな…

🧙‍♀️ **「中身を書き換えるため」**だッ!!


🎯理由その1:c.BodyParser((todo))

  • BodyParser() は、JSONの中身を構造体に詰める処理だ。
  • でもな、この関数は ポインタじゃないと中身に詰められねぇ
  • ポインタじゃなきゃ「はい中身渡しました、でも書き換えられませんバイバイ!」ってなるぜ!

🗣️→ つまり:

🌀**「JSONのデータ、構造体に入れたいならポインタにしとけ!」**


🎯理由その2:無駄なコピーを避ける!

  • &Todo{} にしとくと、その構造体をメモリ上で1個作るだけで済む。
  • 毎回コピーしてたら、ムダが多いぜ!

🎯でも最後に:

todos = append(todos, *todo)

ここで *todo にしてるのは、ポインタの中身を取り出してスライスに入れてるってことだ!

🗣️→

🌀**「スライスは値で持ちたい!でもパースの時はポインタがいる!」**
…っていう両立のためのテクニックなんだな!


🎤まとめ!

  • &Todo{} → 構造体のポインタ作成!
  • BodyParser() → ポインタ必須!
  • *todo → 値としてスライスに入れる!

⚡つまりこのコードはこう言ってるッ!

「ポインタで中身を受け取って、値で保存ッ!
そのへん臨機応変にいくのがGoの流儀だぜッ!!」


魔理沙、以上ッ!!質問あればどんと来いやーッ!🔥

KoderKoder

ありがとう魔理沙


💥 BodyParser() がポインタじゃないと詰められない理由?
それはな……Goの引数の値渡しの仕組みが関係してるッ!


🧠 Goの基本:値渡し vs ポインタ渡し

Goは基本的に、関数に値を渡すときはコピーされる!

func modify(val int) {
	val = 10
}

↑こんな関数、呼び出しても元の変数には何も起こらない。なぜか?
🧪 コピーされた「偽物」に書き込んでるだけだから!!


📦 BodyParser() の動き

Fiberの c.BodyParser() の中身、実はこんなことしてる:

func (c *Ctx) BodyParser(out interface{}) error {
	return json.Unmarshal(c.Body(), out)
}

つまり、json.Unmarshal してんだ!


🚨 json.Unmarshal も、ポインタじゃないと中身書けねぇ!

なぜなら:

json.Unmarshal([]byte(`{"Name":"Marisa"}`), &person)

はOKだけど、

json.Unmarshal([]byte(`{"Name":"Marisa"}`), person)

これは❌エラー!


💥理由はズバリ!

🔥 Unmarshal は、構造体の中身を直接書き換えたいからだッ!!

でも Go では、関数に値(=コピー)を渡すと元の構造体は書き換えられない

だからこそ!

🔧 **「本物に書き込むには、ポインタで場所を教える必要がある」**ってワケだ!


🧪 たとえば魔理沙風に言うと:

値渡し

「このおまんじゅう、写真だけあげるぜ!」📸
→ 食べようとしても味がしない!!

ポインタ渡し

「このおまんじゅう、家の冷蔵庫の3段目にあるぜ!」📍
→ 本物があるからガブッと食べられる!!


✅ だから!

todo := &Todo{} にして
c.BodyParser(todo) にすることで:

🌀「この構造体に中身を書き込んでくれ!」って
住所をFiberに教えてるってことだな!!


これがポインタじゃなきゃダメな理由だッ!!

理解の弾幕、張れたか!?まだ聞きてぇことがあれば、どんと来いやーッ!🌪️

KoderKoder

fmt.Sprint は、Goの標準ライブラリ fmt パッケージに含まれている関数で、任意の値を文字列(string)に変換するために使います。


🧪 基本構文

s := fmt.Sprint(value)
  • valueint でも float64 でも struct でもOK。
  • s には、その値が文字列化されたものが入ります。

✅ 使用例

id := 42
str := fmt.Sprint(id)
fmt.Println(str)  // 出力: "42"
  • int 型の id を、 "42" という 文字列 に変換しています。

🤔 なんで使うの?

Goでは型が厳密なので、次のような比較はできません:

todo.ID == "3" // エラー!intとstringは比較できない

なので、どちらかを型変換する必要があります。
今回のコードではこのようにして回避していました:

if fmt.Sprint(todo.ID) == id
  • todo.ID はおそらく int
  • idc.Params("id") で得られた string
  • 比較のため、todo.ID を文字列に変換。

🔄 ちなみに逆変換したいなら?

文字列 → 数値には strconv.Atoi()strconv.ParseInt() を使います。

idStr := "42"
idInt, _ := strconv.Atoi(idStr)

📌 まとめ

関数名 説明
fmt.Sprint(v) 任意の値 v を文字列化する
用途 型が異なる値を比較したい時などに便利
fmt.Sprint(123)"123"(string型)

もっと詳しく見たい場合は fmt.Sprintf, fmt.Sprintln との違いも解説できますよ!

KoderKoder

なんだこれ?

	todos = append(todos[:i], todos[i+1:]...)
KoderKoder

それは Go における「スライスから要素を削除する」ための定番の書き方です!


🧠 構文の意味

todos = append(todos[:i], todos[i+1:]...)

これは、

todos というスライスから、インデックス i の要素を削除する

という処理です。


🔍 分解して解説

1. todos[:i]

  • スライスの先頭から、インデックス i の直前までを取り出します。
  • i=2 の場合 → todos[0], todos[1]

2. todos[i+1:]

  • インデックス i次の要素から最後までを取り出します。
  • i=2 の場合 → todos[3], todos[4], ...

3. append(..., ...)

  • 上記2つのスライスを 連結 しています。
  • つまり、i 番目の要素(todos[i])だけが含まれなくなる。

4. ...

  • ...スプレッド構文のようなもの。
  • append は引数として複数の要素を受け取るので、スライスを「展開」して渡しています。

✅ 例で見るとわかりやすい

todos := []string{"A", "B", "C", "D", "E"}
i := 2  // "C" を削除したい

todos = append(todos[:i], todos[i+1:]...)
// 結果: ["A", "B", "D", "E"]

📌 注意点

  • 元のスライスの順序は保たれます
  • append によって新しいスライスが作られるので、元のメモリ領域とは異なる可能性があります(GC的にも)

📝 まとめ

部分 意味
todos[:i] i より前の要素
todos[i+1:] i より後ろの要素
append(..., ...) 2つのスライスを結合
... スライスを「展開」して渡す

Goでは専用の「削除関数」は無いため、この書き方が最も一般的です!

他にも「条件に合う要素を全部削除」など応用もできますので、気になったらそちらも説明します!

KoderKoder

Goでは専用の「削除関数」は無いため、この書き方が最も一般的です!

え、ないんだ

KoderKoder

3連休全然勉強してない!やばい!笑