k1 (read: "k1t") is a small toolkit of the helpers you keep rewriting between projects: type casting that survives custom types and deep pointers, an Option type, safe pointer dereferencing, and set lookups.
The core idea: try a direct type switch first, fall back to reflection. So cast functions accept anything shaped right, not just exact types:
typeUserIDstringid:=UserID("u-42")
p:=&idcast.AsString(id) // "u-42"cast.AsString(&p) // "u-42" - pointers are dereferenced deeplyNote
As* functions panic on impossible conversions instead of returning errors. That is by design: k1 is testing-oriented, and in tests a panic is a failure you want loud.
go get github.com/amberpixels/k1package main
import (
"fmt""github.com/amberpixels/k1/cast""github.com/amberpixels/k1/maybe""github.com/amberpixels/k1/ptr""github.com/amberpixels/k1/set"
)
typeUserIDstringfuncmain() {
// cast: conversions that survive custom types and pointersid:=UserID("u-42")
fmt.Println(cast.AsString(&id)) // u-42// maybe: Option[T] instead of *Tport:=maybe.Some(8080)
ifport.Some() {
fmt.Println(port.Unwrap()) // 8080
}
// ptr: dereference with a zero-value fallbackvarname*stringfmt.Printf("%q\n", ptr.Deref(name)) // ""// set: map[T]struct{} without the ceremonyadmins:=set.NewLookup("alice", "bob")
fmt.Println(admins.Has("mallory")) // false
}The cast package converts (As*) and checks (Is*):
cast.AsString([]byte("data")) // "data"cast.AsBytes("data") // []byte("data")cast.AsInt(42.0) // 42 - integral floats convert; 42.5 panicscast.AsFloat(42) // 42.0cast.AsTime(&customTime) // time.Time, also from custom time typesFull set: AsString, AsBytes, AsBool, AsInt, AsFloat, AsTime, AsKind, AsSliceOfAny, AsStrings - plus IsString, IsStringish, IsNil, IsInt, IsStrings, IsTime for checks.
IsString is strict by default (true only for an actual string); loosen it per call or globally:
cast.IsString(UserID("u-42")) // false - strict by defaultcast.IsString(UserID("u-42"), cast.AllowCustomTypes()) // truecast.IsString([]byte("hi"), cast.AllowAll()) // true - most permissivecast.ConfigureIsStringConfig(cast.AllowAll()) // change the default globallyThe maybe package is an Option[T] for comparable types, with marshaling that behaves well in configs and APIs:
port:=maybe.Some(8080)
port.Some() // trueport.Unwrap() // 8080; panics on Nonenone:=maybe.None[int]()
json.Marshal(port) // 8080json.Marshal(none) // nullNone marshals as null in JSON and as the "None" sentinel in TOML; text unmarshalling treats empty, "null", and "None" as None. Shorthands: maybe.True(), maybe.False(), maybe.NoneBool(), maybe.NoneInt().
ptr-ptr.Deref(p)dereferences with a zero-value fallback for nil;ptr.Clone(p)copies a pointee.set-set.Lookup[T]ismap[T]struct{}withHas/Add/Delete/Clear; build one withset.NewLookup("a", "b").quick-quick.Append(a, b...)appends only elements not already present; trades extra memory (and GC pressure) for speed on large slices.errs-errs.UnwrapDeep(err)walks a wrapped error chain to the root cause.reflectish-IndirectDeepfor deep pointer dereferencing,LengthOffor the length of anything length-y, panic-safeInterface.k1(root) -k1.JoinStringers(vals, ", ")joins any slice offmt.Stringers.
k1 is a solo, opinionated project - but if you stumbled upon it and have ideas, questions, or bug reports, an issue is always welcome :)