Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

I feel like C people, out of anyone, should respect the code gen wins of defer. Why would you rely on runtime conditional branches for everything you want cleaned up, when you can statically determine what cleanup functions need to be called?

In any case, the biggest advantage IMO is that resource acquisition and cleanup are next to each other. My brain understands the code better when I see "this is how the resource is acquired, this is how the resource will be freed later" next to each other, than when it sees "this is how this resource is acquired" on its own or "this is how the resource is freed" on its own. When writing, I can write the acquisition and the free at the same time in the same place, making me very unlikely to forget to free something.

 help



Defer might be better than nothing, but it's still a poor solution. An obvious example of a better, structural solution is C#'s `using` blocks.

    using (var resource = acquire()) {

    } // implicit resource.Dispose();
While we don't have the same simplicity in C because we don't use this "disposable" pattern, we could still perhaps learn something from syntax and use a secondary block to have scoped defers. Something like:

    using (auto resource = acquire(); free(resource)) {

    } // free(resource) call inserted here.
That's no so different to how a `for` block works:

    for (auto it = 0; it < count; it++) {

    } // automatically inserts it++; it < count; and conditional branch after secondary block of for loop.
A trivial "hack" for this kind of scoped defer would be to just wrap a for loop in a macro:

    #define using(var, acquire, release) \
        auto var = (acquire); \
        for (bool var##_once = true; var##_once; var##_once = false, (release))

    using (foo, malloc(szfoo), free(foo)) {
        using (bar, malloc(szbar), free(bar)) {
            ...
        } // free(bar) gets called here.
    } // free(foo) gets called here.

That is a different approach, but I don't think you've demonstrated why it's better. Seems like that approach forces you to introduce a new scope for every resource, which might otherwise be unnecessary:

    using (var resource1 = acquire() {
        using (var resource2 = acquire()) {
            using (var resource3 = acquire()) {
                // use resources here..
            }
        }
    }
Compared to:

    var resource1 = acquire();
    defer { release(resource1); }
    var resource2 = acquire();
    defer { release(resource2); }
    var resource3 = acquire();
    defer { release(resource3); }
    // use resources here
Of course if you want the extra scopes (for whatever reason), you can still do that with defer, you're just not forced to.

While the macro version doesn't permit this, if it were built-in syntax (as in C#) we can write something like:

    using (auto res1 = acquire1(); free(res1))
    using (auto res2 = acquire2(); free(res2))
    using (auto res3 = acquire3(); free(res3)) 
    {
        // use resources here
    } 
    // free(res3); free(res2); free(res1); called in that order.
The argument for this approach is it is structural. `defer` statements are not structural control flow: They're `goto` or `comefrom` in disguise.

---

Even if we didn't want to introduce new scope, we could have something like F#'s `use`[1], which makes the resource available until the end of the scope it was introduced.

    use auto res1 = acquire1() defer { free(res1); };
    use auto res2 = acquire2() defer { free(res2); };
    use auto res3 = acquire3() defer { free(res3); };
    // use resources here

In either case (using or use-defer), the acquisition and release are coupled together in the code. With `defer` statements they're scattered as separate statements. The main argument for `defer` is to keep the acquisition and release of resources together in code, but defer statements fail at doing that.

[1]:https://learn.microsoft.com/en-us/dotnet/fsharp/language-ref...


>"this is how the resource is acquired, this is how the resource will be freed later"

Lovely fairy tale. Now can you tell me how you love to scroll back and examine all the defer blocks within a scope when it ends to understand what happens at that point?


I don't typically do that. In 99.999% of cases, 'defer free(something)' was done because it's the correct thing to do at every exit point, so I don't need to think about it at the end of the block.

If you only ever work in your own code bases, sure.



Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: