safecast

package module
v1.6.0 Latest Latest
Warning

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

Go to latest
Published: Mar 12, 2025 License: MIT Imports: 6 Imported by: 43

README

🪄 go-safecast: safe numbers conversion

Go Report Card GoDoc codecov Code Climate Go Imports GitHub Repo stars

go-safecast solves the type conversion issues in Go

In Go, integer type conversion can lead to a silent and unexpected behavior and errors if not handled carefully.

This package helps to convert any number to another, and report an error when if there would be a loss or overflow in the conversion

Usage

package main

import (
  "fmt"
  "math"

  "github.com/ccoveille/go-safecast"
)

func main() {
  var a int

  a = 42
  b, err := safecast.ToUint8(a) // everything is fine
  if err != nil {
    fmt.Println(err)
  }
  fmt.Println(b)
  // Output: 42

  a = 255 + 1
  _, err = safecast.ToUint8(a) // 256 is greater than uint8 maximum value
  if err != nil {
    fmt.Println(err)
    // Output: conversion issue: 256 (int) is greater than 255 (uint8): maximum value for this type exceeded
  }

  a = -1
  _, err = safecast.ToUint8(a) // -1 cannot fit in uint8
  if err != nil {
    fmt.Println(err)
    // Output: conversion issue: -1 (int) is less than 0 (uint8): minimum value for this type exceeded
  }

  str := "\x99" // ASCII code 153 for Trademark symbol
  e := str[0]
  _, err = safecast.ToInt8(e)
  if err != nil {
    fmt.Println(err)
    // Output: conversion issue: 153 (uint8) is greater than 127 (int8): maximum value for this type exceeded
  }
}

Go Playground

Conversion issues

Issues can happen when converting between signed and unsigned integers, or when converting to a smaller integer type.

package main

import "fmt"

func main() {
  var a int64
  a = 42
  b := uint8(a)
  fmt.Println(b) // 42

  a = 255 // this is the math.MaxUint8
  b = uint8(a)
  fmt.Println(b) // 255

  a = 255 + 1
  b = uint8(a)
  fmt.Println(b) // 0 conversion overflow

  a = -1
  b = uint8(a)
  fmt.Println(b) // 255 conversion overflow
}

Go Playground

So you need to adapt your code to write something like this.

package main

import "fmt"

func main() {
  var a int64
  a = 42
  if a < 0 || a > math.MaxUint8 {
    log.Println("overflow") // Output: overflow
  }
  fmt.Println(b) // 42

  a = 255 // this is the math.MaxUint8
  b = uint8(a)
  fmt.Println(b) // 255

  a = 255 + 1
  b = uint8(a)
  if a < 0 || a > math.MaxUint8 {
    log.Println("overflow") // Output: overflow
  }
  fmt.Println(b) // Output: 0

  a = -1
  b = uint8(a)
  if a < 0 || a > math.MaxUint8 {
    log.Println("overflow") // Output: overflow
  }
  fmt.Println(b) // Output:255
}

Go Playground

go-safecast is there to avoid boilerplate copy pasta.

Motivation

The gosec project raised this to my attention when the gosec G115 rule was added

G115: Potential overflow when converting between integer types.

This issue was way more complex than expected, and required multiple fixes.

CWE-190 explains in detail.

But to sum it up, you can face:

  • infinite loop
  • access to wrong resource by id
  • grant access to someone who exhausted their quota

The gosec G115 will now report issues in a lot of project.

Alternatives

Some libraries existed, but they were not able to cover all the use cases.

Stargazers over time

Stargazers over time

Documentation

Overview

Example
var a int

a = 42
b, err := safecast.ToUint8(a) // everything is fine
if err != nil {
	fmt.Println(err)
}
fmt.Println(b)

a = 255 + 1
_, err = safecast.ToUint8(a) // 256 is greater than uint8 maximum value
if err != nil {
	fmt.Println(err)
}

a = -1
_, err = safecast.ToUint8(a) // -1 cannot fit in uint8
if err != nil {
	fmt.Println(err)
}

str := "\x99" // ASCII code 153 for Trademark symbol
e := str[0]
_, err = safecast.ToInt8(e)
if err != nil {
	fmt.Println(err)
}
Output:

42
conversion issue: 256 (int) is greater than 255 (uint8): maximum value for this type exceeded
conversion issue: -1 (int) is less than 0 (uint8): minimum value for this type exceeded
conversion issue: 153 (uint8) is greater than 127 (int8): maximum value for this type exceeded

Index

Examples

Constants

This section is empty.

Variables

View Source
var ErrConversionIssue = errors.New("conversion issue")

ErrConversionIssue is a generic error for type conversion issues It is used to wrap other errors

View Source
var ErrExceedMaximumValue = errors.New("maximum value for this type exceeded")

ErrExceedMaximumValue is an error for when the value is greater than the maximum value of the desired type.

View Source
var ErrExceedMinimumValue = errors.New("minimum value for this type exceeded")

ErrExceedMaximumValue is an error for when the value is less than the minimum value of the desired type.

View Source
var ErrRangeOverflow = errors.New("range overflow")

ErrRangeOverflow is an error for when the value is outside the range of the desired type

View Source
var ErrStringConversion = errors.New("cannot convert from string")

ErrStringConversion is an error for when the conversion fails from string.

View Source
var ErrUnsupportedConversion = errors.New("unsupported type")

ErrUnsupportedConversion is an error for when the conversion is not supported from the provided type.

Functions

func Convert added in v1.4.0

func Convert[NumOut Number, NumIn Input](orig NumIn) (converted NumOut, err error)

Convert attempts to convert any value to the desired type

  • If the conversion is possible, the converted value is returned.
  • If the conversion results in a value outside the range of the desired type, an ErrRangeOverflow error is wrapped in the returned error.
  • If the conversion exceeds the maximum value of the desired type, an ErrExceedMaximumValue error is wrapped in the returned error.
  • If the conversion exceeds the minimum value of the desired type, an ErrExceedMinimumValue error is wrapped in the returned error.
  • If the conversion is not possible for the desired type, an ErrUnsupportedConversion error is wrapped in the returned error.
  • If the conversion fails from string, an ErrStringConversion error is wrapped in the returned error.
  • If the conversion results in an error, an ErrConversionIssue error is wrapped in the returned error.
Example
b, err := safecast.Convert[int8](true)
fmt.Println(b, err)

b, err = safecast.Convert[int8]("true")
fmt.Println(b, err)

c, err := safecast.Convert[int16](17.1)
fmt.Println(c, err)

c, err = safecast.Convert[int16](int64(17))
fmt.Println(c, err)

d, err := safecast.Convert[int32]("17.1")
fmt.Println(d, err)

i, err := safecast.Convert[uint]("100_000")
fmt.Println(i, err)

i, err = safecast.Convert[uint]("0b11")
fmt.Println(i, err)

i, err = safecast.Convert[uint]("0x11")
fmt.Println(i, err)

a := int8(-1)
i, err = safecast.Convert[uint](a)
fmt.Println(i, err)

i, err = safecast.Convert[uint]("-1")
fmt.Println(i, err)

i, err = safecast.Convert[uint]("abc")
fmt.Println(i, err)

i, err = safecast.Convert[uint]("-1.1")
fmt.Println(i, err)

i, err = safecast.Convert[uint](".12345E+5")
fmt.Println(i, err)
Output:

1 <nil>
1 <nil>
17 <nil>
17 <nil>
17 <nil>
100000 <nil>
3 <nil>
17 <nil>
0 conversion issue: -1 (int8) is less than 0 (uint): minimum value for this type exceeded
0 conversion issue: -1 (int64) is less than 0 (uint): minimum value for this type exceeded
0 conversion issue: cannot convert from string abc to uint
0 conversion issue: -1.1 (float64) is less than 0 (uint): minimum value for this type exceeded
12345 <nil>

func MustConvert added in v1.5.0

func MustConvert[NumOut Number, NumIn Input](orig NumIn) NumOut

MustConvert calls Convert to convert the value to the desired type, and panics if the conversion fails.

func ToFloat32 added in v1.3.0

func ToFloat32[T Number](i T) (float32, error)

ToFloat32 attempts to convert any Number value to a float32. If the conversion results in a value outside the range of a float32, an ErrConversionIssue error is returned.

Example
a := int8(42)
i, err := safecast.ToFloat32(a)
fmt.Println(i, err)

b := math.MaxFloat64
i, err = safecast.ToFloat32(b)
fmt.Println(i, err)
Output:

42 <nil>
0 conversion issue: 1.7976931348623157e+308 (float64) is greater than 3.4028235e+38 (float32): maximum value for this type exceeded

func ToFloat64 added in v1.3.0

func ToFloat64[T Number](i T) (float64, error)

ToFloat64 attempts to convert any Number value to a float64. If the conversion results in a value outside the range of a float64, an ErrConversionIssue error is returned.

Example
a := int8(42)
i, err := safecast.ToFloat64(a)
fmt.Println(i, err)

b := math.MaxFloat64
i, err = safecast.ToFloat64(b)
fmt.Println(i, err)
Output:

42 <nil>
1.7976931348623157e+308 <nil>

func ToInt

func ToInt[T Number](i T) (int, error)

ToInt attempts to convert any [Type] value to an int. If the conversion results in a value outside the range of an int, an ErrConversionIssue error is returned.

Example
a := uint64(42)
i, err := safecast.ToInt(a)
fmt.Println(i, err)

b := float32(math.MaxFloat32)
i, err = safecast.ToInt(b)
fmt.Println(i, err)
Output:

42 <nil>
0 conversion issue: 3.4028235e+38 (float32) is greater than 9223372036854775807 (int): maximum value for this type exceeded

func ToInt16

func ToInt16[T Number](i T) (int16, error)

ToInt16 attempts to convert any Number value to an int16. If the conversion results in a value outside the range of an int16, an ErrConversionIssue error is returned.

Example
a := int32(42)
i, err := safecast.ToInt16(a)
fmt.Println(i, err)

a = int32(40000)
i, err = safecast.ToInt16(a)
fmt.Println(i, err)
Output:

42 <nil>
0 conversion issue: 40000 (int32) is greater than 32767 (int16): maximum value for this type exceeded

func ToInt32

func ToInt32[T Number](i T) (int32, error)

ToInt32 attempts to convert any Number value to an int32. If the conversion results in a value outside the range of an int32, an ErrConversionIssue error is returned.

Example
a := int(42)
i, err := safecast.ToInt32(a)
fmt.Println(i, err)

b := uint32(math.MaxInt32) + 1
i, err = safecast.ToInt32(b)
fmt.Println(i, err)
Output:

42 <nil>
0 conversion issue: 2147483648 (uint32) is greater than 2147483647 (int32): maximum value for this type exceeded

func ToInt64

func ToInt64[T Number](i T) (int64, error)

ToInt64 attempts to convert any Number value to an int64. If the conversion results in a value outside the range of an int64, an ErrConversionIssue error is returned.

Example
a := uint64(42)
i, err := safecast.ToInt64(a)
fmt.Println(i, err)

a = uint64(math.MaxInt64) + 1
i, err = safecast.ToInt64(a)
fmt.Println(i, err)
Output:

42 <nil>
0 conversion issue: 9223372036854775808 (uint64) is greater than 9223372036854775807 (int64): maximum value for this type exceeded

func ToInt8

func ToInt8[T Number](i T) (int8, error)

ToInt8 attempts to convert any Number value to an int8. If the conversion results in a value outside the range of an int8, an ErrConversionIssue error is returned.

Example
a := float64(42.42)
i, err := safecast.ToInt8(a)
fmt.Println(i, err)

a = float64(200.42)
i, err = safecast.ToInt8(a)
fmt.Println(i, err)
Output:

42 <nil>
0 conversion issue: 200.42 (float64) is greater than 127 (int8): maximum value for this type exceeded

func ToUint

func ToUint[T Number](i T) (uint, error)

ToUint attempts to convert any Number value to an uint. If the conversion results in a value outside the range of an uint, an ErrConversionIssue error is returned.

Example
a := int8(42)
i, err := safecast.ToUint(a)
fmt.Println(i, err)

a = int8(-1)
i, err = safecast.ToUint(a)
fmt.Println(i, err)
Output:

42 <nil>
0 conversion issue: -1 (int8) is less than 0 (uint): minimum value for this type exceeded

func ToUint16

func ToUint16[T Number](i T) (uint16, error)

ToUint16 attempts to convert any Number value to an uint16. If the conversion results in a value outside the range of an uint16, an ErrConversionIssue error is returned.

Example
a := int64(42)
i, err := safecast.ToUint16(a)
fmt.Println(i, err)

a = int64(-1)
i, err = safecast.ToUint16(a)
fmt.Println(i, err)
Output:

42 <nil>
0 conversion issue: -1 (int64) is less than 0 (uint16): minimum value for this type exceeded

func ToUint32

func ToUint32[T Number](i T) (uint32, error)

ToUint32 attempts to convert any Number value to an uint32. If the conversion results in a value outside the range of an uint32, an ErrConversionIssue error is returned.

Example
a := int16(42)
i, err := safecast.ToUint32(a)
fmt.Println(i, err)

a = int16(-1)
i, err = safecast.ToUint32(a)
fmt.Println(i, err)
Output:

42 <nil>
0 conversion issue: -1 (int16) is less than 0 (uint32): minimum value for this type exceeded

func ToUint64

func ToUint64[T Number](i T) (uint64, error)

ToUint64 attempts to convert any Number value to an uint64. If the conversion results in a value outside the range of an uint64, an ErrConversionIssue error is returned.

Example
a := int8(42)
i, err := safecast.ToUint64(a)
fmt.Println(i, err)

a = int8(-1)
i, err = safecast.ToUint64(a)
fmt.Println(i, err)
Output:

42 <nil>
0 conversion issue: -1 (int8) is less than 0 (uint64): minimum value for this type exceeded

func ToUint8

func ToUint8[T Number](i T) (uint8, error)

ToUint8 attempts to convert any Number value to an uint8. If the conversion results in a value outside the range of an uint8, an ErrConversionIssue error is returned.

Example
a := int64(42)
i, err := safecast.ToUint8(a)
fmt.Println(i, err)

a = int64(-1)
i, err = safecast.ToUint8(a)
fmt.Println(i, err)
Output:

42 <nil>
0 conversion issue: -1 (int64) is less than 0 (uint8): minimum value for this type exceeded

Types

type Float added in v1.1.0

type Float interface {
	~float32 | ~float64
}

Float is a constraint for the float32 and float64 types.

type Input added in v1.6.0

type Input interface {
	Number | ~string | ~bool
}

Input is a constraint for all types that can be used as input for Convert, and MustConvert

type Integer added in v1.1.0

type Integer interface {
	Signed | Unsigned
}

Integer is a constraint for the all unsigned and signed integers

type Number

type Number interface {
	Integer | Float
}

Number is a constraint for all integers and floats TODO: consider using complex, but not sure there is a need

type Signed added in v1.1.0

type Signed interface {
	~int | ~int8 | ~int16 | ~int32 | ~int64
}

Signed is a constraint for all signed integers: int, int8, int16, int32, and int64 types.

type Unsigned added in v1.1.0

type Unsigned interface {
	~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64
}

Unsigned is a constraint for all unsigned integers: uint, uint8, uint16, uint32, and uint64 types. TODO: support uintpr

Jump to

Keyboard shortcuts

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