ProgramaciónDesarrollador Backend

Explique las características del trabajo de los parámetros de función deferred en Go: ¿cuándo y cómo se calculan los parámetros para defer, y en qué se diferencia esto de la llamada a la función en el momento de la activación de defer?

Supere entrevistas con el asistente de IA Hintsage

Respuesta.

Defer es un mecanismo único de Go que permite ejecutar una función después de que finalice la función actual, incluso si se produce un panic. Históricamente, esto es análogo a las construcciones on-exit, pero en Go se implementa como una pila de llamadas deferred. Un matiz importante es que los parámetros de la función deferred se calculan en el momento de la declaración de defer, ¡no en el momento de su real llamada!

Problema — comportamiento no obvio: se podría esperar que los parámetros se pasen al activarse el defer, pero no es así. Esto a menudo conduce a errores al trabajar con variables mutables o externas.

Solución — tener siempre en cuenta que los parámetros se calculan de inmediato y que el efecto de la llamada deferred sucede después.

Ejemplo de código:

func f() { x := 5 defer fmt.Println(x) x = 10 } // Imprimirá: 5, y no 10

Características clave:

  • Los valores de los parámetros para funciones deferred se calculan en el momento de la declaración de defer.
  • La función deferred siempre se ejecuta incluso en caso de panic (si el panic no se captura antes).
  • Si en deferred se declara una función anónima — se puede acceder a los valores actuales de las variables.

Preguntas capciosas.

¿Qué imprimirá el siguiente código? ¿Por qué?

func main() { i := 0 defer fmt.Println(i) i = 1 }

Respuesta: imprimirá 0. El argumento de la función fmt.Println se guarda inmediatamente al declarar el defer.

¿Afacta el cambio de la variable después de declarar el defer a la transmisión de su valor a la función?

No, no afecta — el cálculo ocurre al declarar el defer:

defer fmt.Println(x) // El valor de x se guarda ahora, no después

¿Se puede hacer un defer para imprimir el último estado de la variable?

Sí, mediante una función anónima (closure):

defer func() { fmt.Println(x) }() // capturará el valor actual de x en el momento de la llamada deferred

Errores típicos y anti-patrones

  • Esperar que el parámetro esté actualizado en el momento de la llamada deferred.
  • Usar defer con parámetros junto con variables mutables.
  • Pilas de defer enredadas sin una descripción clara de las dependencias.

Ejemplo de la vida real

Caso negativo

El código se llama así:

var f *os.File // ... defer f.Close()

Pero f se asigna más tarde, ¡por lo que se produce un panic al llamar a un puntero nil!

Ventajas:

  • Notación corta, si la variable ya está inicializada.

Desventajas:

  • Si f == nil — panic.

Caso positivo

Envolver la acción de limpieza a través de una función deferred anónima con una verificación:

defer func() { if f != nil { f.Close() } }()

Ventajas:

  • Seguridad y ausencia de panics.

Desventajas:

  • Notación más larga y "ruidosa".