-1

I use some anonymous functions in my code and I’m trying to understand the difference (if there is one) between these two code snipets being called in a function:

defer func(s *Service, ID string) {
    err := s.Deprovision(ID)
    if err != nil {
        s.logger.Error(err)
    }
}(service, identifier)


defer func() {
    err := service.Deprovision(identifier)
    if err != nil {
        service.logger.Error(err)
    }
}()

One directly uses variables available in the parent func and the other essentially declares these variables in the anonymous function and then specifies the parent variables to pass in.

Does this make a difference? If so, when do I need to be mindful of this?

  • 2
    Whether these two functions will do the same thing or not depends on what the surrounding function is doing with the two variables. In other words, you need to be mindful of the difference only if the two variables are changed by the surrounding function between the `defer` statement and the surrounding function's body end (or last possible exit). – mkopriva Aug 08 '23 at 16:44
  • 1
    You need to look at the scope of `service`, and `identifier` -- do they need to be scoped to the anonymous function or not? – JimB Aug 08 '23 at 16:56
  • 2
    Basically the first deferred function is given _the values of the variables_. The second deferred function _"captures" the variables themselves_. If the variables' values are modified after the defer statement then the second deferred function will see the modified values, but the first deferred function will not, it will see only the values passed to it at `defer`. (by modification here I mean re-assignment of the variables, for example if you modify only a field of the `*Service` instance, then that modification would be visible to the first `defer` as well) – mkopriva Aug 08 '23 at 17:02
  • 1
    Thanks everyone. I understand now. That’s somewhat what I suspected but I could not find a definitive answer anywhere. Much appreciated! – Greg T. Wallace Aug 08 '23 at 18:12

1 Answers1

1

The two code snippets you provided are both using anonymous functions with defer statements. However, they differ in how they capture and use variables from the enclosing function. Let's break down the differences and discuss when you need to be mindful of them:

  1. Direct Use of Variables (First Snippet): In the first code snippet, the anonymous function directly uses the parameters s and ID from the enclosing function's scope. This means that these variables are captured by the closure and can be accessed directly within the anonymous function without being passed as arguments.

  2. Passing Variables as Arguments (Second Snippet): In the second code snippet, the anonymous function does not use the parameters from the enclosing function's scope. Instead, it captures the service and identifier variables by value from the enclosing function's scope and then uses them within the function body. This is essentially equivalent to declaring and initializing new local variables within the anonymous function.

Does this make a difference?

Yes, there is a difference between the two approaches, and the choice between them depends on your specific use case.

  • Scoping and Clarity: The first approach is more explicit and may be clearer to someone reading the code, as it shows exactly which variables are being used from the enclosing scope. This can help prevent potential mistakes and improve code readability.

  • Performance and Memory: The second approach, where variables are captured by value, can have a small impact on performance and memory usage, especially if the enclosing function's scope contains large objects. This is because each closure will have its own copy of the captured variables, which could increase memory consumption.

When to Be Mindful:

  1. Variable Changes: If the values of the variables (service and identifier in your case) change after the anonymous function is defined but before it is executed (due to loops or other control structures), the behavior of the second approach might be unexpected.

  2. Closures and Goroutines: When using goroutines in conjunction with closures, the captured variables can have a subtle impact on concurrency behavior. Make sure you understand how closures capture variables and how they can affect concurrent execution.

  3. Performance Considerations: If memory usage is a concern or if capturing by value causes performance issues, you might want to consider the first approach, which directly uses the existing variables.

In most cases, the differences between the two approaches might not be significant, but it's good to be aware of these considerations when making your choice. Choose the approach that best fits your specific use case and promotes code clarity and maintainability.

Adem_Bc
  • 48
  • 6