结构体String()的实现在使用Sprintf的”+ “标志时导致堆栈溢出。

huangapple go评论75阅读模式
英文:

struct String() implemention causes stack overflow with Sprintf "+" flag

问题

Golang新手问题:为什么我不能在String()方法的实现中使用"%+v"标志来打印结构体?

我有一个结构体,我想实现一个String()方法来进行漂亮的打印。我喜欢这里给出的答案,但是我不喜欢打字,所以我试图修改它,以便在结构体中使用"%+v"格式标志返回一个字符串。根据fmt文档:

%v 默认格式的值 当打印结构体时,加号标志(%+v)会添加字段名

如果我简单地使用fmt.Printf("%+v", color)调用它,这样是可以的,但是如果我尝试在String()方法的实现中加入"+"标志,就会导致堆栈溢出(这是我第一次在stackoverflow.com上提出"stack overflow"问题)。

我确定我在这里没有理解指针引用,或者存在一些递归。我怀疑这个新手找到了我的第一个Golang bug,所以有人可以解释一下吗?

在这里可以看到go play的演示:https://play.golang.org/p/13_qI8Iwwa

英文:

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 结构体String()的实现在使用Sprintf的”+ “标志时导致堆栈溢出。 )

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

答案1

得分: 9

请注意,我将为您翻译以下内容:

参见Package fmt文档

除非使用%T%p这两个动词打印,否则对于实现了特定接口的操作数,会应用特殊的格式化考虑。应用顺序如下:

  1. 如果操作数实现了String() string方法,该方法将被调用以将对象转换为字符串,然后按照所需的动词格式化。

为了避免出现递归,例如:

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

在递归之前将值转换:

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

如果该类型具有String方法,还可以通过自引用数据结构(例如包含自身作为元素的切片)触发无限递归。然而,这种病态情况很少见,且该包不会对其进行保护。


在代码中:

func (c Color) String() string {
    // 这会导致堆栈溢出
    return fmt.Sprint(c)
}

fmt.Sprint(c)fmt.Println(c)的调用会再次递归调用func (c Color) String() string,导致溢出:您可以在Go Playground上尝试。


此外,这个示例可以正常工作:https://play.golang.org/p/NYLtrxUeiA

英文:

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:
>
> 5. 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

答案2

得分: 0

你可以通过将类型Color重新定义为一个新的局部类型(例如type _Color Color),然后将Sprintf应用于转换后的值_Color(color),从而避免无限递归调用你的String()函数。

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

注意:不要使用类型别名(例如type _Color = Color),因为这不会引入新的类型,而只是为旧类型引入一个新名称,这样你就会回到无限递归的问题。

编辑:我已经相应地更新了你的示例,你可以检查它是否正常工作:https://go.dev/play/p/Cb1jQTcajc4

英文:

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(&quot;%+v&quot;, _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

huangapple
  • 本文由 发表于 2016年9月28日 02:13:10
  • 转载请务必保留本文链接:https://go.coder-hub.com/39731608.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定