Dlaczego moja metoda interfejsu Stringer nie jest wywoływana? Podczas stosowania fmt.Println

Załóżmy, że mam następujący kod:

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)
}
Kiedy dzwonię do fmt.Println (myCar) i obiekt jest wskaźnikiem, moja metoda String() zostanie wywołana poprawnie. Jeśli jednak obiekt jest wartością, moje wyjście jest formatowane przy użyciu domyślnego formatowania wbudowanego w Go, a mój kod do sformatowania tego obiektu nie jest wywoływany. Interesujące jest to, że w obu przypadkach zadzwonię do mycara.String () ręcznie działa poprawnie czy mój obiekt jest wskaźnikiem czy wartość.

Jak mogę sformatować obiekt tak, jak chcę, bez względu na to, czy obiekt jest oparty na wartościach czy wskaźnikach, gdy jest używany z Println?

Nie chcę używać metody value dla String, ponieważ wtedy oznacza to, że za każdym razem, gdy jest wywoływany obiekt jest kopiowany, co nie jest uzasadnione. I nie chcę zawsze ręcznie dzwonić .String() albo dlatego, że staram się, aby system pisania kaczek działał.

 55
go
Author: Flimzy, 2013-06-07

6 answers

Podczas wywoływania fmt.Println, myCar jest domyślnie przekonwertowana na wartość typu interface{}, Jak widać z podpisu funkcji. Kod z pakietu fmt następnie wykonuje przełącznik typu , aby dowiedzieć się, jak wydrukować tę wartość, wyglądając mniej więcej tak:

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

Jednak przypadek fmt.Stringer nie powiedzie się, ponieważ Car nie zaimplementuje String (zgodnie z definicją na *Car). Wywołanie String działa ręcznie, ponieważ kompilator widzi, że String potrzebuje *Car i w ten sposób automatycznie konwertuje myCar.String() do (&myCar).String(). Jeśli chodzi o interfejsy, musisz to zrobić ręcznie. Więc albo musisz zaimplementować String na Car, albo zawsze przekaż wskaźnik do fmt.Println:

fmt.Println(&myCar)
 75
Author: guelfey,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-06-07 07:32:24

Metody

Wskaźniki vs. wartości

Reguła o wskaźnikach a wartościach dla odbiorników jest taka, że metody wartości można wywoływać na wskaźnikach i wartościach, ale metody wskaźników mogą być wywoływane na wskaźnikach. Dzieje się tak dlatego, że metody wskaźników mogą modyfikować odbiorcę; wywołanie ich na kopii wartości spowoduje, że te zmiany, które należy usunąć.

Dlatego, aby twoja metoda String działała, gdy jest wywoływana zarówno na wskaźnikach, jak i wartościach, użyj odbiornika wartości. Na przykład,

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

Wyjście:

{make:Toyota, year:1996}
{make:Toyota, year:1996}
 26
Author: peterSO,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-06-07 07:58:01

Zdefiniuj swoje fmt.Stringer na odbiorniku wskaźnika:

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

Plac zabaw


Wyjście:

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

Następnie zawsze przekaż wskaźnik do instancji Car do fmt.Println. W ten sposób unika się potencjalnie kosztownej kopii wartości pod twoją kontrolą.

 7
Author: zzzz,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-06-08 10:20:42

OP zapytał dalej:

OP: [gdy używany jest odbiornik wartości] "czy to w zasadzie oznacza, że jeśli mam dużą strukturę, to za każdym razem, gdy przechodzi przez Println, zostanie skopiowana?"

Poniższy eksperyment jest dowodem na to, że odpowiedź brzmi " tak " (gdy używany jest odbiornik wartości). Zauważ, że metoda String() zwiększa rok w tym eksperymencie i sprawdź, jak wpływa to na wydruk.

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

Z wartością receiver (c Car), następujące wydrukowane wyjście pokazuje, że Go wykonuje kopie wartości struktury Car, ponieważ przyrost roku nie jest odzwierciedlany w kolejnych wywołaniach Println:

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

Zmieniając odbiornik na wskaźnik (c *Car) ale nie zmieniając nic innego, wydrukowane wyjście staje się:

{ptr:0xc420094020, make:Toyota, year:1996}
{ptr:0xc420094020, make:Toyota, year:1997}
{1998 Toyota}
{1998 Toyota}

Nawet jeśli wskaźnik jest dostarczany jako argument w wywołaniu Println, tzn. fmt.Println(&myCar), Go nadal tworzy kopię struktury Car , gdy używany jest odbiornik wartości. PO chce uniknąć wartość kopii są wykonane, a mój wniosek jest taki, że tylko odbiorniki wskaźników spełniają ten wymóg.

 5
Author: Paulo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-08-14 22:16:08

Jest to jednak związane tylko z implementacją fmt zamiast Go.

String () with pointer receiver would be called by https://github.com/davecgh/go-spew ponieważ spew Drukuj rzeczy w ten sposób:

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
    }
 0
Author: igonejack,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2019-11-19 15:12:46

Ogólnie rzecz biorąc, najlepiej unikać przypisywania wartości do zmiennych za pomocą statycznych inicjalizatorów, tj.

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

To dlatego, że może stworzyć dokładnie skargę, o której mówisz, jeśli zapomnisz przekazać foo jako wskaźnik przez &foo albo decydujesz się użyć odbiorników wartości, w końcu tworzysz wiele klonów swoich wartości.

Zamiast tego spróbuj przypisać wskaźniki do statycznych inicjalizatorów domyślnie , tzn.

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

This way f will always be a pointer a jedyny czas, kiedy otrzymasz kopię wartości, to jawne użycie odbiorników wartości.

(są oczywiście chwile, kiedy chcesz zapisać wartość z inicjalizatora statycznego, ale powinny to być przypadki brzegowe)

 -2
Author: JSS,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-12-26 12:16:52