En Go, la palabra clave defer pospone la ejecución de una función hasta que la función envolvente finaliza. Esto es conveniente para liberar recursos y finalizar. Sin embargo, el uso de defer en combinación con métodos que aceptan punteros (*T) o valores (T) tiene matices no evidentes.
Por razones de seguridad del código, Go fue diseñado inicialmente para garantizar la llamada automática de código de limpieza, independientemente del lugar de salida de la función. Sin embargo, el tipo de receptor utilizado (puntero o valor) determina si el objeto del método que llama a defer se copia o se modifica el objeto original.
Si llamamos a un método con un receptor de valor (T), el valor de la estructura se copia a defer. Si se llama a un método con un receptor de puntero (*T), el método trabaja con el objeto original. Como resultado, los cambios en los datos dentro del método defer por valor no se notarán, mientras que por puntero afectarán a la estructura externa. Esto conduce a errores difíciles de rastrear, especialmente cuando se intenta cambiar el estado de un objeto a través de defer.
Al diseñar métodos y utilizar defer, siempre se debe elegir conscientemente el tipo de receptor. Los cambios que deben perdurar hasta el final de la función deben hacerse a través de un puntero.
Ejemplo de código:
package main import "fmt" type Counter struct { Value int } func (c Counter) IncValue() { // método por valor defer func() { c.Value++ // solo se incrementa la copia fmt.Println("[Value receiver defer] Value:", c.Value) }() } func (c *Counter) IncPointer() { // método por puntero defer func() { c.Value++ // se incrementa el original fmt.Println("[Pointer receiver defer] Value:", c.Value) }() } func main() { c := Counter{Value: 10} c.IncValue() // Value seguirá siendo 10 c.IncPointer() // Value será 11 fmt.Println("Original Value after calls:", c.Value) }
Características clave:
1. ¿Cambiará el objeto externo si en defer se llama a un método que acepta un receptor por valor?
No, el objeto original no cambiará en defer, ya que el método trabaja con una copia de la estructura.
2. ¿Se puede confiar en defer para garantizar el cambio de estado de la estructura a través de un método por valor?
No. Se deben utilizar métodos con puntero si se desea reflejar cambios en el objeto original.
3. ¿Qué pasará si después de la declaración de defer se modifican los campos de la estructura?
Las acciones dependen de la forma de pasar el receptor: si el método/función recibe una copia, los cambios posteriores a la declaración de defer no se notarán en la función diferida.
func (c Counter) Demo() { defer fmt.Println("defer c.Value:", c.Value) // capturará el valor actual c.Value = 42 // no afectará a defer }
Escribimos una función para cerrar una conexión, actualizando el estado a través de defer con un método por valor. Resultó que la bandera de cierre no se actualizaba.
Pros:
Contras:
Utilizamos métodos con receptor por puntero para la finalización, modificando el objeto original.
Pros:
Contras: