Skip to content

A library that provides Go Generics friendly "optional" features.

License

Notifications You must be signed in to change notification settings

moznion/go-optional

Repository files navigation

go-optional .github/workflows/check.yml codecov GoDoc

A library that provides Go Generics friendly "optional" features.

Synopsis

some := optional.Some[int](123)
fmt.Printf("%v\n", some.IsSome()) // => true
fmt.Printf("%v\n", some.IsNone()) // => false

v, err := some.Take()
fmt.Printf("err is nil: %v\n", err == nil) // => err is nil: true
fmt.Printf("%d\n", v) // => 123

mapped := optional.Map(some, func (v int) int {
    return v * 2
})
fmt.Printf("%v\n", mapped.IsSome()) // => true

mappedValue, _ := some.Take()
fmt.Printf("%d\n", mappedValue) // => 246
none := optional.None[int]()
fmt.Printf("%v\n", none.IsSome()) // => false
fmt.Printf("%v\n", none.IsNone()) // => true

_, err := none.Take()
fmt.Printf("err is nil: %v\n", err == nil) // => err is nil: false
// the error must be `ErrNoneValueTaken`

mapped := optional.Map(none, func (v int) int {
    return v * 2
})
fmt.Printf("%v\n", mapped.IsNone()) // => true

and more detailed examples are here: ./examples_test.go.

Docs

GoDoc

Supported Operations

Value Factory Methods

Option value handler methods

nil == None[T]

This library deals with nil as same as None[T]. So it works with like the following example:

var nilValue Option[int] = nil
fmt.Printf("%v\n", nilValue.IsNone()) // => true
fmt.Printf("%v\n", nilValue.IsSome()) // => false

JSON marshal/unmarshal support

This Option[T] type supports JSON marshal and unmarshal.

If the value wanted to marshal is Some[T] then it marshals that value into the JSON bytes simply, and in unmarshaling, if the given JSON string/bytes has the actual value on corresponded property, it unmarshals that value into Some[T] value.

example:

type JSONStruct struct {
	Val Option[int] `json:"val"`
}

some := Some[int](123)
jsonStruct := &JSONStruct{Val: some}

marshal, err := json.Marshal(jsonStruct)
if err != nil {
	return err
}
fmt.Printf("%s\n", marshal) // => {"val":123}

var unmarshalJSONStruct JSONStruct
err = json.Unmarshal(marshal, &unmarshalJSONStruct)
if err != nil {
	return err
}
// unmarshalJSONStruct.Val == Some[int](123)

Elsewise, when the value is None[T], the marshaller serializes that value as null. And if the unmarshaller gets the JSON null value on a property corresponding to the Optional[T] value, or the value of a property is missing, that deserializes that value as None[T].

example:

type JSONStruct struct {
	Val Option[int] `json:"val"`
}

none := None[int]()
jsonStruct := &JSONStruct{Val: none}

marshal, err := json.Marshal(jsonStruct)
if err != nil {
	return err
}
fmt.Printf("%s\n", marshal) // => {"val":null}

var unmarshalJSONStruct JSONStruct
err = json.Unmarshal(marshal, &unmarshalJSONStruct)
if err != nil {
	return err
}
// unmarshalJSONStruct.Val == None[int]()

And this also supports omitempty option for JSON unmarshaling. If the value of the property is None[T] and that property has omitempty option, it omits that property.

ref:

The "omitempty" option specifies that the field should be omitted from the encoding if the field has an empty value, defined as false, 0, a nil pointer, a nil interface value, and any empty array, slice, map, or string. https://pkg.go.dev/encoding/json#Marshal

example:

type JSONStruct struct {
	OmitemptyVal Option[string] `json:"omitemptyVal,omitempty"` // this should be omitted
}

jsonStruct := &JSONStruct{OmitemptyVal: None[string]()}
marshal, err := json.Marshal(jsonStruct)
if err != nil {
	return err
}
fmt.Printf("%s\n", marshal) // => {}

SQL Driver Support

Option[T] satisfies sql/driver.Valuer and sql.Scanner, so this type can be used by SQL interface on Golang.

example of the primitive usage:

sqlStmt := "CREATE TABLE tbl (id INTEGER NOT NULL PRIMARY KEY, name VARCHAR(32));"
db.Exec(sqlStmt)

tx, _ := db.Begin()
func() {
    stmt, _ := tx.Prepare("INSERT INTO tbl(id, name) values(?, ?)")
    defer stmt.Close()
    stmt.Exec(1, "foo")
}()
func() {
    stmt, _ := tx.Prepare("INSERT INTO tbl(id) values(?)")
    defer stmt.Close()
    stmt.Exec(2) // name is NULL
}()
tx.Commit()

var maybeName Option[string]

row := db.QueryRow("SELECT name FROM tbl WHERE id = 1")
row.Scan(&maybeName)
fmt.Println(maybeName) // Some[foo]

row := db.QueryRow("SELECT name FROM tbl WHERE id = 2")
row.Scan(&maybeName)
fmt.Println(maybeName) // None[]

Known Issues

The runtime raises a compile error like "methods cannot have type parameters", so Map(), MapOr(), MapWithError(), MapOrWithError(), Zip(), ZipWith(), Unzip() and UnzipWith() have been providing as functions. Basically, it would be better to provide them as the methods, but currently, it compromises with the limitation.

Author

moznion ([email protected])