英文:
Why isn't my Stringer interface method getting invoked? When using fmt.Println
问题
假设我有以下代码:
package main
import "fmt"
type Car struct{
year int
make string
}
func (c *Car)String() string{
return fmt.Sprintf("{make:%s, year:%d}", c.make, c.year)
}
func main() {
myCar := Car{year:1996, make:"Toyota"}
fmt.Println(myCar)
}
当我调用fmt.Println(myCar)并且对象是一个指针时,我的String()方法会被正确调用。然而,如果对象是一个值,我的输出将使用Go内置的默认格式化,并且我的代码来格式化该对象不会被调用。
有趣的是,无论对象是基于指针还是基于值,如果我手动调用myCar.String(),它都能正常工作。
无论对象是基于值还是基于指针,我如何让对象以我想要的方式格式化在Println中使用?
我不想使用值方法来实现String,因为这意味着每次调用它时都会复制对象,这似乎不合理。而且我也不想总是手动调用.String(),因为我想让鸭子类型系统发挥作用。
英文:
Suppose I have the following code:
package main
import "fmt"
type Car struct{
year int
make string
}
func (c *Car)String() string{
return fmt.Sprintf("{make:%s, year:%d}", c.make, c.year)
}
func main() {
myCar := Car{year:1996, make:"Toyota"}
fmt.Println(myCar)
}
When I call fmt.Println(myCar) and the object in question is a pointer, my String() method gets called properly. If, however the object is a value, my output is formatted using the default formatting built into Go and my code to format the said object is not called.
The interesting thing is in either case if I call myCar.String() manually it works properly whether my object is either a pointer or value.
How can I get my object formatted the way I want no matter if the object is value-based or pointer-based when used with Println?
I don't want to use a value method for String because then that means every time it's invoked the object is copied which seams unreasonable. And I don't want to have to always manually called .String() either because I'm trying to let the duck-typing system do it's work.
答案1
得分: 81
当调用fmt.Println
时,myCar
会被隐式转换为interface{}
类型的值,正如你从函数签名中可以看到的那样。然后,fmt
包中的代码会进行type switch来确定如何打印这个值,大致如下:
switch v := v.(type) {
case string:
os.Stdout.WriteString(v)
case fmt.Stringer:
os.Stdout.WriteString(v.String())
// ...
}
然而,fmt.Stringer
的情况失败了,因为Car
没有实现String
(因为它是在*Car
上定义的)。手动调用String
是有效的,因为编译器看到String
需要一个*Car
,因此自动将myCar.String()
转换为(&myCar).String()
。对于任何涉及接口的事情,你必须手动处理。所以你要么在Car
上实现String
,要么总是传递一个指针给fmt.Println
:
fmt.Println(&myCar)
英文:
When calling fmt.Println
, myCar
is implicitly converted to a value of type interface{}
as you can see from the function signature. The code from the fmt
package then does a type switch to figure out how to print this value, looking something like this:
switch v := v.(type) {
case string:
os.Stdout.WriteString(v)
case fmt.Stringer:
os.Stdout.WriteString(v.String())
// ...
}
However, the fmt.Stringer
case fails because Car
doesn't implement String
(as it is defined on *Car
). Calling String
manually works because the compiler sees that String
needs a *Car
and thus automatically converts myCar.String()
to (&myCar).String()
. For anything regarding interfaces, you have to do it manually. So you either have to implement String
on Car
or always pass a pointer to fmt.Println
:
fmt.Println(&myCar)
答案2
得分: 27
方法
关于接收器的指针 vs. 值的规则是,值方法可以在指针和值上调用,但指针方法只能在指针上调用。这是因为指针方法可以修改接收器;在值的副本上调用它们会导致这些修改被丢弃。
因此,为了使您的 String
方法在指针和值上都能正常工作,请使用值接收器。例如,
package main
import "fmt"
type Car struct {
year int
make string
}
func (c Car) String() string {
return fmt.Sprintf("{make:%s, year:%d}", c.make, c.year)
}
func main() {
myCar := Car{year: 1996, make: "Toyota"}
fmt.Println(myCar)
fmt.Println(&myCar)
}
输出:
{make:Toyota, year:1996}
{make:Toyota, year:1996}
英文:
> Methods
>
> Pointers vs. Values
>
> The rule about pointers vs. values for receivers is that value methods
> can be invoked on pointers and values, but pointer methods can only be
> invoked on pointers. This is because pointer methods can modify the
> receiver; invoking them on a copy of the value would cause those
> modifications to be discarded.
Therefore, for your String
method to work when invoked on both pointers and values, use a value receiver. For example,
package main
import "fmt"
type Car struct {
year int
make string
}
func (c Car) String() string {
return fmt.Sprintf("{make:%s, year:%d}", c.make, c.year)
}
func main() {
myCar := Car{year: 1996, make: "Toyota"}
fmt.Println(myCar)
fmt.Println(&myCar)
}
Output:
{make:Toyota, year:1996}
{make:Toyota, year:1996}
答案3
得分: 7
package main
import "fmt"
type Car struct {
year int
make string
}
func (c *Car) String() string {
return fmt.Sprintf("{maker:%s, produced:%d}", c.make, c.year)
}
func main() {
myCar := Car{year: 1996, make: "Toyota"}
myOtherCar := &Car{year: 2013, make: "Honda"}
fmt.Println(&myCar)
fmt.Println(myOtherCar)
}
英文:
Define your fmt.Stringer on a pointer receiver:
package main
import "fmt"
type Car struct {
year int
make string
}
func (c *Car) String() string {
return fmt.Sprintf("{maker:%s, produced:%d}", c.make, c.year)
}
func main() {
myCar := Car{year: 1996, make: "Toyota"}
myOtherCar := &Car{year: 2013, make: "Honda"}
fmt.Println(&myCar)
fmt.Println(myOtherCar)
}
Output:
{maker:Toyota, produced:1996}
{maker:Honda, produced:2013}
Then, always pass a pointer to instances of Car to fmt.Println. This way a potentially expensive value copy is avoided under your control.
答案4
得分: 6
The OP further asked:
> OP: [当使用值接收器时] "这是否意味着如果我有一个大的结构体,那么每次它经过Println时都会被复制?"
以下实验证明了答案是"是的"(当使用值接收器时)。请注意,该实验中的String()
方法会增加年份,并检查这如何影响打印输出。
type Car struct {
year int
make string
}
func (c Car) String() string {
s := fmt.Sprintf("{ptr:%p, make:%s, year:%d}", c, c.make, c.year)
// 增加年份以证明:c是副本还是引用?
c.year += 1
return s
}
func main() {
myCar := Car{year: 1996, make: "Toyota"}
fmt.Println(&myCar)
fmt.Println(&myCar)
fmt.Println(myCar)
fmt.Println(myCar)
}
使用值接收器(c Car)
,以下打印输出显示Go会对Car
结构体进行值复制,因为年份的增加在后续对Println
的调用中没有反映出来:
<!-- language: lang-none -->
{ptr:%!p(main.Car={1996 Toyota}), make:Toyota, year:1996}
{ptr:%!p(main.Car={1996 Toyota}), make:Toyota, year:1996}
{ptr:%!p(main.Car={1996 Toyota}), make:Toyota, year:1996}
{ptr:%!p(main.Car={1996 Toyota}), make:Toyota, year:1996}
将接收器更改为指针(c *Car)
,但不更改其他内容,打印输出变为:
<!-- language: lang-none -->
{ptr:0xc420094020, make:Toyota, year:1996}
{ptr:0xc420094020, make:Toyota, year:1997}
{1998 Toyota}
{1998 Toyota}
即使在调用Println
时提供指针作为参数,即fmt.Println(&myCar)
,当使用值接收器时,Go仍然会对Car
结构体进行值复制。OP希望避免进行值复制,我的结论是只有指针接收器才能满足这个要求。
英文:
The OP further asked:
> OP: [when a value receiver is used] "Does this basically mean that if I have a large struct, then every time it goes through Println it will be copied?"
The following experiment is evidence that the answer is "yes" (when a value receiver is used). Note that the String()
method increments the year in this experiment, and check how this affects the printed output.
type Car struct {
year int
make string
}
func (c Car) String() string {
s := fmt.Sprintf("{ptr:%p, make:%s, year:%d}", c, c.make, c.year)
// increment the year to prove: is c a copy or a reference?
c.year += 1
return s
}
func main() {
myCar := Car{year: 1996, make: "Toyota"}
fmt.Println(&myCar)
fmt.Println(&myCar)
fmt.Println(myCar)
fmt.Println(myCar)
}
With a value receiver (c Car)
, the following printed output shows that Go makes value copies of the Car
struct, because the year increment is not reflected in subsequent calls to Println
:
<!-- language: lang-none -->
{ptr:%!p(main.Car={1996 Toyota}), make:Toyota, year:1996}
{ptr:%!p(main.Car={1996 Toyota}), make:Toyota, year:1996}
{ptr:%!p(main.Car={1996 Toyota}), make:Toyota, year:1996}
{ptr:%!p(main.Car={1996 Toyota}), make:Toyota, year:1996}
Changing the receiver to a pointer (c *Car)
but changing nothing else, the printed output becomes:
<!-- language: lang-none -->
{ptr:0xc420094020, make:Toyota, year:1996}
{ptr:0xc420094020, make:Toyota, year:1997}
{1998 Toyota}
{1998 Toyota}
Even when a pointer is provided as argument in a call to Println
, i.e. fmt.Println(&myCar)
, Go still makes a value copy of the Car
struct when a value receiver is used. The OP wants to avoid value copies being made, and my conclusion is that only pointer receivers satisfy that requirement.
答案5
得分: 0
只与fmt
的实现相关,而不是与Go相关。
由于spew
以这种方式打印内容,因此会调用带有指针接收器的String()方法:
...
switch iface := v.Interface().(type) {
case fmt.Stringer:
defer catchPanic(w, v)
if cs.ContinueOnMethod {
w.Write(openParenBytes)
w.Write([]byte(iface.String()))
w.Write(closeParenBytes)
w.Write(spaceBytes)
return false
}
w.Write([]byte(iface.String()))
return true
}
英文:
It's only related to implementation of fmt
instead of Go however.
String() with pointer receiver would be invoked by https://github.com/davecgh/go-spew since spew
print things in this way:
v = reflect.ValueOf(arg)
...
switch iface := v.Interface().(type) {
case fmt.Stringer:
defer catchPanic(w, v)
if cs.ContinueOnMethod {
w.Write(openParenBytes)
w.Write([]byte(iface.String()))
w.Write(closeParenBytes)
w.Write(spaceBytes)
return false
}
w.Write([]byte(iface.String()))
return true
}
答案6
得分: -2
通常来说,最好避免通过静态初始化器给变量赋值,即
f := Foo{bar:1,baz:"2"}
这是因为如果你忘记通过&foo
将foo
作为指针传递,或者决定使用值接收器,就会出现你所说的问题,你最终会创建很多值的克隆。
相反,尽量默认将指针赋给静态初始化器,即
f := &Foo{bar:1,baz:"2"}
这样,f
将始终是一个指针,只有在你明确使用值接收器时才会得到一个值的副本。
(当然,有时你确实想要存储静态初始化器的值,但这些情况应该是边缘案例)
英文:
Generally speaking, it's best to avoid assigning values to variables via static initializers, i.e.
f := Foo{bar:1,baz:"2"}
This is because it can create exactly the complaint you're talking about, if you forget to pass foo
as a pointer via &foo
or you decide to use value receivers you end up making a lot of clones of your values.
Instead, try to assign pointers to static initializers by default, i.e.
f := &Foo{bar:1,baz:"2"}
This way f
will always be a pointer and the only time you'll get a value copy is if you explicitly use value receivers.
(There are of course times when you want to store the value from a static initializer, but those should be edge cases)
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论