Represent and transform data safely
Go Interface to String: When to Assert and When to Format
Need a string from a Go interface? Learn when to use a checked type assertion, fmt.Sprint, fmt.Sprintf, or fmt.Stringer.

If an interface value holds a string and you need that string, use s, ok := value.(string). If you need text describing whatever the interface holds, use fmt.Sprint(value) or fmt.Sprintf. The distinction matters: formatting an integer as "42" does not make the integer inside the interface a string.
Retrieve a stored string with a type assertion
An interface{} variable can hold values of different dynamic types. A string assertion asks one specific question: is the value currently held by this interface a string? The comma-ok form gives you a result and a boolean, so a mismatch can be handled without a panic. By contrast, value.(string) without ok panics when the dynamic type is not string.
Here is a complete example. It first retrieves a stored string, then reuses the same interface variable for values that need different treatment:
package main
import "fmt"
type Phase int
func (Phase) String() string {
return "queued"
}
func main() {
var value interface{} = "ready"
if s, ok := value.(string); ok {
fmt.Println("stored:", s)
}
value = 42
if s, ok := value.(string); ok {
fmt.Println("stored:", s)
} else {
fmt.Println("not a string")
}
fmt.Println(fmt.Sprint(value))
fmt.Println(fmt.Sprintf("value: %v", value))
value = []int{1, 2, 3}
fmt.Println(fmt.Sprint(value))
fmt.Println(fmt.Sprintf("value: %v", value))
value = Phase(1)
fmt.Println(fmt.Sprint(value))
if stringer, ok := value.(fmt.Stringer); ok {
fmt.Println(stringer.String())
}
}
The first check finds the stored "ready". After value = 42, the second check takes the else branch: the interface now holds an int, not a string. The ok check is useful precisely when the value's dynamic type is not already guaranteed at the point of use. If your next operation requires an actual stored string, formatting the integer is not a substitute for deciding what to do with the mismatch.
Format a representation of any value
For the integer in the example, fmt.Sprint(value) produces "42", while fmt.Sprintf("value: %v", value) produces "value: 42". For the slice, the corresponding results are "[1 2 3]" and "value: [1 2 3]". Those are strings returned by the formatting calls; the interface still holds an integer or a slice until it is assigned something else. Examples of these default representations and prefixed %v output show why formatting is handy when the dynamic type may vary.
Use fmt.Sprint when the default representation alone is enough. Use fmt.Sprintf when you need to place that representation into a larger message, as in "value: %v". For a single value, fmt.Sprint(value) and fmt.Sprintf("%v", value) produce the same default representation. Neither expression checks whether value contains a string; both ask fmt to render the value they receive.
A String() method affects formatting, not the stored type
The example's named Phase type has a String() string method. That is the method required by fmt.Stringer. When value holds a Phase, fmt.Sprint(value) uses the method and returns "queued". It does not change the dynamic type from Phase to string, so value.(string) would still be the wrong assertion.
The final if makes a different assertion: it checks whether the value implements fmt.Stringer before calling String() directly. That can be useful when you specifically want the type's method. It is not a way to call String() on every interface value—an integer or slice in this example does not acquire that method just because fmt.Sprint can format it.
The choice is therefore about what your next step requires. Assert string when it needs the actual string held in the interface, and handle ok == false. Format when it needs a textual representation of the value, whatever its dynamic type may be.


Comments
No comments yet.