go

En Go, ¿por qué no se invoca mi método de interfaz Stringer? Cuando se usa fmt.Println



(4)

Métodos

Punteros vs. Valores

La regla sobre punteros vs. valores para receptores es que los métodos de valor pueden invocarse en punteros y valores, pero los métodos de puntero solo se pueden invocar en punteros. Esto se debe a que los métodos de puntero pueden modificar el receptor; invocarlos en una copia del valor provocaría que esas modificaciones sean descartadas.

Por lo tanto, para que su método String funcione cuando se invoca tanto en punteros como en valores, use un receptor de valores. Por ejemplo,

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) }

Salida:

{make:Toyota, year:1996} {make:Toyota, year:1996}

Supongamos que tengo el siguiente código:

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) }

Cuando llamo a fmt.Println (myCar) y el objeto en cuestión es un puntero, mi método String () se llama correctamente. Sin embargo, si el objeto es un valor, mi salida se formatea utilizando el formato predeterminado integrado en Go y mi código para formatear dicho objeto no se llama.

Lo interesante es que, en cualquier caso, si llamo a myCar.String () manualmente, funciona correctamente si mi objeto es un puntero o un valor.

¿Cómo puedo formatear mi objeto de la manera que quiero, sin importar si el objeto está basado en valores o en un puntero cuando se usa con Println?

No quiero usar un método de valor para String porque eso significa que cada vez que se invoca, el objeto se copia, lo que resulta irrazonable. Y no quiero tener que llamar siempre manualmente .String () ya sea porque estoy tratando de dejar que el sistema de tipado de patos haga su trabajo.

¡Gracias por adelantado!

-Ralph


Al llamar a fmt.Println , myCar se convierte implícitamente a un valor de tipo interface{} como puede ver en la firma de la función. El código del paquete fmt hace un cambio de tipo para descubrir cómo imprimir este valor, con un aspecto similar a este:

switch v := v.(type) { case string: os.Stdout.WriteString(v) case fmt.Stringer: os.Stdout.WriteString(v.String()) // ... }

Sin embargo, el caso fmt.Stringer falla porque Car no implementa String (como se define en *Car ). Calling String funciona manualmente porque el compilador ve que String necesita un *Car y, por lo tanto, convierte automáticamente myCar.String() en (&myCar).String() . Para cualquier cosa relacionada con las interfaces, debes hacerlo de forma manual. Entonces, debe implementar String on Car o siempre pasar un puntero a fmt.Println :

fmt.Println(&myCar)


Defina su fmt.Stringer en un receptor de puntero:

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) }

Playground

Salida:

{maker:Toyota, produced:1996} {maker:Honda, produced:2013}

Luego, siempre pase un puntero a las instancias de Car para fmt.Println. De esta forma, se evita una copia de valor potencialmente costosa bajo su control.


En general, es mejor evitar asignar valores a variables a través de inicializadores estáticos, es decir,

f := Foo{bar:1,baz:"2"}

Esto se debe a que puede crear exactamente la queja de la que está hablando, si olvida pasar foo como un puntero a través de &foo o si decide usar receptores de valor, terminará haciendo muchos clones de sus valores.

En su lugar, intente asignar punteros a inicializadores estáticos de forma predeterminada , es decir,

f := &Foo{bar:1,baz:"2"}

De esta manera, f siempre será un puntero y la única vez que obtendrás una copia de valor es si usas explícitamente receptores de valores.

(Por supuesto, hay momentos en los que desea almacenar el valor de un inicializador estático, pero esos deberían ser casos extremos)