Parsing Booleans and Integers in Go: ParseBool, ParseInt, and ParseUint 2/7

String parsing is a fundamental operation in Go programming. Whether you’re reading configuration files, processing user input, or handling API responses, you’ll frequently need to convert string representations into their appropriate data types. Go’s strconv
package provides three essential functions for this purpose: ParseBool
, ParseInt
, and ParseUint
.
Understanding ParseBool
The ParseBool
function converts string representations of boolean values into actual boolean types. Its signature is straightforward:
func ParseBool(str string) (bool, error)
This function accepts a limited set of string values and converts them to their boolean equivalents. The accepted values are case-insensitive and include:
"true"
,"t"
,"1"
→true
"false"
,"f"
,"0"
→false
Let’s see how this works in practice:
package main
import (
"fmt"
"strconv"
)
func main() {
// Valid true values
trueValues := []string{"true", "TRUE", "t", "T", "1"}
for _, val := range trueValues {
result, err := strconv.ParseBool(val)
if err != nil {
fmt.Printf("Error parsing %s: %v\n", val, err)
continue
}
fmt.Printf("ParseBool(%s) = %t\n", val, result)
}
// Valid false values
falseValues := []string{"false", "FALSE", "f", "F", "0"}
for _, val := range falseValues {
result, err := strconv.ParseBool(val)
if err != nil {
fmt.Printf("Error parsing %s: %v\n", val, err)
continue
}
fmt.Printf("ParseBool(%s) = %t\n", val, result)
}
// Invalid values
invalidValues := []string{"yes", "no", "2", "on", "off", ""}
for _, val := range invalidValues {
result, err := strconv.ParseBool(val)
if err != nil {
fmt.Printf("Error parsing %s: %v\n", val, err)
} else {
fmt.Printf("ParseBool(%s) = %t\n", val, result)
}
}
}
The function is strict about its input validation. Any string that doesn’t match the accepted patterns will return an error of type *strconv.NumError
. This makes ParseBool
reliable but requires you to handle cases where the input might not conform to these specific formats.
Mastering ParseInt: Converting Strings to Signed Integers
The ParseInt
function handles the conversion of string representations to signed integers with precise control over number base and bit size. Its signature provides three parameters:
func ParseInt(s string, base int, bitSize int) (int64, error)
The base
parameter determines how the string should be interpreted numerically, while bitSize
controls the range of the resulting integer. Let’s explore each aspect systematically.
Base Parameter Examples
The base parameter accepts values from 2 to 36, with some special behaviors:
Binary (base 2):
result, err := strconv.ParseInt("1010", 2, 64)
// result = 10 (decimal)
result, err := strconv.ParseInt("11111111", 2, 64)
// result = 255 (decimal)
Octal (base 8):
result, err := strconv.ParseInt("755", 8, 64)
// result = 493 (decimal)
result, err := strconv.ParseInt("1777", 8, 64)
// result = 1023 (decimal)
Decimal (base 10):
result, err := strconv.ParseInt("12345", 10, 64)
// result = 12345
result, err := strconv.ParseInt("-987", 10, 64)
// result = -987
Hexadecimal (base 16):
result, err := strconv.ParseInt("ff", 16, 64)
// result = 255 (decimal)
result, err := strconv.ParseInt("DEADBEEF", 16, 64)
// result = 3735928559 (decimal)
Base 0: Automatic Detection
When you set base to 0, ParseInt
automatically detects the number format based on the string prefix:
package main
import (
"fmt"
"strconv"
)
func main() {
examples := []string{
"42", // decimal (no prefix)
"0755", // octal (0 prefix)
"0xff", // hexadecimal (0x prefix)
"0b1010", // binary (0b prefix)
"-0x20", // negative hexadecimal
}
for _, example := range examples {
result, err := strconv.ParseInt(example, 0, 64)
if err != nil {
fmt.Printf("Error parsing %s: %v\n", example, err)
continue
}
fmt.Printf("ParseInt(%s, 0, 64) = %d\n", example, result)
}
}
This automatic detection follows Go’s integer literal conventions, making it particularly useful when parsing Go source code or configuration files that use Go-style number formats.
BitSize Parameter Usage
The bitSize
parameter doesn’t change the return type (always int64
) but validates that the parsed value fits within the specified bit range:
// 8-bit signed integer range: -128 to 127
result, err := strconv.ParseInt("100", 10, 8) // Valid
result, err := strconv.ParseInt("200", 10, 8) // Error: out of range
// 16-bit signed integer range: -32768 to 32767
result, err := strconv.ParseInt("30000", 10, 16) // Valid
result, err := strconv.ParseInt("40000", 10, 16) // Error: out of range
// 32-bit signed integer range: -2147483648 to 2147483647
result, err := strconv.ParseInt("2000000000", 10, 32) // Valid
result, err := strconv.ParseInt("3000000000", 10, 32) // Error: out of range
The most common bitSize
values are 8, 16, 32, and 64. Using bitSize
0 is equivalent to using the platform’s int size (32 or 64 bits depending on your system).
Understanding ParseUint: Unsigned Integer Parsing
The ParseUint
function works similarly to ParseInt
but specifically handles unsigned integers, which can only represent non-negative values. This allows for a larger positive range compared to signed integers of the same bit size:
func ParseUint(s string, base int, bitSize int) (uint64, error)
Key Differences from ParseInt
The most significant difference is that ParseUint
cannot accept negative values. Any string containing a negative sign will result in an error:
package main
import (
"fmt"
"strconv"
)
func main() {
// Valid unsigned values
validValues := []string{"0", "255", "65535", "4294967295"}
for _, val := range validValues {
result, err := strconv.ParseUint(val, 10, 64)
if err != nil {
fmt.Printf("Error parsing %s: %v\n", val, err)
continue
}
fmt.Printf("ParseUint(%s, 10, 64) = %d\n", val, result)
}
// Invalid values (negative numbers)
invalidValues := []string{"-1", "-100", "-0"}
for _, val := range invalidValues {
result, err := strconv.ParseUint(val, 10, 64)
if err != nil {
fmt.Printf("Error parsing %s: %v\n", val, err)
}
}
}
Base Usage with ParseUint
ParseUint
supports the same base options as ParseInt
, including automatic detection with base 0:
// Different bases with unsigned parsing
examples := map[string]int{
"11111111": 2, // Binary: 255
"377": 8, // Octal: 255
"255": 10, // Decimal: 255
"FF": 16, // Hexadecimal: 255
}
for str, base := range examples {
result, err := strconv.ParseUint(str, base, 64)
if err != nil {
fmt.Printf("Error parsing %s (base %d): %v\n", str, base, err)
continue
}
fmt.Printf("ParseUint(%s, %d, 64) = %d\n", str, base, result)
}
// Auto-detection examples
autoDetectExamples := []string{
"255", // Decimal
"0377", // Octal
"0xFF", // Hexadecimal
"0b11111111", // Binary
}
for _, example := range autoDetectExamples {
result, err := strconv.ParseUint(example, 0, 64)
if err != nil {
fmt.Printf("Error parsing %s: %v\n", example, err)
continue
}
fmt.Printf("ParseUint(%s, 0, 64) = %d\n", example, result)
}
BitSize Ranges for Unsigned Integers
The bitSize
parameter for ParseUint
defines different maximum values compared to signed integers:
// 8-bit unsigned: 0 to 255
result, err := strconv.ParseUint("200", 10, 8) // Valid
result, err := strconv.ParseUint("300", 10, 8) // Error: out of range
// 16-bit unsigned: 0 to 65535
result, err := strconv.ParseUint("50000", 10, 16) // Valid
result, err := strconv.ParseUint("70000", 10, 16) // Error: out of range
// 32-bit unsigned: 0 to 4294967295
result, err := strconv.ParseUint("4000000000", 10, 32) // Valid
result, err := strconv.ParseUint("5000000000", 10, 32) // Error: out of range
// 64-bit unsigned: 0 to 18446744073709551615
result, err := strconv.ParseUint("18446744073709551615", 10, 64) // Valid (max uint64)
Practical Applications
ParseUint
is particularly useful when working with:
- Port numbers (0-65535)
- RGB color values (0-255 per channel)
- Array indices and sizes
- Timestamps and durations
- Network addresses and masks
// Parsing RGB color components
rgbComponents := []string{"255", "128", "64"}
var rgb [3]uint8
for i, component := range rgbComponents {
val, err := strconv.ParseUint(component, 10, 8)
if err != nil {
fmt.Printf("Invalid RGB component: %s\n", component)
continue
}
rgb[i] = uint8(val)
}
fmt.Printf("RGB: (%d, %d, %d)\n", rgb[0], rgb[1], rgb[2])
The combination of base flexibility and bit size validation makes ParseUint
a powerful tool for handling various unsigned integer parsing scenarios in your Go applications.
BitSize Parameter Deep Dive and Practical Limits
Understanding the bitSize
parameter is crucial for writing robust parsing code. This parameter serves as both a validator and a documentation tool, ensuring that parsed values fit within the expected range for your specific use case.
BitSize Validation Mechanics
The bitSize
parameter doesn’t affect the return type but validates the parsed value against the specified bit range. Here’s how it works internally:
package main
import (
"fmt"
"strconv"
)
func demonstrateBitSizeValidation() {
testValue := "1000"
// Different bitSize values with the same input
bitSizes := []int{8, 16, 32, 64}
for _, bitSize := range bitSizes {
result, err := strconv.ParseInt(testValue, 10, bitSize)
if err != nil {
fmt.Printf("bitSize %d: Error - %v\n", bitSize, err)
} else {
fmt.Printf("bitSize %d: Success - %d\n", bitSize, result)
}
}
// Testing edge cases
edgeCases := map[string]int{
"127": 8, // Maximum int8
"128": 8, // Exceeds int8
"-128": 8, // Minimum int8
"-129": 8, // Below int8 minimum
}
for value, bitSize := range edgeCases {
result, err := strconv.ParseInt(value, 10, bitSize)
fmt.Printf("ParseInt(%s, 10, %d): ", value, bitSize)
if err != nil {
fmt.Printf("Error - %v\n", err)
} else {
fmt.Printf("Success - %d\n", result)
}
}
}
Complete BitSize Reference Table
Here’s a comprehensive reference for all standard bit sizes:
// Signed integer ranges (ParseInt)
// bitSize 8: -128 to 127
// bitSize 16: -32768 to 32767
// bitSize 32: -2147483648 to 2147483647
// bitSize 64: -9223372036854775808 to 9223372036854775807
// Unsigned integer ranges (ParseUint)
// bitSize 8: 0 to 255
// bitSize 16: 0 to 65535
// bitSize 32: 0 to 4294967295
// bitSize 64: 0 to 18446744073709551615
func demonstrateAllRanges() {
// Testing maximum values for each bit size
signedMaxValues := map[int]string{
8: "127",
16: "32767",
32: "2147483647",
64: "9223372036854775807",
}
unsignedMaxValues := map[int]string{
8: "255",
16: "65535",
32: "4294967295",
64: "18446744073709551615",
}
fmt.Println("Signed integer maximum values:")
for bitSize, maxVal := range signedMaxValues {
result, err := strconv.ParseInt(maxVal, 10, bitSize)
if err != nil {
fmt.Printf("bitSize %d: Unexpected error - %v\n", bitSize, err)
} else {
fmt.Printf("bitSize %d: %d\n", bitSize, result)
}
}
fmt.Println("\nUnsigned integer maximum values:")
for bitSize, maxVal := range unsignedMaxValues {
result, err := strconv.ParseUint(maxVal, 10, bitSize)
if err != nil {
fmt.Printf("bitSize %d: Unexpected error - %v\n", bitSize, err)
} else {
fmt.Printf("bitSize %d: %d\n", bitSize, result)
}
}
}
Special BitSize Cases
There are two special cases worth understanding:
BitSize 0: Uses the platform’s native int size (32-bit on 32-bit systems, 64-bit on 64-bit systems)
// On a 64-bit system
result, err := strconv.ParseInt("2147483648", 10, 0) // Usually succeeds
// On a 32-bit system
result, err := strconv.ParseInt("2147483648", 10, 0) // Would fail
// To write portable code, be explicit:
result, err := strconv.ParseInt("2147483648", 10, 64) // Always consistent
BitSize > 64: The functions will accept bitSize values greater than 64, but they behave the same as bitSize 64:
result, err := strconv.ParseInt("100", 10, 128) // Treated as bitSize 64
Practical Error Handling Patterns
When working with bitSize validation, you’ll often need to handle range errors specifically:
func parseWithFallback(s string, preferredBitSize int) (int64, error) {
// Try preferred bit size first
result, err := strconv.ParseInt(s, 10, preferredBitSize)
if err != nil {
// Check if it's a range error
if numErr, ok := err.(*strconv.NumError); ok {
if numErr.Err == strconv.ErrRange {
fmt.Printf("Value %s exceeds %d-bit range, trying 64-bit\n", s, preferredBitSize)
return strconv.ParseInt(s, 10, 64)
}
}
return 0, err
}
return result, nil
}
// Usage example
values := []string{"100", "40000", "3000000000"}
for _, val := range values {
result, err := parseWithFallback(val, 16)
if err != nil {
fmt.Printf("Failed to parse %s: %v\n", val, err)
} else {
fmt.Printf("Parsed %s as %d\n", val, result)
}
}
Performance Considerations
The bitSize parameter has minimal performance impact since validation occurs after parsing. However, choosing the right bitSize can help catch data corruption or invalid inputs early:
// Good: Explicit about expected range
func parsePort(s string) (uint16, error) {
val, err := strconv.ParseUint(s, 10, 16)
if err != nil {
return 0, fmt.Errorf("invalid port number: %w", err)
}
return uint16(val), nil
}
// Less ideal: No range validation
func parsePortWeak(s string) (uint16, error) {
val, err := strconv.ParseUint(s, 10, 64)
if err != nil {
return 0, err
}
return uint16(val), nil // Could truncate unexpectedly
}
Understanding bitSize limits helps you write more predictable and maintainable parsing code, especially when dealing with data that needs to fit specific constraints like database column types or network protocol fields.