Closed29

A Tour of Go やってみた Part4

kun432kun432

1. Methods

Goにはクラスはないが、型に関数を定義できる。これを「メソッド」と呼ぶ。例えば以下。

type Vertex struct {
    X, Y float64
}

func (v Vertex) Abs() float64 {
    return math.Sqrt(v.X*v.X + v.Y*v.Y)
}

上記の(v Vertex)の部分が「レシーバ」になる。上記の例だと構造体VertexAbs()というメソッドを生やして、Abs()v を受け取って、処理結果を返すことができる。

1.go
package main

import (
	"fmt"
	"math"
)

type Vertex struct {
	X, Y float64
}

func (v Vertex) Abs() float64 {
	return math.Sqrt(v.X*v.X + v.Y*v.Y)
}

func main() {
	// メソッドを呼び出し
	v := Vertex{3, 4}
	fmt.Println(v.Abs())
}
出力
5

struct+メソッドでオブジェクトっぽい操作が可能になるということ。

kun432kun432

2. Methods are functions

メソッドは、レシーバーを引数にしただけの、ただの関数なので、普通の関数として書き換えることもできる。

2.go
package main

import (
	"fmt"
	"math"
)

type Vertex struct {
	X, Y float64
}

func Abs(v Vertex) float64 {
	return math.Sqrt(v.X*v.X + v.Y*v.Y)
}

func main() {
	v := Vertex{3, 4}
	fmt.Println(Abs(v))
}
出力
5
kun432kun432

3. Methods continued

メソッドはstructだけでなく、自分で定義した型ならどんな型にも定義できる。以下は自分で作成したfloat64の新しい型(エイリアス)に対して、abs()メソッドを定義している。

3.go
package main

import (
	"fmt"
	"math"
)

type Myfloat float64

func (f Myfloat) Abs() float64 {
	if f < 0 {
		return float64(-f)
	}
	return float64(f)
}

func main() {
	f := Myfloat(-math.Sqrt2)
	fmt.Println(f.Abs())
}
出力
1.4142135623730951

ただし、レシーバー型は同じパッケージで定義された型でなければならない、つまり、他パッケージの型、組み込みのintやstringなどに対してはメソッド追加できない。

kun432kun432

4. Pointer receivers

メソッドのレシーバーを*Tみたいなポインタ型にできる。これをポインタレシーバと呼ぶ。例えばこういう感じ。

type Vertex struct {
    X, Y float64
}

// Scale はポインタレシーバー *Vertex を受け取って、
// レシーバー先の値を直接変更する
func (v *Vertex) Scale(f float64) {
    v.X = v.X * f
    v.Y = v.Y * f
}

メソッドは、メソッド内で自分自身、つまりレシーバーのフィールドを書き換えたい場合のほうが多い。値レシーバだとコピーされてしまうため、メソッド内で変更しても変更されない。なので、ポインタレシーバのほうが使い方として一般的。

4.go
package main

import (
	"fmt"
	"math"
)

type Vertex struct {
	X, Y float64
}

func (v Vertex) Abs() float64 {
	return math.Sqrt(v.X*v.X + v.Y*v.Y)
}

func (v *Vertex) Scale(f float64) {
	v.X = v.X * f
	v.Y = v.Y * f
}

func main() {
	v := Vertex{3, 4}
	fmt.Println(v.Abs())
	v.Scale(10)
	fmt.Println(v.Abs())
}
出力
5
50

Scaleを値レシーバに変えて再度実行してみるとこうなる。

func (v Vertex) Scale(f float64) {
	v.X = v.X * f
	v.Y = v.Y * f
}
出力
5
5
kun432kun432

5. Pointers are functions

AbsScaleメソッドを関数として書き直す。

5.go
package main

import (
	"fmt"
	"math"
)

type Vertex struct {
	X, Y float64
}

func Abs(v Vertex) float64 {
	return math.Sqrt(v.X*v.X + v.Y*v.Y)
}

func Scale(v *Vertex, f float64) {
	v.X = v.X * f
	v.Y = v.Y * f
}

func main() {
	v := Vertex{3, 4}
	Scale(&v, 10)
	fmt.Println(Abs(v))
}
出力
5

Scale を値で受け取るように書き換える。

func Scale(v Vertex, f float64) {
	v.X = v.X * f
	v.Y = v.Y * f
}
出力
./5.go:23:8: cannot use &v (value of type *Vertex) as Vertex value in argument to Scale

それはそうだよね。呼び出し元も値を渡すように書き換える。

func main() {
	v := Vertex{3, 4}
	Scale(v, 10)  # 値で渡す
	fmt.Println(Abs(v))
}
出力
5

Scaleは何も返していないのでそれも当然。以下のように修正する。

func Scale(v Vertex, f float64) Vertex {
	v.X = v.X * f
	v.Y = v.Y * f
    return v
}

func main() {
	v := Vertex{3, 4}
	v2 := Scale(v, 10)
	fmt.Println(Abs(v2))
}
出力
50
kun432kun432

6. Methods and pointer indirection

1つ前で分かる通り、関数の呼び出しは引数の型に呼び出し方も合わせる必要があるが、メソッドがポインタレシーバである場合、呼び出すときのレシーバは変数でもポインタでもどちらでもいける。

6.go
package main

import "fmt"

type Vertex struct {
	X, Y float64
}

// ポインタレシーバを持つメソッド
func (v *Vertex) Scale(f float64) {
	v.X = v.X * f
	v.Y = v.Y * f
}

// ポインタ引数を持つ関数
func ScaleFunc(v *Vertex, f float64) {
	v.X = v.X * f
	v.Y = v.Y * f
}

func main() {
	v := Vertex{3, 4}
	v.Scale(2)  // 裏では `(&v).Scale(5) `が呼び出される
	fmt.Println(v)
	ScaleFunc(&v, 10)
	fmt.Println(v)

	p := &Vertex{4, 3}
	p.Scale(3)
	fmt.Println(p)
	ScaleFunc(p, 8)
	fmt.Println(p)
}
出力
{6 8}
{60 80}
&{12 9}
&{96 72}
kun432kun432

7. Methods and pointer indirection (2)

同様に、メソッドが値レシーバである場合でも、呼び出すときのレシーバは変数でもポインタでもどちらでもいける。

7.go
package main

import (
	"fmt"
	"math"
)

type Vertex struct {
	X, Y float64
}

// 値レシーバを持つメソッド
func (v Vertex) Abs() float64 {
	return math.Sqrt(v.X*v.X + v.Y*v.Y)
}

// 値引数を持つ関数
func AbsFunc(v Vertex) float64 {
	return math.Sqrt(v.X*v.X + v.Y*v.Y)
}

func main() {
	v := Vertex{3, 4}
	fmt.Println(v.Abs())
	fmt.Println(AbsFunc(v))

	p := &Vertex{4, 3}
	fmt.Println(p.Abs()) // (*p).Abs() として解釈される
	fmt.Println(AbsFunc(*p))
}
出力
5
5
5
5

つまりメソッドなら、値レシーバの場合でもポイントレシーバの場合でも、呼び出し側はあまり気にしなくて良い。コピーを変えるか、本体を変えるかだけを考えてメソッド定義すれば良い。

kun432kun432

8. Choosing a value or pointer receiver

値レシーバを使うべきか、ポインタレシーバを使うべきか?ポインタレシーバを使う理由は以下の2つ。

  1. レシーバー先を直接変更したい場合
    • 例: オブジェクト内部のフィールドを更新したい場合など
  2. コピーコストを避けたい場合
    • 例: 大きな struct(たとえば数百バイト以上)の場合
      • メソッドを呼ぶたびに「コピー」が走ると重い
      • ポインタで渡すほうが効率が良い
8,go
package main

import (
	"fmt"
	"math"
)

type Vertex struct {
	X, Y float64
}

func (v *Vertex) Scale(f float64) {
	v.X = v.X * f
	v.Y = v.Y * f
}

func (v *Vertex) Abs() float64 {
	return math.Sqrt(v.X*v.X + v.Y*v.Y)
}

func main() {
	v := Vertex{3, 4}
	fmt.Printf("Scale実行前: %+v, Abs: %v\n", v, v.Abs())
	v.Scale(5)
	fmt.Printf("Scale実行後: %+v, Abs: %v\n", v, v.Abs())
}
出力
Scale実行前: {X:3 Y:4}, Abs: 5
Scale実行後: {X:15 Y:20}, Abs: 25

どちらを使うか?のベストプラクティスとしては以下らしい

  • 変更が必要か?大きさが大きいか?で判断
    • 変更が必要: ポインタレシーバを選択
    • 読み取りのみ & 型が小さい: 値レシーバでもOK、ただし他と混ぜないほうがベター
  • 一貫性を重視
    • 同じ型のメソッドは「全部ポインタ」「全部値」のどちらかに統一するのがベター
  • 可読性を重視
    • 一貫性があるとコードを読む際に、型のメソッドを理解しやすい
kun432kun432

9. Interfaces

interface型はメソッドのシグネチャ(名前+引数+戻り値)の集合を定義する。

・・・の前に、インタフェースとはなんぞや?ChatGPTによると以下。

プログラミングにおける「インタフェース(interface)」とは、大まかに言うと「機能や振る舞いの約束事」を定めたものです。クラスや構造体がどんなメソッド(操作)を持つべきかを宣言し、それを実装する側(具象クラスや具象型)にその実装を任せる仕組みです。以下、ポイントを押さえて説明します。

  1. 「何ができるか」を示す契約書

    • インタフェースは「こういう操作ができるよ」というメソッドのシグネチャ(名前・引数・戻り値)を並べたリストです。
    • 具体的にどう動くか(中身)は書かず、あくまで「~できる」という約束だけを定めます。
  2. 実装は別の場所で行う

    • インタフェースを実装するクラス/型は、その約束に則って中身を定義します。
    • たとえば「Drawable」というインタフェースなら draw() メソッドを持つことだけを決め、実装クラスでは「円を描画する」「四角を描画する」といった具体的な描画処理を書くイメージです。
  3. 利点:依存関係の切り離し(疎結合)

    • 呼び出し側は「Drawableなら draw() が呼べる」だけを知っていればよく、内部の実装を気にしません。
    • これにより、異なる実装を簡単に入れ替えられ、テストや拡張がしやすくなります。
      プログラミングにおける「インタフェース(interface)」とは、大まかに言うと「機能や振る舞いの約束事」を定めたものです。クラスや構造体がどんなメソッド(操作)を持つべきかを宣言し、それを実装する側(具象クラスや具象型)にその実装を任せる仕組みです。以下、ポイントを押さえて説明します。
  4. 言語ごとの書き方の例

    • Java
      // インタフェースの定義
      public interface Shape {
          double area();
      }
      // 実装クラス
      public class Circle implements Shape {
          private double radius;
          public Circle(double r) { radius = r; }
          public double area() { return Math.PI * radius * radius; }
      }
      
    • Go
      // インタフェースの定義
      type Reader interface {
          Read(p []byte) (n int, err error)
      }
      // 標準パッケージの os.File などが自動的にこの Reader を実装する
      
  5. 身近なアナロジー:電気のコンセント

    • コンセント(インタフェース)は「プラグを差し込む」という動作だけを提供します。
    • テレビも、掃除機も同じ形のプラグを使いますが、中で電気をどう使うかは機器ごとに異なります。
    • プログラミングの世界では、コンセントに相当するのがインタフェース、電気機器が実装クラスにあたります。

まとめると、インタフェースは「これだけの機能を提供しますよ」という仕様書のようなもの。実際の動作は後からそれに従って自由に書くことで、コードの再利用性や保守性が高まります。

type Abser interface {
    Abs() float64
}

Abser型にはAbs() float64というメソッドを持っているならどんな型でもいれることができる、というインタフェースになる。

こういうコードがあるとして。

package main

import (
    "fmt"
    "math"
)

type Vertex struct {
    X, Y float64
}

func (v *Vertex) Abs() float64 {
    return math.Sqrt(v.X*v.X + v.Y*v.Y)
}

func main() {
    v := Vertex{3, 4}
    fmt.Println(v)
    fmt.Println(v.Abs())
}

そのままやれば普通に動く。

出力
{3 4}
5

ここにインタフェースを追加

package main

import (
    "fmt"
    "math"
)

type Vertex struct {
    X, Y float64
}

// インターフェース
type Abser interface {
    Abs() float64
}

// Pointer レシーバーに Abs 定義
func (v *Vertex) Abs() float64 {
    return math.Sqrt(v.X*v.X + v.Y*v.Y)
}

func main() {
    var a Abser
    v := Vertex{3, 4}
    a = v          //  コンパイルエラー
    fmt.Println(a)
}
出力
./9.go:24:9: cannot use v (variable of struct type Vertex) as Abser value in assignment: Vertex does not implement Abser (method Abs has pointer receiver)
  • v := Vertex{3, 4}は値型のVertex
  • インタフェースに登録されているのはポインタ型の*Vertexのポインタレシーバを持つAbs()メソッド
  • aにはインタフェースで制約がかかっているので、代入できない

ということらしい、ここ難しい・・・ちょっと現時点ではちゃんと理解ができていないかもだけど、とりあえず進める。

なので、以下のようにすれば良い。

func main() {
    var a Abser
    v := Vertex{3, 4}
    a = &v          //  ポインタで渡すことで、`*Vertex`は`Abs()`を持つため、エラーにならない
    fmt.Println(a)
}
出力
&{3 4}

A Tour of Goのサンプルコードも。これもエラーになる。

9-1.go
package main

import (
	"fmt"
	"math"
)

type Abser interface {
	Abs() float64
}

func main() {
	var a Abser
	f := MyFloat(-math.Sqrt2)
	v := Vertex{3, 4}

	a = f  // MyFloat は Abser を実装している
	a = &v // *Vertex は Abser を実装している

	// 以下の場合は、v は Vertex であり、 *Vertexではないため、Abser を実装していない
	a = v

	fmt.Println(a.Abs())
}

type MyFloat float64

func (f MyFloat) Abs() float64 {
	if f < 0 {
		return float64(-f)
	}
	return float64(f)
}

type Vertex struct {
	X, Y float64
}

func (v *Vertex) Abs() float64 {
	return math.Sqrt(v.X*v.X + v.Y*v.Y)
}
kun432kun432

10. Interfaces are implemented implicitly

Java、C#、TypeScriptなどでインタフェースを定義した場合、クラスへの実装にはimplementsが必要になるらしい。

// インターフェース定義
public interface Abser {
    double abs();
}

// 実装するときは `implements` が必須!
public class Vertex implements Abser {
    double x, y;
    public Vertex(double x, double y) { this.x = x; this.y = y; }
    @Override
    public double abs() {
        return Math.sqrt(x*x + y*y);
    }
}
// インターフェース定義
public interface IAbser {
    double Abs();
}

// 実装クラスではコロンで宣言!
public class Vertex : IAbser {
    public double X, Y;
    public double Abs() {
        return Math.Sqrt(X*X + Y*Y);
    }
}
// インターフェース定義
interface Abser {
  abs(): number;
}

// 実装クラスでは `implements` を使うよ
class Vertex implements Abser {
  constructor(public x: number, public y: number) {}
  abs(): number {
    return Math.hypot(this.x, this.y);
  }
}

goの場合、これは暗黙的に行われる、つまり implementsを宣言する必要はない

10.go
package main

import "fmt"

type I interface {
	M()
}

type T struct {
	S string
}

// このメソッドは、T が I を実装していることを意味するが、
// 明示的に宣言する必要はない。
func (t T) M() {
	fmt.Println(t.S)
}

func main() {
	var i I = T{"こんにちは!"}
	i.M()
}
出力
こんにちは!

インタフェースが求めるメソッドをその型が持っていれば、実装していると判断される。これにより、インタフェースの定義は実装と分離する。つまり、パッケージが別であっても良いということになる。
逆に言うと、実装が突然出てくることもある。

kun432kun432

11. Interface values

インタフェース型の変数は、値と型のタプルとなる。

11.go
package main

import (
	"fmt"
	"math"
)

type I interface {
	M()
}

type T struct {
	S string
}

// T にポインタレシーバーで M() を定義
func (t *T) M() {
	fmt.Println(t.S)
}

type F float64

// F に値レシーバーで M() を定義
func (f F) M() {
	fmt.Println(f)
}

func main() {
	var i I

	i = &T{"こんにちは!"}
	describe(i)
	i.M()

	i = F(math.Pi)
	describe(i)
	i.M()
}

func describe(i I) {
	
	fmt.Printf("(%v, %T)\n", i, i)
}
出力
(&{こんにちは!}, *main.T)
こんにちは!
(3.141592653589793, main.F)
3.141592653589793

インタフェースの値のメソッドを呼ぶと、この型を見て、その型に実装されているメソッドを呼ぶ。

package main

import (
    "fmt"
    "math"
)

// インターフェースでメソッドのシグネチャを定義
type Abser interface {
    Abs() float64
}

// Vertex にポインタレシーバーでメソッドを定義
type Vertex struct{ X, Y float64 }
func (v *Vertex) Abs() float64 {
    return math.Sqrt(v.X*v.X + v.Y*v.Y)
}

// MyFloat に値レシーバーでメソッドを定義
type MyFloat float64
func (f MyFloat) Abs() float64 {
    if f < 0 {
        return float64(-f)
    }
    return float64(f)
}

func main() {
    var a Abser

    a = &Vertex{3, 4}
    // a の中身は ( &Vertex{3,4}, *Vertex )
    fmt.Println(a.Abs()) // → Vertex の Abs が呼ばれる

    a = MyFloat(-2.5)
    // a の中身は ( MyFloat(-2.5), MyFloat )
    fmt.Println(a.Abs()) // → MyFloat の Abs が呼ばれる
}
出力
5
2.5
kun432kun432

12. Interface values with nil underlying values

Go では、インターフェース変数に nil が入っていても、そのままメソッドを呼び出すと、レシーバーが nil の状態でメソッドが実行される(他の言語ではnullポインタ例外が起きるようになっているものが多い。)よって、メソッド側でこれをチェックする実装にするのが一般的。

12.go
package main

import "fmt"

type I interface {
	M()
}

type T struct {
	S string
}

func (t *T) M() {
	// t が nil かどうかをチェック
	if t == nil {
		fmt.Println("<nil>")
		return
	}
	fmt.Println(t.S)
}

func main() {
	var i I
	var t *T
	i = t
	i.M()

	i = &T{"hello"}
	i.M()
}

func describe(i I) {
	fmt.Printf("(%v, %T)\n", i, i)
}
出力
<nil>
hello

ただし、インターフェース変数自体は非 nilであり、これは値が nil であっても、型がセットされているため。

kun432kun432

13. Nil interface values

インタフェース値がnilというのは、値も型もnilな状態を指す
値も型もないと、その型のどのメソッドを呼べばいいのかわからないため、エラーになる。

13.go
package main

import "fmt"

type I interface {
	M()
}

func main() {
	var i I
	describe(i)
	i.M()
}

func describe(i I) {
	fmt.Printf("(%v, %T)\n", i, i)
}
出力
(<nil>, <nil>)
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x2 addr=0x0 pc=0x1042b5cf8]

goroutine 1 [running]:
main.main()
        /Users/kun432/work/a-tour-of-go/2_methods_and_interfaces/13.go:12 +0x28
exit status 2
kun432kun432

14. The empty interface

空のインタフェースを定義することができる

var i interface{}

空のインタフェースはどんな型でもいれることができる、というのは全ての型は「少なくとも0個のメソッドを実装している」と言えるため。

14.go
package main

import "fmt"

func main() {
	var i interface{}
	describe(i)

	i = 42
	describe(i)

	i = "hello"
	describe(i)
}

func describe(i interface{}) {
	fmt.Printf("(%v, %T)\n", i, i)
}
出力
(<nil>, <nil>)
(42, int)
(hello, string)

以下のようなユースケースで使用する

  1. 未知の型を受け取りたい場合
package main

import "fmt"

func describe(i interface{}) {
    fmt.Printf("value=%v, type=%T\n", i, i)
}

func main() {
    describe(42)
    describe("Hello")
    describe([]int{1, 2, 3})
}
出力
value=42, type=int
value=Hello, type=string
value=[1 2 3], type=[]int
  1. 汎用的なデータ構造
package main

import (
    "fmt"
)

func main() {
    // いろんな型の値を入れられるマップ
    data := map[string]interface{}{
        "name":   "Alice",            // string
        "age":    30,                 // int
        "active": true,               // bool
        "scores": []int{85, 90, 78},  // []int
    }

    for k, v := range data {
        fmt.Printf("%s: %v (type: %T)\n", k, v, v)
    }
}
出力
name: Alice (type: string)
age: 30 (type: int)
active: true (type: bool)
scores: [85 90 78] (type: []int)
kun432kun432

この辺から自分の理解力を超えてきている感があるなぁ・・・

kun432kun432

15. Type assersions

interface{} 型を使うとなんでもいれることができる。ここから中味=具体的な型を取り出すのが型アサーション。

package main

import "fmt"

func main() {
	var i interface{} = "hello"
	s := i.(string)  // 型アサーション
	fmt.Println(s)	
}

iinterface{} 型の変数として宣言しつつ、string型の値helloを初期値として宣言。型アサーションでistring型として値を取り出しsに代入している。

実行するとこうなる

出力
hello

ただし以下のように異なる型で取り出そうとするとPanicになる

	var i interface{} = "hello"
	s := i.(float64)
出力
panic: interface conversion: interface {} is string, not float64

この場合は返り値を2つ受け取るようにする。istring型の値を保持していれば、2つ目の返り値にtrueが返され、1つ目の返り値に値が返る。

	var i interface{} = "hello"
	s, ok := i.(string)
	fmt.Println(s, ok)	
出力
hello true

指定した型の値を持たない場合はfalseが返り、値はゼロ値になる。

	var i interface{} = "hello"
	s, ok := i.(float64)
	fmt.Println(s, ok)
出力
0 false

mapと使い方が似ている

https://zenn.dev/link/comments/aac4e6cf0febe5

kun432kun432

16. Type switches

「型switch」を使えば、interface{}型に入っている値の「型」に応じた分岐処理が書ける。つまり、caseに型を指定できる。型アサーションと同じ書き方をするけども、特別なキーワードtypeが使用して、switch v := i.(type)というような書き方になる。

16.go
package main

import "fmt"

func do(i interface{}) {
	switch v := i.(type) {
	case int:
		fmt.Printf("%v かける 2 は %v です。\n", v, v*2)
	case string:
		fmt.Printf("%q は %v バイトです。\n", v, len(v))
	default:
		fmt.Printf("型 %T は知りません。\n", v)
	}
}

func main() {
	do(21)
	do("こんにちは")
	do(true)
}
出力
21 かける 2 は 42 です。
"こんにちは" は 15 バイトです。
型 bool は知りません。
kun432kun432

17. Stringers

fmtパッケージには特別なインタフェースStringerがある。

type Stringer interface {
    String() string
}

これによりString()メソッドを持ってる型は、自動的にStringerインタフェースを満たす型かどうか=自分自身を文字列で出力できるか、をチェックすることになる。

17.go
package main

import "fmt"

type Person struct {
	Name string
	Age int
}

// String メソッドを実装することで、Stringer インターフェースを満たす
func (p Person) String() string {
	return fmt.Sprintf("%v (%v 歳)", p.Name, p.Age)
}

func main() {
	a := Person{"山田太郎", 30}
	b := Person{"鈴木花子", 20}
	fmt.Println(a)  // 自動的に String メソッドが呼び出される
	fmt.Println(b)
}
出力
山田太郎 (30 歳)
鈴木花子 (20 歳)
kun432kun432

19. Errors

Goではエラーが起きたかどうかを特別なerror型の値で表現する。

error型はインタフェースとして以下のように定義されている。fmt.Stringerと似ている。

type error interface {
    Error() string
}

つまり、Error()メソッドを持っていればエラーとして使えることになる。Goの関数はerrorを返すようになっているものが多いみたい。

package main

import (
	"fmt"
	"strconv"
)

func main() {
	i, err := strconv.Atoi("42")
	if err != nil {
		fmt.Println("エラー:", err)
		return
	}
	fmt.Println("変換後の値:", i)
}
出力
変換後の値: 42

strconv.Atoi("abc")の場合はこうなる

出力
エラー: strconv.Atoi: parsing "abc": invalid syntax

Goでは例外を使わずにこういう感じでエラーを返して確認するスタイルみたい。

19.go
package main

import (
	"fmt"
	"time"
)

// カスタムなエラー型を定義
type MyError struct {
	When time.Time
	What string
}

// Error メソッドを実装することで、error インターフェースを満たす
func (e *MyError) Error() string {
	return fmt.Sprintf("%v: %s", e.When, e.What)
}

// 意図的にエラーを返す関数
func run() error {
	return &MyError{
		time.Now(),
		"エラーが発生しました",
	}
}

func main() {
	if err := run(); err != nil {
		fmt.Println(err)
	}
}
出力
2025-07-05 17:16:01.166233 +0900 JST m=+0.000128834: エラーが発生しました
kun432kun432

20. Exercise: Errors

スキップしたけど、

注意: Error メソッドの中で、 fmt.Sprint(e) を呼び出すことは、無限ループのプログラムになることでしょう。 最初に fmt.Sprint(float64(e)) として e を変換しておくことで、これを避けることができます。 なぜでしょうか?

のところ。以下のように書くと無限ループする。

// カスタムエラー型の定義
type ErrNegativeSqrt float64

// errorインタフェースの実装
func (e ErrNegativeSqrt) Error() string {
	return fmt.Sprintf("cannot Sqrt negative number: %v", e)
}

fmt.Sprint(e)とした場合、

  • eError()メソッドを持っているので、errorとして扱い、e.Error()を呼び出す
  • Error()の中でfmt.Sprint(e)を呼び出す、つまり Error()を呼んで無限ループ

となる。

float64にキャストすることで、fmtErrorではなくStringerとして扱うので、無限ループせずに出力してくれるようになる。

kun432kun432

21. Readers

Goには、「ファイル」「ネットワーク」「文字列」「暗号化されたストリーム」など様々なデータの読み出しを共通化するために、io.Readersインタフェースが定義されており、Read()メソッドを持つ

type Reader interface {
    Read(p []byte) (n int, err error)
}
func (T) Read(b []byte) (n int, err error)
  • b []byte: データを読み込むためのバッファ
  • n int: 実際に読み込んだバイト数
  • err error: 最後まで読見終わるとio.EOFが返る

文字列を、一定のバッファサイズ分、順に読み込んでいくサンプル。日本語UTF-8だとバイト単位の区切りを少し変えないといけない。

21.go
package main

import (
	"fmt"
	"io"
	"strings"
)

func main() {
	r := strings.NewReader("こんにちは!今日は良いお天気ですね!")

	// 9バイトずつ読み込むバッファ
	b := make([]byte, 9)
	for {
		// データをバッファに読み込んで格納
		n, err := r.Read(b)
		// 読み込んだデータの長さ、エラー、バッファの内容を表示
		fmt.Printf("n = %v err = %v b = %v\n", n, err, b)
		// 読み込んだデータの内容を表示
		fmt.Printf("b[:n] = %q\n", b[:n])
		// ファイルの末尾に達した場合、ループを終了
		if err == io.EOF {
			break
		}
	}
}

余談。上はたまたまうまくいったけど、日本語の場合はちゃんとやるなら文字単位にしたほうが良さそう。その場合は bufio.ReaderReadRune()を使えばいけるらしい。

package main

import (
	"fmt"
	"bufio"
	"strings"
)

func main() {
	r := strings.NewReader("Hello!😊今日は良いお天気ですね!☀️")
	s := bufio.NewReader(r)

	for {
		char, size, err := s.ReadRune()
		if err != nil {
			break
		}
		fmt.Printf("%q (%d バイト)\n", char, size)
	}
}
出力
'H' (1 バイト)
'e' (1 バイト)
'l' (1 バイト)
'l' (1 バイト)
'o' (1 バイト)
'!' (1 バイト)
'😊' (4 バイト)
'今' (3 バイト)
'日' (3 バイト)
'は' (3 バイト)
'良' (3 バイト)
'い' (3 バイト)
'お' (3 バイト)
'天' (3 バイト)
'気' (3 バイト)
'で' (3 バイト)
'す' (3 バイト)
'ね' (3 バイト)
'!' (3 バイト)
'☀' (3 バイト)
'️' (3 バイト)
kun432kun432

24. Images

Imageパッケージは以下のimageインタフェースを定義している

package image

type Image interface {
    ColorModel() color.Model
    Bounds() Rectangle
    At(x, y int) color.Color
}

定義されているメソッド

  • ColorModel(): 色モデル(RGB or グレースケール)を返す
  • Bounds(): 画像のサイズ(image.Rectangle 型、つまり長方形)を返す
  • At(x, y int): 指定したピクセルの色を返す
24.go
package main

import (
    "fmt"
    "image"
)

func main() {
    m := image.NewRGBA(image.Rect(0, 0, 100, 100))
    fmt.Println(m.Bounds())
    fmt.Println(m.At(0, 0).RGBA())
}
(0,0)-(100,100)
0 0 0 0

color.Color と color.Model は共にインタフェースですが、定義済みの color.RGBA と color.RGBAModel を使うことで、このインタフェースを無視できます。 これらのインタフェースは、image/color パッケージで定義されています。

ここはちょっとよくわからないな。一応、color.Colorcolor.Modelは以下のようなインタフェースになっているらしい。

type Color interface {
    RGBA() (r, g, b, a uint32)
}

type Model interface {
    Convert(c Color) Color
}

こういうメソッドが定義されていて、これらを実装すれば「色」として使えるということらしい。まあここは置いておく。

kun432kun432

25. Exercise: Images

スキップしよっかなと思ったけど、一応。

回答例
25.go
package main

import (
	"image"
	"image/color"
	"golang.org/x/tour/pic"
)

// 自作の画像構造体
type Image struct{}

// 画像サイズを定義
func (img Image) Bounds() image.Rectangle {
	return image.Rect(0, 0, 256, 256)  // 256x256 の画像
}

// 色モデルを定義
func (img Image) ColorModel() color.Model {
	return color.RGBAModel
}

// 各ピクセルの色を定義
func (img Image) At(x, y int) color.Color {
	v := uint8((x + y) / 2)  // 色の値を適当に計算
	return color.RGBA{v, v, 255, 255}  // 青っぽいグラデーション
}

func main() {
	m := Image{}
	pic.ShowImage(m)  // 画像を表示
}

ローカルで実行する場合はモジュールを初期化してパッケージを取得する必要がある

go mod init myimage
go get golang.org/x/tour/pic

実行

go run 25.go
出力
IMAGE:iVBORw0KGgoAAAANSUhEUgAAAQAAAAEACAIAAADTED8xAAACaUlEQVR42uzVMRGAAAzAwLSHf8tgAAf95QVkyVNvNRN50FWBl10V6ABa0AFIByAdgHQA0gFIByAdgHQA0gFIByAdgHQA0gFIByAdgHQA0gFIByAdgHQA0gFIByAdgHQA0gFIByAdgHQA0gFIByAdgHQA0gFIByAdgHQA0gFIByAdgHQA0gFIByAdgHQA0gFIByAdgHQA0gFIB6ADqEAHIB2AdADSAUgHIB2AdADSAUgHIB2AdADSAUgHIB2AdADSAUgHIB2AdADSAUgHIB2AdADSAUgHIB2AdADSAUgHIB2AdADSAUgHIB2AdADSAUgHIB2AdADSAUgHIB2AdADSAUgHIB2AdADSAUgHIB2AdAA6gBZ0ANIBSAcgHYB0ANIBSAcgHYB0ANIBSAcgHYB0ANIBSAcgHYB0ANIBSAcgHYB0ANIBSAcgHYB0ANIBSAcgHYB0ANIBSAcgHYB0ANIBSAcgHYB0ANIBSAcgHYB0ANIBSAcgHYB0ANIBSAcgHYB0ANIB6AAq0AFIByAdgHQA0gFIByAdgHQA0gFIByAdgHQA0gFIByAdgHQA0gFIByAdgHQA0gFIByAdgHQA0gFIByAdgHQA0gFIByAdgHQA0gFIByAdgHQA0gFIByAdgHQA0gFIByAdgHQA0gFIByAdgHQA0gFIByAdgA6gAh2AdADSAUgHIB2AdADSAUgHIB2AdADSAUgHIB2AdADSAUgHIB2AdADSAUgHIB2AdADSAUgHIB2AdADSAUgHIB2AdADSAUgHIB2AdADSAUgHIB2AdADSAUgHIB2AdADSAUgHIB2AdADyxy8AAP//YSoDD5pLB7MAAAAASUVORK5CYII=

pic.ShowImage()は本来はブラウザで画像表示するための関数らしく、ローカルだとbase64 PNGで出力される。ブラウザで表示するには、URL欄に

出力
data:image/png;base64,

の後に上記のIMAGE:以降の文字列を続けて入力すれば良い。

このスクラップは2ヶ月前にクローズされました