5 ms·
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`
by mojuba 9mo ago
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`.
- dwattttt 9mo agoI 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/ https://docs.rs/defer-rs/latest/defer_rs/
- deleted 9mo ago[deleted]
- mojuba 9mo agoI 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 }
- ninkendo 9mo agoEDIT: 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() }
- mojuba 9mo agoHuh, I didn't know about `return` in `defer`, but is it really useful?
- deleted 9mo ago[deleted]
- ninkendo 9mo agoNo, 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.
- deleted 9mo ago[deleted]
- kibwen 9mo agoThis control flow is wacky. Please never do this.
- kibwen 9mo agoIt'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.
- troglo-byte 9mo ago#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; } }
- deleted 9mo ago[deleted]
- rezonant 9mo agoWhy would that be preferable to just using an RAII style scope_exit with a lambda
- troglo-byte 9mo agoMeh, 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.
- chuckadams 9mo ago"We have syntax macros at home"
- pelorat 9mo agoAnd here I thought we were trying to finally kill off pre-processor macros.
- Someone 9mo agoI think Swift’s defer (https://docs.swift.org/swift-book/documentation/the-swift-programming-language/statements/#Defer-Statement https://docs.swift.org/swift-book/documentation/the-swift-pr...) was inspired by/copied from go (https://go.dev/tour/flowcontrol/12 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.
- mojuba 9mo agoYeah, 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 }
- kibwen 9mo agoI'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.
- Someone 9mo agoI can see your point, but that (https://book.pythontips.com/en/latest/context_managers.html 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-reference/statements/using https://learn.microsoft.com/en-us/dotnet/csharp/language-ref...), in Java, implement AutoCloseable (https://docs.oracle.com/javase/tutorial/essential/exceptions/tryResourceClose.html 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 https://news.ycombinator.com/item?id=46410610