4

Golang noob question: Why can I not use the "%+v" flag for a struct in the String() implementation method?

I have a struct where I want to implement a String() method for pretty print. I like the answer given here, but I don't like to type, so I'm trying to modify it to return a string using the "%+v" format flag for structs. from the fmt doc:

%v the value in a default format when printing structs, the plus flag (%+v) adds field names

This works fine if I simply call it with fmt.Printf("%+v", color), but if I try to put the + flag in the String() implementation, I get a stack overflow (my first chance to ask a "stack overflow" question on stackoverflow.com ;) )

I'm sure I'm not understanding a pointer reference here, or there is some recursion. I doubt this noob found my first Golang bug, so can someone please explain?

see go play demonstration here https://play.golang.org/p/13_qI8Iwwa

2 Answers 2

9

See Package fmt Docs:

Except when printed using the verbs %T and %p, special formatting considerations apply for operands that implement certain interfaces. In order of application:

  1. If an operand implements method String() string, that method will be invoked to convert the object to a string, which will then be formatted as required by the verb (if any).

To avoid recursion in cases such as

type X string 
func (x X) String() string { return Sprintf("<%s>", x) }

convert the value before recurring:

func (x X) String() string { return Sprintf("<%s>", string(x)) }

Infinite recursion can also be triggered by self-referential data structures, such as a slice that contains itself as an element, if that type has a String method. Such pathologies are rare, however, and the package does not protect against them.


Inside:

func (c Color) String() string {
    // THIS CAUSES STACK OVERFLOW
    return fmt.Sprint(c)
}

The call to

fmt.Sprint(c)

or fmt.Println(c) which calls func (c Color) String() string again recursively causes overflow: try it on The Go Playground


Also this works fine: https://play.golang.org/p/NYLtrxUeiA

Sign up to request clarification or add additional context in comments.

1 Comment

Thanks @amd. Your initial answer pointed out the obvious recursion... But using the new string (string(x)) loses the struct reference, so there's still no way to use the "+" flag... however, your solution in your playground to use the "#" flag in the String() method uses Go's "syntax representation", gives me the labels, and avoids the recursion. Thanks!
2

You can get around the infinite recursion by redefining your type Color as a new local type (e.g. type _Color Color) and then applying Sprintf to the converted value _Color(color), since that cannot recursively call your String() function.

func (color Color) String() string {
    type _Color Color
    return fmt.Sprintf("%+v", _Color(color))
}

Note: don't use type aliases (e.g. type _Color = Color) because that doesn't introduce a new type, but just introduces a new name for the old type and you're back to that infinite recursion problem.

EDIT: I updated your example accordingly, so you can check that it works: https://go.dev/play/p/Cb1jQTcajc4

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.