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

I like how Swift solved this: there's a more universal `defer { ... }` block that's executed at the end of a given scope no matter what, and after the `return` statement is evaluated if it's a function scope. As such it has multiple uses, not just for `try ... finally`.


I think Swift’s defer (https://docs.swift.org/swift-book/documentation/the-swift-pr...) was inspired by/copied from go (https://go.dev/tour/flowcontrol/12), but they may have taken it from an even earlier language that I’m not aware of.

Defer has two advantages over try…finally: firstly, it doesn’t introduce a nesting level.

Secondly, if you write

       foo
       defer revert_foo
, when scanning the code, it’s easier to verify that you didn’t forget the revert_foo part than when there are many lines between foo and the finally block that calls revert_foo.

A disadvantage is that defer breaks the “statements are logically executed in source code order” convention. I think that’s more than worth it, though.


The oldest defer-like feature I can find reference to is the ON_BLOCK_EXIT macro from this article in the December 2000 issue of the C/C++ Users Journal:

https://jacobfilipp.com/DrDobbs/articles/CUJ/2000/cexp1812/a...

A similar macro later (2006) made its way into Boost as BOOST_SCOPE_EXIT:

https://www.boost.org/doc/libs/latest/libs/scope_exit/doc/ht...

I can't say for sure whether Go's creators took inspiration from these, but it wouldn't be surprising if they did.


Yeah, it's especially handy in UI code where you can have asynchronous operations but want to have a clear start/end indication in the UI:

    busy = true
    Task {
        defer { busy = false }
        // do async stuff, possibly throwing exceptions and whatnot
    }


I'll disagree here. I'd much rather have a Python-style context manager, even if it introduces a level of indentation, rather than have the sort of munged-up control flow that `defer` introduces.


I can see your point, but that (https://book.pythontips.com/en/latest/context_managers.html) requires the object you’re using to implement __enter__ and __exit__ (or, in C#, implement IDisposable (https://learn.microsoft.com/en-us/dotnet/csharp/language-ref...), in Java, implement AutoCloseable (https://docs.oracle.com/javase/tutorial/essential/exceptions...); there likely are other languages providing something similar).

Defer is more flexible/requires less boilerplate to add callsite specific handling. For an example, see https://news.ycombinator.com/item?id=46410610


I was contemplating what it would look like to provide this with a macro in Rust, and of course someone has already done it. It's syntactic sugar for the destructor/RAII approach.

https://docs.rs/defer-rs/latest/defer_rs/


I don't know Rust but, can this `defer` evaluate after the `return` statement is evaluated like in Swift? Because in Swift you can do this:

    func atomic_get_and_inc() -> Int {
        sem.wait()
        defer {
            value += 1
            sem.signal()
        }
        return value
    }


It's easy to demonstrate that destructors run after evaluating `return` in Rust:

    struct PrintOnDrop;
    
    impl Drop for PrintOnDrop {
        fn drop(&mut self) {
            println!("dropped");
        }
    }
    
    fn main() {
        let p = PrintOnDrop;
        return println!("returning");
    }
But the idea of altering the return value of a function from within a `defer` block after a `return` is evaluated is zany. Please never do that, in any language.


EDIT: I don’t think you can actually put a return in a defer, I may have misremembered, it’s been several years. Disregard this comment chain.

It gets even better in swift, because you can put the return statement in the defer, creating a sort of named return value:

    func getInt() -> Int {
        let i: Int // declared but not
                   // defined yet!

        defer { return i }

        // all code paths must define i
        // exactly once, or it’s a compiler
        // error
        if foo() {
            i = 0
        } else {
            i = 1
        }

        doOtherStuff()
    }


This control flow is wacky. Please never do this.


Huh, I didn't know about `return` in `defer`, but is it really useful?


No, I actually misremembered… you can’t return in a defer.

The magical thing I was misremembering is that you can reference a not-yet-defined value in a defer, so long as all code paths define it once:

  fn callFoo() -> FooResult {
    let fooParam: Int // declared, not defined yet
    defer {
      // fooParam must get defined by the end of the function
      foo(fooParam)
      otherStuffAfterFoo() // …
    }

    // all code paths must assign fooParam
    if cond {
      fooParam = 0
    } else {
      fooParam = 1
      return // early return!
    }

    doOtherStuff()
  }
Blame it on it being years since I’ve coded in swift, my memory is fuzzy.


    #include <iostream>
    #define RemParens_(VA) RemParens__(VA)
    #define RemParens__(VA) RemParens___ VA
    #define RemParens___(...) __VA_ARGS__
    #define DoConcat_(A,B) DoConcat__(A,B)
    #define DoConcat__(A,B) A##B
    #define defer(BODY) struct DoConcat_(Defer,__LINE__) { ~DoConcat_(Defer,__LINE__)() { RemParens_(BODY) } } DoConcat_(_deferrer,__LINE__)

    int main() {
        {
            defer(( std::cout << "Hello World" << std::endl; ));
            std::cout << "This goes first" << std::endl;
        }
    }


Why would that be preferable to just using an RAII style scope_exit with a lambda


Meh, I was going to use the preprocessor for __LINE__ anyways (to avoid requiring a variable name) so I just made it an "old school lambda." Besides, scope_exit is in C++23 which is still opt-in in most cases.


And here I thought we were trying to finally kill off pre-processor macros.


"We have syntax macros at home"




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

Search: