back

by jotaen·3y ago·view on hn ↗
A somewhat related technique which I often find useful is something that’s known as “immediately invoked function expressions” (IIFE) in JavaScript. That also creates a sub-scope in place, but it let’s you return values to the enclosing scope. E.g.:

   result := func() string {
       helperVar1 := //...
       helperVar2 := //...
       return helperVar1 + helperVar2
   }()
So it’s basically an anonymous function that is invoked right away. You could achieve the same scope separation by pulling it out as named function, but sometimes I like it better to keep things closer together.
4 comments
This is really helpful in Go for `defer`. For example, if I'm manipulating files in a loop, I don't want to do:

    for _, fileName := range fileNames {
        f, err := os.Open(fileName)
        if err != nil {
            return err
        }
        defer f.Close()
        doSomething(f)
    }
... because I might run out of quota for open file handles. I want the defer to trigger at the end of the loop rather than at the end of the function, so I'll often put a closure in the loop body:

    for _, fileName := range fileNames {
        if err := func() error {
            f, err := os.Open(fileName)
            if err != nil {
                return err
            }
            defer f.Close()
            doSomething()
            return nil
        }(); err != nil {
            return err
        }
    }
That said, I don't like the ergonomics and if I'm doing a lot of file things, I'll write a `func withFile(fileName string, callback func(*os.File) error) error` function which often composes more nicely.
I use this for package globals especially:

   var pkgGlobal = func() string {
      ... 
   }()

Much better than:

   var pkgGlobal string

   func init() {
      pkgGlobal = ...
   }
Yet again the heavy initialism that looks like it came right out of a C++ standards document is just a long name for a simple thing.
Conceptually yes, but IIUC there should be worlds of difference at the compilation output level (i.e. unlike calling a function, the compiler's not obligated to set up or tear down a context every time it enters or exits a block; it can just treat the static scope semantics without having any impact on the runtime semantics).

ETA: except for go's `defer`, and off the top of my head I don't actually know if Go is obliged to run the defer immediately upon exiting the block or can choose to run it at some other point in the function.

In Go, `defer` runs at the end of the enclosing function, not the enclosing block.
> unlike calling a function, the compiler's not obligated to set up or tear down a context

I guess it depends on what you mean by "context" but the spec is very clear that a block creates scope, and the end removes scope.

https://go.dev/ref/spec#Declarations_and_scope

> The scope of a constant or variable identifier declared inside a function begins at the end of the ConstSpec or VarSpec (ShortVarDecl for short variable declarations) and ends at the end of the innermost containing block.

That shouldn’t be the case for c++ and Rust lambdas that are immediately invoked. The compiler should see through it.
Not necessarily for C++. At least for msbuild, you can use `__declspec(noinline)` on a lambda. This can be handy for complex macros that would otherwise allocate a bunch of memory.