top | item 47088814

(no title)

sparkie | 9 days ago

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.

discuss

order

usefulcat|9 days ago

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.

sparkie|9 days ago

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...