Live data from Hacker News

Rust's Block Pattern

notgull.net

91–100 of 121 posts

Re: Rust's Block Pattern

#91
post #70
post #39

I use this all the time. It's features like these that sell Rust for me honestly; even if you wrapped your whole program in `unsafe` it would still be a massively better language than C++ or C.

Many languages use this idiom. Some popular ones even. So while it's good that Rust joined them, it's hardly a differentiator.

It's a differentiator wrt C and C++, is what I said.

Re: Rust's Block Pattern

#92

We do this via run in TS: export const run = (f: () => T): T => { return f(); };

Can you clarify why do you prefer this over an IIFE `(() => {...})()`?

I like it. IIFEs always make me nervous because they look like they beg to be removed if you don't know why they are used. Using an explicit function such as `run` looks much more intentional, and provide a single intuitive place (the documentation of the `run` function) to explain the pattern.

Re: Rust's Block Pattern

#93

I love that this is part of the syntax. I typically use closures to do this in other languages, but the syntax is always so cumbersome. You get the "dog balls" that Douglas Crockford always called them: ``` const config = (() => { const raw_data = ... ... return compiled; })()' const result = config.whatever; // carry on return result; ``` Really wish block were expressions in more languages.

Yes, I constantly use this pattern in C++/JavaScript, although I haven't tested how performant it is in the former (what does the compiler even do with such an expression?)

At least in simple cases the compiler will just inline the closure, as if it never existed. There shouldn't be any measurable overhead.

Re: Rust's Block Pattern

#94
post #66

Earlier quoted context omitted.

That prevents other control flow mechanisms (return, break) from operating past the function boundary. In general, I avoid single-callsite functions as much as possible (including the iterator api) for this reason.

It sounds like you're fighting the language - Rust is sort of FP-light and you're encouraged to return a null/error value from the intermediate calculation instead of doing an early return from the outer scope. It's a nice and easy to follow way to structure the code IME. Yes, it's more verbose when an early return would have been just right - so be it.

For the case where `try` is useful over the functional form (i.e. parent's situation of having a desired Result, plus some unrelated early-returning), that ends up with nested `Result`s though, i.e. spamming an `Ok(Ok(x))` on all the non-erroring cases, which gets ugly fast.

Re: Rust's Block Pattern

#95

I love that this is part of the syntax. I typically use closures to do this in other languages, but the syntax is always so cumbersome. You get the "dog balls" that Douglas Crockford always called them: ``` const config = (() => { const raw_data = ... ... return compiled; })()' const result = config.whatever; // carry on return result; ``` Really wish block were expressions in more languages.

https://github.com/tc39/proposal-do-expressions

(Not to be confused with do notation)

Re: Rust's Block Pattern

#97
post #91
post #70

Earlier quoted context omitted.

Many languages use this idiom. Some popular ones even. So while it's good that Rust joined them, it's hardly a differentiator.

It's a differentiator wrt C and C++, is what I said.

Yes, sadly this isn't a part of standard C or C++.

It is available as a language extension in Clang and GCC and widely used (e.g. by the Linux kernel).

Unfortunately it is not supported by the third major compiler out there so many projects can't or don't want to use it.

Re: Rust's Block Pattern

#98
post #12

There are some situations with tricky lifetime issues that are almost impossible to write without this pattern. Trying to break code out into functions would force you to name all the types (not even possible for closures) or use generics (which can lead to difficulties specifying all required trait bounds), and `drop()` on its own is of no use since it doesn't effect the lexical lifetimes.

Conversely, I use this "block pattern" a lot, and sometimes it causes lifetime issues: let foo: &[SomeType] = { let mut foo = vec![]; // ... initialize foo ... &foo }; This doesn't work: the memory is owned by the Vec, whose lifetime is tied to the block, so the slice is invalid outside of that block. To be fair, it's probably best to just make foo a Vec, and turn it into a slice where needed.

There is some experimental work for that here I believe:

https://doc.rust-lang.org/beta/unstable-book/language-featur...

AFAIU it essentially creates a variable in inner scope but defers drop to the outer scope so that you can return the reference

Re: Rust's Block Pattern

#99
post #14

Block expression https://doc.rust-lang.org/reference/expressions/block-expr.h... Also in Kotlin, Scala, and nim.

I think this comes from functional programming. I'd just call it "everything is an expression" (which isn't quite true in Rust but it's a lot more true than it is in traditional imperative languages like C++ and Python).

Re: Rust's Block Pattern

#100
post #58

Earlier quoted context omitted.

My instinct is this would get hairy much faster if you want to actually close over variables compared to using a block.

Not sure if that is relevant to your point, but: For better and for worse, closing over any outer scope variables is syntactically free in Rust lambdas. You just access them.

It's syntactically free, but it can cause borrow-checker errors thst cause your code to outright fail to compile.
Post reply on HN