W Go słowo kluczowe defer opóźnia wykonanie funkcji do zakończenia otaczającej funkcji. Jest to wygodne do zwalniania zasobów i finalizacji. Jednakże, użycie defer w połączeniu z metodami przyjmującymi wskaźniki (*T) lub wartości (T) ma nieoczywiste niuanse.
Z powodów bezpieczeństwa kodu Go został pierwotnie zaprojektowany, aby zapewnić automatyczne wywołanie kodu czyszczącego, niezależnie od miejsca wyjścia z funkcji. Jednak używany typ odbiorcy (wskaźnik lub wartość) decyduje o tym, czy obiekt metody, wywołującej defer, jest kopiowany, czy oryginalny obiekt jest modyfikowany.
Jeśli wywołujemy metodę z odbiorcą-wartością (T), do defer kopiowana jest wartość struktury. Jeśli wywoływana jest metoda z odbiorcą-wskaźnikiem (*T), metoda działa na oryginalnym obiekcie. W rezultacie zmiany danych w metodzie defer po wartości będą niezauważalne, a po wskaźniku wpłyną na zewnętrzną strukturę. To prowadzi do trudnych do wychwycenia błędów, szczególnie podczas próby zmiany stanu obiektu przez defer.
Podczas projektowania metod i używania defer należy zawsze świadomie wybierać typ odbiorcy. Zmiany, które powinny przetrwać do końca funkcji, należy wprowadzać przez wskaźnik.
Przykład kodu:
package main import "fmt" type Counter struct { Value int } func (c Counter) IncValue() { // metoda po wartości defer func() { c.Value++ // zwiększy się tylko kopia fmt.Println("[Value receiver defer] Value:", c.Value) }() } func (c *Counter) IncPointer() { // metoda po wskaźniku defer func() { c.Value++ // zwiększy się oryginał fmt.Println("[Pointer receiver defer] Value:", c.Value) }() } func main() { c := Counter{Value: 10} c.IncValue() // Value pozostanie 10 c.IncPointer() // Value stanie się 11 fmt.Println("Original Value after calls:", c.Value) }
Kluczowe cechy:
1. Czy zewnętrzny obiekt zmieni się, jeśli w defer wywołamy metodę, przyjmującą odbiorcę po wartości?
Nie, w defer oryginalny obiekt nie zmieni się, ponieważ metoda działa na kopii struktury.
2. Czy można polegać na defer dla gwarantowanej zmiany stanu struktury przez metodę po wartości?
Nie. Należy używać metod z wskaźnikiem, jeśli chcesz odzwierciedlić zmiany w oryginalnym obiekcie.
3. Co się stanie, jeśli po deklaracji defer zmienić pola struktury?
Działania zależą od sposobu przekazywania odbiorcy: jeśli metoda/funkcja otrzymuje kopię, zmiany po deklaracji defer nie będą zauważalne w odłożonej funkcji.
func (c Counter) Demo() { defer fmt.Println("defer c.Value:", c.Value) // przechwyci bieżącą wartość c.Value = 42 // nie wpłynie na defer }
Pisaliśmy funkcję zamykania połączenia, aktualizując stan przez defer z metodą po wartości. Okazało się, że flaga zamknięcia nie jest aktualizowana.
Zalety:
Wady:
Używaliśmy metod z odbiorcą-wskaźnikiem do finalizacji, zmieniając oryginalny obiekt.
Zalety:
Wady: