Detect value out of range errors using type assertion in Golang
Asked Answered
P

2

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)
}
Pelvic answered 11/3, 2015 at 2:45 Comment(0)
M
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
Manipular answered 11/3, 2015 at 3:33 Comment(2)
Exactly what I wanted... Thanks!Pelvic
It would be nice if they added more examples. The error handling documentation didn't really cover this very well.Pelvic
B
-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)
}
Biblioclast answered 11/3, 2015 at 3:30 Comment(4)
Looks like type assertions used in assignment or initialization have a second return of a boolean and do not cause a runtime panic golang.org/ref/spec#Type_assertionsPelvic
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") { ... } }Biblioclast
Mucking around with the string output of Error() in Go completely misses the point and is very bad practice.Trueblue
I agree, I wasn't thinking through this problem clearly. I'm glad peterSO posted his superior solution.Biblioclast

© 2022 - 2024 — McMap. All rights reserved.