1

Given the following code:

iv, err := strconv.ParseInt("18446744073709551448", 10, 64)
fmt.Println(iv)
fmt.Printf("%#v\n", err)
fmt.Printf("%v\n", err)

//Output:
9223372036854775807
&strconv.NumError{Func:"ParseInt", Num:"18446744073709551448", Err:(*errors.errorString)(0x1040a040)}
strconv.ParseInt: parsing "18446744073709551448": value out of range

How can I detect that the function failed due to being out of range of an int64? The strconv.ParseInt function returns an error type, but in this case it is actually a strconv.NumError type as indicated by %#v. The Error Handling and Go article mentions you can use type assertion to check for specific types of errors, but it doesn't give any examples. What expression should I use to complete this code:

if expression {
    uv, err := strconv.ParseUint("18446744073709551448", 10, 64)
}
Greg Bray
  • 14,929
  • 12
  • 80
  • 104

2 Answers2

11

We have,

Package strconv

var ErrRange = errors.New("value out of range")

ErrRange indicates that a value is out of range for the target type.

type NumError struct {
        Func string // the failing function (ParseBool, ParseInt, ParseUint, ParseFloat)
        Num  string // the input
        Err  error  // the reason the conversion failed (ErrRange, ErrSyntax)
}

A NumError records a failed conversion.

func (e *NumError) Error() string

For example,

package main

import (
    "fmt"
    "strconv"
)

func main() {
    iv, err := strconv.ParseInt("18446744073709551448", 10, 64)
    if err != nil {
        if numError, ok := err.(*strconv.NumError); ok {
            if numError.Err == strconv.ErrRange {
                fmt.Println("Detected", numError.Num, "as a", strconv.ErrRange)
                return
            }
        }
        fmt.Println(err)
        return
    }
    fmt.Println(iv)
}

Output:

Detected 18446744073709551448 as a value out of range
peterSO
  • 158,998
  • 31
  • 281
  • 276
-1

The error that's returned from strconv.ParseInt is only known at compile time to be some type that implements the Error interface. Type assertion allows you to insist that it's a strconv.NumError and inspect its fields directly, but at the risk of throwing a runtime panic if you turn out to be wrong:

if err.(*strconv.NumError).Err.Error() == "value out of range" {
    uv, err := strconv.ParseUint("18446744073709551448", 10, 64)
}

A more flexible solution (but maybe too loosey-goosey for your purposes) would be to perform a substring match on the err.Error() method:

if strings.Contains(err.Error(), "value out of range") {
    uv, err := strconv.ParseUint("18446744073709551448", 10, 64)
}
Tim Pierce
  • 5,514
  • 1
  • 15
  • 31
  • 3
    Looks like type assertions used in assignment or initialization have a second return of a boolean and do not cause a runtime panic https://golang.org/ref/spec#Type_assertions – Greg Bray Mar 11 '15 at 03:55
  • Good point. But to take advantage of the comma ok, you'll almost certainly need to make the type assertion all by itself and not also try to dereference `.Err.Error()`. i.e. `if ne, ok := err.(*strconv.NumError); ok { if ne.Err.Error() == "value out of range") { ... } }` – Tim Pierce Mar 11 '15 at 04:00
  • 2
    Mucking around with the string output of `Error()` in Go completely misses the point and is very bad practice. – Dave C Mar 11 '15 at 16:34
  • I agree, I wasn't thinking through this problem clearly. I'm glad peterSO posted his superior solution. – Tim Pierce Mar 12 '15 at 17:22