Fix

"cannot use X (untyped string constant) as int value"

TITLE: Fix: "cannot use X (untyped string constant) as int value"

The string looks like a number, but Go disagrees

You're building a server. You read the port from a configuration file. The file says "8080". You want to pass that to net.Listen. You write port := int("8080") and hit save. The editor turns red. The compiler rejects the build with cannot use "8080" (untyped string constant) as int value in assignment. You stared at the code. The string looks like a number. Why does Go refuse to treat it as one?

Text is not a number

Go separates text from numbers at the language level. A string literal like "123" is a sequence of characters. The compiler sees three bytes: 1, 2, 3. It does not see the mathematical value one hundred twenty-three. The int() function performs a type conversion. Conversions change how the compiler interprets an existing value. They do not transform data. You can convert an int32 to an int because both represent numbers. You cannot convert a string to an int because the underlying data is text, not a number. To get a number from text, you need to parse. Parsing reads the characters, validates them, and constructs a new integer value.

Think of conversion like casting metal. If you have a block of steel, you can cast it into a new shape. If you have a blueprint of a block, you cannot cast the blueprint. You need to build the block first. int() is the mold. strconv.Atoi is the factory that builds the block from the blueprint.

Conversions reshape data. Parsers build data.

Minimal example

The standard library provides strconv.Atoi to convert a base-10 numeric string to an int. The function returns two values: the integer and an error. You must handle the error. The if err != nil pattern is verbose by design. The community accepts the boilerplate because it makes the failure path visible. Ignoring the error hides bugs where the input contains garbage.

package main

import (
	"fmt"
	"strconv"
)

// ParsePort converts a string representation of a port to an integer.
// It demonstrates the standard pattern for parsing numeric text.
func ParsePort(s string) int {
	// strconv.Atoi parses the string and returns an int and an error.
	// This is the standard way to convert numeric text to an integer.
	n, err := strconv.Atoi(s)
	if err != nil {
		// Handle the error. In real code, you might return the error or log it.
		fmt.Println("Failed to parse port:", err)
		return 0
	}
	return n
}

func main() {
	// This causes a compile error: cannot use "8080" (untyped string constant) as int value.
	// port := int("8080")

	// Use strconv.Atoi to parse the string.
	port := ParsePort("8080")
	fmt.Println("Port:", port)
}

Trust gofmt. The indentation in the examples follows the standard tool output. Most editors run it on save. Don't argue about formatting; let the tool decide.

What happens at compile time and runtime

When the compiler encounters int("8080"), it checks the conversion rules. Go allows converting an untyped constant to a type if the constant's value fits in that type. The catch is the constant must be of a compatible kind. An untyped integer constant like 42 can convert to int, float64, or byte. An untyped string constant like "42" can only convert to string or []byte. The compiler rejects the code because a string constant cannot convert to an integer type. The error message cannot use "8080" (untyped string constant) as int value tells you exactly what happened. The compiler identified the source as a string constant and the target as an int value. No conversion exists between them.

At runtime, strconv.Atoi takes the string, walks through each character, checks that every character is a digit, and builds the integer mathematically. If it finds a letter or symbol, it stops and returns an error. This validation is why parsing is safer than casting. The parser guarantees the result is a valid number derived from the input.

Realistic example: parsing user input

User input is always text. HTTP query parameters, JSON fields, and command-line arguments arrive as strings. You must parse them before using them as numbers. The example below shows a handler that reads a limit parameter, parses it, and validates the range.

package main

import (
	"fmt"
	"net/http"
	"strconv"
)

// HandleConfig reads a numeric setting from the request query.
// It demonstrates parsing user input safely and validating the result.
func HandleConfig(w http.ResponseWriter, r *http.Request) {
	// Get the raw string from the query parameter.
	// Query parameters are always strings.
	rawLimit := r.URL.Query().Get("limit")

	// Parse the string to an integer.
	// Atoi handles base-10 integers.
	limit, err := strconv.Atoi(rawLimit)
	if err != nil {
		// Return a 400 error if the user sent garbage.
		http.Error(w, "Invalid limit parameter", http.StatusBadRequest)
		return
	}

	// Validate the range.
	if limit <= 0 || limit > 100 {
		http.Error(w, "Limit must be between 1 and 100", http.StatusBadRequest)
		return
	}

	fmt.Fprintf(w, "Processing limit: %d", limit)
}

func main() {
	http.HandleFunc("/config", HandleConfig)
	fmt.Println("Server starting on :8080")
	// In production, handle the error from ListenAndServe.
	_ = http.ListenAndServe(":8080", nil)
}

User input is text. Parse it, don't cast it.

The untyped constant trap

Go has untyped constants. This feature causes confusion for developers coming from dynamic languages. Consider these two declarations:

var x = 123
var y = "123"

Both compile. The compiler infers the type of x as int and y as string. The literal 123 is an untyped integer constant. The literal "123" is an untyped string constant. Untyped constants can adapt to the type of the variable they are assigned to, provided the value is representable.

The error appears when you force a type mismatch:

var z int = "123"

The compiler rejects this with cannot use "123" (untyped string constant) as int value in variable declaration. The untyped string constant cannot convert to an integer type. The type of the constant is string, not number. The compiler does not look inside the quotes to guess your intent. It enforces the type boundary.

This distinction matters when you copy values from configuration files or JSON. If you accidentally include quotes around a number in your source code, you get the same error. Check your literals. A number literal has no quotes. A string literal has quotes.

Pitfalls and alternatives

The strconv package offers more than Atoi. Choose the right function for your data.

strconv.Atoi is a convenience wrapper around strconv.ParseInt. It assumes base 10 and returns an int. If you need to parse hexadecimal, octal, or binary strings, Atoi will fail. Use strconv.ParseInt with the appropriate base. For example, strconv.ParseInt("0xFF", 16, 64) parses a hex string.

strconv.ParseInt returns an int64. This is safer for arithmetic because int size varies by platform. On 32-bit systems, int is 32 bits. A large numeric string might overflow an int but fit in an int64. If you are processing financial data or large IDs, prefer ParseInt with bit size 64.

Sometimes the error comes from a simple typo. You meant to write count := 123 but typed count := "123". The compiler error is identical. Scan your code for stray quotes. This mistake is common when developers copy values from documentation or JSON snippets without stripping the delimiters.

The compiler error is your friend. It stops you from shipping broken math.

When to use what

Use strconv.Atoi when you need to convert a base-10 numeric string to an int and the default bit size of your platform is sufficient.

Use strconv.ParseInt when you need to handle non-decimal bases, control the bit size, or require a fixed int64 return type for arithmetic safety.

Use int() when you are converting between numeric types like int32 to int, or converting an untyped integer constant to a specific type.

Use a raw number literal without quotes when the value is known at compile time and does not come from external input.

Where to go next