safe

package module
v0.0.0-...-6b0c4ff Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 8, 2024 License: MIT Imports: 10 Imported by: 0

README

safe

Toe-caps for your Golang foot guns. [WIP]

safe is the friedly counterpart to unsafe. One of the most common issues when developing in Go are runtime panics. This package provides a set of generic functions and data structures that safeguards you from runtime panics and are meant to mitigate programmer mistakes.

Data Structures

All iterable data structures provide a Range iterator method which can be used with the range expression, introduced in Go 1.23.

Array

Array is a generic, fixed-size array with out-of-bounds panic protection. When indexing an array A, the provided index i will be clamped to the range i E [0, len(A)-1].

CircularArray

CircularArray is a generic, fixed-size circular array with out-of-bounds panic protection.

When indexing a circular array, if the provided index exceeds the length of the array, it will be wrapped around. Negative index value are allowed and will also be wrapped around in the opposite direction. For example, one can index the last element of the array as:

lastElement := circularArray.Index(-1)
CircularSlice

CircularSlice is a generic, dynamically-sized array with out-of-bounds and out-of-memory panic protection.

When indexing a circular slice, if the provided index exceeds the length of the array, it will be wrapped around. Negative index value are allowed and will also be wrapped around in the opposite direction. See CircularArray for an example.

The capacity of the circular slice can be increased after initialization using the Grow method, all added values will be set to the generic zero value. Upon initialization, an estimate is made of the maximum size in memory the slice can take based on the runtime memory profile of the system. This will be used as the maximum allowed capacity of the slice to prevent out-of-memory panics. The determined maximum safe capacity is returned by the MaxCap method.

Channel

Channel is a generic channel with double-close and nil-channel panic protection.

NonBlockingChannel

NonBlockingChannel is a generic non-blocking channel with double-close and nil-channel panic protection.

When pushing to a full channel the overflow counter is incremented. The overflow count can be retrieved using the Overflow method. When popping from an empty channel, the generic zero value is returned.

Map

Map is a generic map with nil-panic protection and a built-in lock. The key must be a comparable type.

Slice

Slice is a generic, dynamically-sized array with out-of-bounds and out-of-memory panic protection.

Any index below zero will be clamped to zero. If an index is set that is greater than the length of the slice, the slice will automatically grow its length to that index. All values between the previous last value and the new last value will be set to the generic zero value of the slice type. Upon initialization, an estimate is made of the maximum size in memory the slice can take based on the runtime memory profile of the system. This will be used as the maximum allowed capacity of the slice to prevent out-of-memory panics. The determined maximum safe capacity is returned by the MaxCap method.

Math helpers

Add, Multiply, Subtract

Add, Multiply, and Subtract are generic math functions that offer overflow protection.

Clamp, ClampMin, ClampMax

Clamp is a generic function to clamp any number between a minimum and maximum value. ClampMin and ClampMax work the same but only clamp the lower or upper limit.

CompareFloats

CompareFloats is a safe way to compare two floating point numbers providing an absolute and a relative tolerance.

Divide

Divide is a generic division function that provides division-by-zero panic protection. When the denominator is zero, the returned value will be the maximum value expressed by the numeric type passed to the Divide function with the sign given by the numerator.

Ternary

Ternary can be used as a ternary operator for inline comparisons.

Pointer helpers

Dereference

Dereference is a generic function for safely dereferencing any pointer. If the pointer is nil, it will return the generic zero value.

SetValue

SetValue is a generic function that sets the value of a pointer reference with a builtin nil-check.

Type assertion

TypeAssert

TypeAssert is a safe type assertion function. It returns the zero value of the provided type if the assertion fails. Beware that the asserted type can be nil in case of an interface.

RequireTypeAssert

RequireTypeAssert is a type assertion assetion function with error return that can panic. Returns an error if type assertion fails. Instead of returning a nil-value when it asserts an interface as nil, like TypeAssert does, it will panic and recover instead.

Miscellaneous

IsNil

IsNil is a helper function to do a deep nil check on any type.

Questions

Why do the math functions use the unsafe package?

At the moment (Go 1.23), Go does not offer generic type assertion or generic type switching. So, after extensive testing and benchmarking, I found that switching based on the byte size of the generic zero type is currently the fastest way to differentiate between different numeric types. However, this does require some examination of the types overflow behaviour to deduce the concrete type. See the implementations of Add, Divide, Multiply, and Subtract as example.

Why do you dereference new(T) without nil-checking?

Currently (Go 1.23), there is no pre-defined way to return the zero value of a generic type, but you can instantiate a pointer to a generic zero type using new(T). This value is always safe to dereference and will not panic, so we can use *new(T). However, this can return nil in some cases, so package users are still expected to do proper nil-checking when working with nullable types.

Documentation

Index

Constants

This section is empty.

Variables

View Source
var (
	ErrPanic        = errors.New("type assertion panic")
	ErrNilInterface = errors.New("type assertion to nil interface")
	ErrTypeAssert   = errors.New("type assertion failed")
)

Functions

func Add

func Add[T Number](x, y T) T

A generic add function with overflow protection.

func Clamp

func Clamp[T Number](number, min, max T) T

func ClampMax

func ClampMax[T Number](number, max T) T

func ClampMin

func ClampMin[T Number](number, min T) T

func CompareFloats

func CompareFloats[T constraints.Float](a, b, tolerance, relativeTolerance T) bool

func Dereference

func Dereference[T any](pointer *T) T

Dereference with nil pointer dereference protection. Returns the zero value of the underlying type is the provided pointer is nil.

func Divide

func Divide[T Number](x, y T) T

A generic divide function with divide by zero protection. If the denominator is zero, the maximum or mininum value of the generic type will be returned based on the sign of numerator.

func IsNil

func IsNil(value any) bool

func Multiply

func Multiply[T Number](x, y T) T

A generic multiply function with overflow protection.

func RequireTypeAssert

func RequireTypeAssert[T any](value any) (typ T, err error)

TODO: test Safer type assertion with error return that can panic and recover in case the type cannot be asserted and the generic zero value is nil. Returns an error if type assertion fails. If the generic zero value is nil, panic and recover instead.

func SetValue

func SetValue[T any](pointer **T, value T)

Set the underlying value of a pointer with nil pointer dereference protection.

func Subtract

func Subtract[T Number](x, y T) T

A generic subtract function with overflow protection.

func Ternary

func Ternary[T any](condition bool, incase T, otherwise T) T

func TypeAssert

func TypeAssert[T any](value any) T

Safer generic type assertion. Returns zero value of the provided type is assertion fails. Beware that the asserted type can be nil in case of an interface, so this is not runtime safe!

Types

type AltArray

type AltArray[T any] struct {
	// contains filtered or unexported fields
}

A generic fixed size slice with out-of-bounds protection by clamping indices to [0, len-1].

func NewAltArray

func NewAltArray[T any](length int) *AltArray[T]

func (*AltArray[T]) Clear

func (a *AltArray[T]) Clear()

func (*AltArray[T]) Index

func (a *AltArray[T]) Index(index int) T

func (*AltArray[T]) Len

func (a *AltArray[T]) Len() int

func (*AltArray[T]) Range

func (a *AltArray[T]) Range() iter.Seq2[int, T]

TODO: test

func (*AltArray[T]) Set

func (a *AltArray[T]) Set(index int, value T) bool

type Array

type Array[T any] struct {
	// contains filtered or unexported fields
}

A generic fixed size slice with out-of-bounds protection by clamping indices to [0, len-1].

func NewArray

func NewArray[T any](length int) *Array[T]

func (*Array[T]) Clear

func (a *Array[T]) Clear()

func (*Array[T]) Index

func (a *Array[T]) Index(index int) T

func (*Array[T]) Len

func (a *Array[T]) Len() int

func (*Array[T]) Range

func (a *Array[T]) Range() iter.Seq2[int, T]

TODO: test

func (*Array[T]) Set

func (a *Array[T]) Set(index int, value T)

type Channel

type Channel[T any] struct {
	// contains filtered or unexported fields
}

A generic blocking channel with double closing and nil channel protection.

func NewChannel

func NewChannel[T any](size int) *Channel[T]

A generic channel with double closing and nil channel protection.

func (*Channel[T]) Clear

func (c *Channel[T]) Clear()

TODO: test

func (*Channel[T]) Close

func (c *Channel[T]) Close()

func (*Channel[T]) Len

func (c *Channel[T]) Len() int

func (*Channel[T]) Pop

func (c *Channel[T]) Pop() T

func (*Channel[T]) Push

func (c *Channel[T]) Push(item T)

func (*Channel[T]) Range

func (c *Channel[T]) Range() iter.Seq[T]

TODO: test

type CircularArray

type CircularArray[T any] struct {
	// contains filtered or unexported fields
}

A generic fixed size slice with out-of-bounds protection by allowing index overflow. Out-of-bounds indices are wrapped around like in a circular buffer.

func NewCircularArray

func NewCircularArray[T any](length int) *CircularArray[T]

func (*CircularArray[T]) Clear

func (a *CircularArray[T]) Clear()

func (*CircularArray[T]) Index

func (a *CircularArray[T]) Index(i int) T

func (*CircularArray[T]) Len

func (a *CircularArray[T]) Len() int

func (*CircularArray[T]) Range

func (a *CircularArray[T]) Range() iter.Seq2[int, T]

TODO: test

func (*CircularArray[T]) Set

func (a *CircularArray[T]) Set(i int, value T)

type CircularChannel

type CircularChannel[T any] struct {
	// contains filtered or unexported fields
}

A generic, circular channel that blocks on pop, but not on push,

with double closing and nil channel protection.

Block until an new entry is available on pop. If the channel is full, the oldest entry will be dropped on push.

func NewCircularChannel

func NewCircularChannel[T any](size int) *CircularChannel[T]

func (*CircularChannel[T]) Clear

func (c *CircularChannel[T]) Clear()

TODO: test

func (*CircularChannel[T]) Close

func (c *CircularChannel[T]) Close()

func (*CircularChannel[T]) Len

func (c *CircularChannel[T]) Len() int

func (*CircularChannel[T]) Pop

func (c *CircularChannel[T]) Pop() T

func (*CircularChannel[T]) Push

func (c *CircularChannel[T]) Push(item T)

func (*CircularChannel[T]) Range

func (c *CircularChannel[T]) Range() iter.Seq[T]

TODO: test

type CircularSlice

type CircularSlice[T any] struct {
	// contains filtered or unexported fields
}

A generic slice with out-of-bounds protection by allowing index overflow. For reading, out-of-bounds indices are wrapped around like in a circular buffer. For writing, the slice will automatically grow its underlying capacity up to, a pre-determined maximum capacity base on the system memory statistics.

func NewCircularSlice

func NewCircularSlice[T any](capacity int) CircularSlice[T]

func (CircularSlice[T]) Cap

func (s CircularSlice[T]) Cap() int

func (CircularSlice[T]) Grow

func (s CircularSlice[T]) Grow(n int)

func (CircularSlice[T]) Index

func (s CircularSlice[T]) Index(index int) T

func (CircularSlice[T]) Len

func (s CircularSlice[T]) Len() int

func (CircularSlice[T]) MaxCap

func (s CircularSlice[T]) MaxCap() int

func (*CircularSlice[T]) Range

func (a *CircularSlice[T]) Range() iter.Seq2[int, T]

TODO: test

func (CircularSlice[T]) Set

func (s CircularSlice[T]) Set(index int, value T)

type Map

type Map[Key comparable, Value any] struct {
	// contains filtered or unexported fields
}

func NewMap

func NewMap[Key comparable, Value any](size int) *Map[Key, Value]

func (*Map[Key, Value]) Clear

func (m *Map[Key, Value]) Clear(key Key)

func (*Map[Key, Value]) Delete

func (m *Map[Key, Value]) Delete(key Key)

func (*Map[Key, Value]) Len

func (m *Map[Key, Value]) Len() int

func (*Map[Key, Value]) Load

func (m *Map[Key, Value]) Load(key Key) (value Value, ok bool)

func (*Map[Key, Value]) Range

func (m *Map[Key, Value]) Range() iter.Seq2[Key, Value]

TODO: test

func (*Map[Key, Value]) Store

func (m *Map[Key, Value]) Store(key Key, value Value)

func (*Map[Key, Value]) Swap

func (m *Map[Key, Value]) Swap(key Key, value Value) (previous Value, loaded bool)

type NonBlockingChannel

type NonBlockingChannel[T any] struct {
	// contains filtered or unexported fields
}

A generic non-blocking channel with double closing and nil channel protection. Returns the zero value of the generic type if no item is available on the channel. Increases the overflow counter when the channel is full.

func NewNonBlockingChannel

func NewNonBlockingChannel[T any](size int) *NonBlockingChannel[T]

func (*NonBlockingChannel[T]) Clear

func (c *NonBlockingChannel[T]) Clear()

TODO: test

func (*NonBlockingChannel[T]) Close

func (c *NonBlockingChannel[T]) Close()

func (*NonBlockingChannel[T]) Len

func (c *NonBlockingChannel[T]) Len() int

func (*NonBlockingChannel[T]) Overflow

func (c *NonBlockingChannel[T]) Overflow() T

func (*NonBlockingChannel[T]) OverflowCount

func (c *NonBlockingChannel[T]) OverflowCount() uint64

func (*NonBlockingChannel[T]) Pop

func (c *NonBlockingChannel[T]) Pop() T

func (*NonBlockingChannel[T]) Push

func (c *NonBlockingChannel[T]) Push(item T)

func (*NonBlockingChannel[T]) Range

func (c *NonBlockingChannel[T]) Range() iter.Seq[T]

TODO: test

type Number

type Number interface {
	constraints.Integer | constraints.Float
}

type Slice

type Slice[T any] struct {
	// contains filtered or unexported fields
}

A generic slice with out-of-bounds protection. For reading, out-of-bounds indices will be clamped to [0, len-1]. For writing, the slice will automatically grow its underlying capacity up to, a pre-determined maximum capacity base on the system memory statistics.

func NewSlice

func NewSlice[T any](capacity int) *Slice[T]

func (*Slice[T]) Cap

func (s *Slice[T]) Cap() int

func (*Slice[T]) Index

func (s *Slice[T]) Index(index int) T

func (*Slice[T]) Len

func (s *Slice[T]) Len() int

func (*Slice[T]) MaxCap

func (s *Slice[T]) MaxCap() int

func (*Slice[T]) Range

func (a *Slice[T]) Range() iter.Seq2[int, T]

TODO: test

func (*Slice[T]) Set

func (s *Slice[T]) Set(index int, value T)

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL