ys memos

Blog

Goで安全に使えるOption型を書いてみた


go

2025/05/26


Goでは、「存在するかわからない」値を表現するにあたって、

  • ゼロ値で表現
  • 例えば -1などの使わない範囲を決め、それで表現
  • 型をポインタ画にしてnilで表現

などの手法が使われることが多いと思われる。

しかし、これらの手法はそれぞれに問題があり、かつ、コード全体でその意図を意識しながら読み書きする必要が出てしまい、ハイコンテキストなコードとなってしまうおそれもある。

そこで、このようなシチュエーションでつかえるようなOption型を実装してみた。


目指すのはこのあたり。

  • 型で意味を表現できるようにする
  • メモリ効率を維持する
  • メモリ安全性を向上する

パッケージはGitHubに公開している。 (2025/05/26現在)

package main

import (
  "fmt"
  "github.com/ysuzuki19/robustruct/pkg/option"
)

type User struct {
  Name string             // 必須フィールド
  Age  option.Option[int] // 存在するかわからないフィールド
}

func main() {
  user := User{
    Name: "Alice",
    Age:  option.NewSome(30), // 存在する場合
  }

  if user.Age.IsSome() {
    fmt.Printf("User %s is %d years old.\n", user.Name, user.Age.Unwrap())
  } else {
    fmt.Printf("User %s age is unknown.\n", user.Name)
  }

  // 存在しない場合
  user2 := User{
    Name: "Bob",
    Age:  option.None[int](), // 存在しない場合、型指定とともに Noneを使う
  }

  if user2.Age.IsSome() {
    fmt.Printf("User %s is %d years old.\n", user2.Name, user2.Age.Unwrap())
  } else {
    fmt.Printf("User %s age is unknown.\n", user2.Name)
  }
}

参考にするのはRustのOption型とし、用途やメソッドの種類などは参考にしつつ、型情報や内部の挙動はGoの慣例に合わせて実装する。

構造体そのものはシンプルで、単に T 型のポインタを保持する。このシンプルな仕組みにより、Optionによる抽象化によるメモリ使用量の増加を抑える。

type Option[T any] struct {
	ptr *T
}


考慮している点として、「Goではゼロ値で自動で初期化される」のを前提とする必要があり、この場合でも安全に動く必要がある。

つまり、例えば

type Option[T any] struct {
  ptr *T,
  is_some bool
}

のような構造体にして、データ管理と状態管理を別にしてしまうと、間違えて option.Option {is_some: true}のように初期化してしまった場合に、意図せずnilポインタを参照してしまう危険性がある。 それを避けたい場合、o.is_some && o.ptr != nilといった条件を書くことも可能だが、この比較を行う場合、実際には後半ブロックのみで十分であるため、この作りに落ち着いた。


safetyのみを考慮するならば、Option型のポインタを直接取得するメソッドは不要であるが、Goの慣例に合わせて、以下のようなメソッドを追加している。

  • Ptr() *T - ポインタをそのまま取得する

これは、直接ポインタを取得するもので、それが存在するものなのかどうかは度外視し、単にポインタを取得する。 

  • Get() (*T, bool) - ポインタとその存在有無を取得する

mapアクセスなどでも見られるような、データとその存在有無を同時に取得するメソッド。


こちらに公開しているので、よかったら使ってみてください!


関連タグを探す