Go1.17beta1がダウンロードできるようになったので、Generics(Type Parameters)でStackを書いて軽く遊んだメモ。
Generics (Type Parameters)について
最新の仕様のProposalは以下を見ると良い。
実際にどんな感じのコードになるのかは、GitHubのdev.typeparams branchのコードを見ると良さそう。
Go1.17beta1のダウンロード
普段使っているgoコマンドから以下のようにGo1.17beta1をダウンロードできる。
$ go get golang.org/dl/go1.17beta1 $ go1.17beta1 download
これで ~/sdk/go1.17beta1 にインストールされる。
FYI: Goは標準の機能で複数のバージョンをインストールできる。より詳しくは以下のドキュメントを読むと良い。
Genericsのサンプルコード
Go1.17beta1に付属している以下のディレクトリに結構ある。
$ ls $(go1.17beta1 env GOROOT)/test/typeparam absdiff.go fact.go issue45738.go min.go slices.go tparam1.go adder.go graph.go list.go ordered.go smallest.go typelist.go append.go importtest.go list2.go orderedmap.go smoketest.go value.go chans.go index.go lockable.go pair.go stringable.go combine.go interfacearg.go map.go pragma.go stringer.go cons.go issue45547.go maps.go sets.go struct.go double.go issue45722.go metrics.go settable.go sum.go
GenericsでStackを書いてみる
こんな感じになった。現時点ではType Parametersの型をexportすることができないっぽいので、stack
と小文字で定義してmainパッケージに書いている。
// run -gcflags=-G=3 package main import "fmt" var ( ErrEmptyStack = fmt.Errorf("stack is empty") ) type stack[T any] struct { data []T capacity int } func newStack[T any](capacity int) *stack[T] { if capacity <= 0 { panic("must be 'capacity' > 0") } data := make([]T, 0, capacity) return &stack[T]{data: data, capacity: capacity} } func (s *stack[T]) Push(v T) { s.data = append(s.data, v) } func (s *stack[T]) Pop() (T, error) { if s.Size() == 0 { var zero T return zero, ErrEmptyStack } v := s.data[len(s.data)-1] s.data = s.data[:len(s.data)-1] return v, nil } func (s *stack[T]) Peek() (T, error) { if s.Size() == 0 { var zero T return zero, ErrEmptyStack } return s.data[len(s.data)-1], nil } func (s *stack[T]) Size() int { return len(s.data) } func main() { s := newStack[int](10) s.Push(1) s.Push(2) s.Push(3) fmt.Printf("s = %+v\n", s) _, err := s.Pop() if err != nil { panic(err) } fmt.Printf("s = %+v\n", s) }
コード書いてて少し悩んだのは、func (s *stack[T]) Pop() (T, error)
のようなメソッドでerrorを返す時にT型のゼロ値をどうやって返すのか、という点だった。どうやら以下のように var zero T
を定義してそれを返すのが良いらしい。return nil, ErrEmptyStack
という風に書くと、Tはポインタ型ではないのでコンパイルエラーになる。
func (s *stack[T]) Pop() (T, error) { if s.Size() == 0 { var zero T return zero, ErrEmptyStack } v := s.data[len(s.data)-1] s.data = s.data[:len(s.data)-1] return v, nil }
実行
main関数があれば普通にgo1.17beta1 runコマンドで実行できる。-gcflags=-G=3
を付けないとgenericsのコードはコンパイルできないので注意。
$ go1.17beta1 run -gcflags=-G=3 ./typeparam.go s = &{data:[1 2 3] capacity:10} s = &{data:[1 2] capacity:10}
感想
Type Parametersの機能が入るリリースは2022年の2月のGo 1.18だと思うけど、現時点でもモリモリ実装されているのでとても楽しみである。