Try blocks let you encapsulate the early-return behavior of Try-returning operations so that they don't leak through to the surrounding function. This lets you use the ? operator 1. when the Try type doesn't match that of the function this is taking place in 2. when you want to use ? to short circuit, but don't want to return from the enclosing function. For instance, in a function returning Result<T,E>, you could have a try block where you do a bunch of operations with Option and make use of the ? operator, or have ? produce an Err without returning from the enclosing function. Without try blocks, you pretty much need to define a one-off closure or function so that you can isolate the use of ? within its body.
The best part of try blocks is the ability to use the ? operator within them. Any block can return a result, but only function blocks (and try blocks) can propagate an Err with the ? operator.
The closest thing I can think of that will let you return a result from within a separate scope using a set of foo()? calls would be a lambda function that's called immediately, but that has its own problems when it comes to moving and it probably doesn't compile to very fast code either. Something like https://play.rust-lang.org/?version=stable&mode=debug&editio...
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.
Our codebase is full of this pattern and I love it. Every time I get clean up temporaries and expose an immutable variable outside of the setup, makes me way too happy.
A lot of the time it looks like this:
let config = {
let config = get_config_bytes();
let mut config = Config::from(config);
config.do_something_mut();
config.do_another_mut();
config
};
More significantly the new variables x and y in the block are Drop'd at the end of the block rather than at the end of the function. This can be significant if:
- Drop does something, like close a file or release a lock, or
- x and y don't have Send and/or Sync, and you have an await point in the function or are doing multi-threaded stuff
This is why you should almost always use std::sync::Mutex rather than tokio::sync::Mutex. std's Mutex isn't Sync/Send, so the compiler will complain if you hold it across an await. Usually you don't want mutex's held across an await.
Can this also affect stack usage? Like if `x` gets dropped before `y` is introduced, can `y` reuse `x`'s stack space (let's assume they are same size/alignment). Or does the compiler already do that if it can see that one is not used after the other is introduced?
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?)
This seems like a great way to group semantically-related statements, reduce variable leakage, and reduce the potential to silently introduce additional dependencies on variables. Seems lighter weight (especially from a cognitive load perspective) than lambdas. Appropriate for when there is a single user of the block -- avoids polluting the namespace with additional functions. Can be easily turned into a separate function once there are multiple users.
The first example given is not at all convincing. Its is clear as the sky that loading the config file should be be a separate function of its own. Coupling sending HTTP requests with it makes no sense.
The second example "erasure of mutability" makes more sense. But this effectively makes it a Rust-specific pattern.
It's essentially an inline function with only 1 client. Can be a preference for inline readability and automatically enforces there are no other clients of the "function".
Blocks being expressions is one of the features of the Rust language I really love (and yes I know it's not something Rust invented, but it's still not in many other popular languages).
That last example is probably my biggest use of it because I hate having variables being unnecessarily mutable.
Not mentioned in the article but kinda neat: you can label such a block and break out of it, too! The break takes an argument that becomes the value of the block that is broken out of.
I just learned this one, and am gradually starting to use it! It applies for loops too. I saw it in ChatGPT code, and had to stop and look it up. Rust is a big language, for worse and for better.
I wouldn't call Rust "a big language" because of labeled break. This is a pretty standard language feature, you can do the same in C (and therefore C++), Go, Javascript, Java, C#...
... is something to be used very sparingly. I reckon I write a new one about once a year.
Very often if you think harder you realise you didn't want this, you should write say, a function (from which you can return) or actually you didn't want to break early at all. Not always, but often. If you write more "break 'label value" than just break then you are almost certainly Doing It Wrong™.
Each does different things, and Rust also has plenty of them. and_then(), or(), or_else(), then(), the list goes on. Kotlin just implements them more widely.
Actually, Kotlin's with() and apply() are more powerful than what Rust can provide. Then again, Rust isn't designed with OO in mind, so you probably shouldn't use those patterns in Rust anyway.
I agree, i started with (scope) blocks in Rust, but keep the habit in Kotlin win the run - scope-function. Since run takes no arguments, it feels like the closest equivalent to Rust scopes (compared to other Korlin scope functions, which also keep their local variables from polluting the rest of the function body).
That's all fine until later on, probably in some obscure loop, `i_think_this_is_setup` is used without you noticing.
Instead doing something like this tells the reader that it will be used again:
i_think_this_is_setup = even_more_stuff
the_thing = begin
setup_a = some_stuff
setup_b = some_more_stuff
run_setup(setup_a, setup_b, i_think_this_is_setup)
end
I now don't mentally have to keep track of what `setup_a` or `setup_b` are anymore and, since the writer made a conscious effort not to put it in the block, you will take an extra look for it in the outer scope.
Clojure also has the threading macro -> and ->> which are great at converting exactly the same type of code into a stream of modifications instead of breaking out everything into variables. Naming things can be very useful sometimes but sometimes it is entirely gratuitous and distracting to have
let input = read_input();
let trimmed_input = input.trim();
let trimmed_uppercase_input = trimmed_input.uppercase();
...
The extra variable names are almost completely boilerplate and make it also annoying to reorder things.
In Clojure you can do
(-> (read-input) string/trim string/upcase)
And I find that so much more readable and refactorable.
This is one of those natural consequences of "everything is an expression" languages that I really like! I like more explicit syntax like Zig's labelled blocks, but any of these are cool.
Try this out, you can actually (technically) assign a variable to `continue` like:
let x = continue;
Funnily enough, one of the few things that are definitely always a statement are `let` statements! Except, you also have `let` expressions, which are technically different, so I guess that's not really a difference at all.
I think the technique is important to have in your vocabulary, but I think the examples given are a weak sell.
In the example given, I would have preferred to extract to a method—-what if I want to load the config from somewhere else? And perhaps the specific of strip comments itself could have been extracted to a more-semantically-aptly named post-processing method.
I see the argument that when extracted to a function, that you don’t need to go hunting for it. But if we look at the example with the block, I still see a bunch of detail about how to load the config, and then several lines using it. What’s more important in that context—-the specifics of the loading of config, or the specifics of how requests are formed using the loaded config?
The fact that you need to explain what’s happening with comments is a smell. Properly named variables and methods would obviate the need for the comments and would introduce semantic meaning thru names.
I think blocks are useful when you are referencing a lot of local variables and also have fairly localized meaning within the method. For example, you can write a block to capture a bunch of values for logging context—-then you can call that block in every log line to get a logging context based on current method state. It totally beats extracting a logging context method that consumes many variables and is unlikely to be reused outside of the calling method, and yet you get delayed evaluation and single point of definition for it.
So yes to the pattern, but needs a better example.
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.
This is a great addition to the best patterns and practices in Rust. Worth noting and using. In JavaScript there's the proposal of "do expressions" which accomplish the same.
I have one better: the try block pattern.
https://doc.rust-lang.org/beta/unstable-book/language-featur...
Can this just be done as a lambda that is immediately evaluated? It's just much more verbose.
I want that stabilized so bad but it's not been really moving forward.
There's some active work recently on fixing blocking issues, e.g.:
https://github.com/rust-lang/rust/pull/148725
https://github.com/rust-lang/rust/pull/149489
I was not a fan when I first saw it but I'm becoming desperate to have it the more Rust I write.
#![feature(try_blocks)]
You only live once.
I've tried it recently, from memory error inference wasn't that great through it.
That's exactly what's currently being fixed before stabilizing it.
One of the first things I tried in Rust a couple of years ago coming from Haskell. Unfortunately it's still not stabilized :(
Now that is pretty cool.
Why does this need special syntax? Couldn't blocks do this if the expression returns a result in the end?
Try blocks let you encapsulate the early-return behavior of Try-returning operations so that they don't leak through to the surrounding function. This lets you use the ? operator 1. when the Try type doesn't match that of the function this is taking place in 2. when you want to use ? to short circuit, but don't want to return from the enclosing function. For instance, in a function returning Result<T,E>, you could have a try block where you do a bunch of operations with Option and make use of the ? operator, or have ? produce an Err without returning from the enclosing function. Without try blocks, you pretty much need to define a one-off closure or function so that you can isolate the use of ? within its body.
The best part of try blocks is the ability to use the ? operator within them. Any block can return a result, but only function blocks (and try blocks) can propagate an Err with the ? operator.
Not without being able to use the ? operator.
The closest thing I can think of that will let you return a result from within a separate scope using a set of foo()? calls would be a lambda function that's called immediately, but that has its own problems when it comes to moving and it probably doesn't compile to very fast code either. Something like https://play.rust-lang.org/?version=stable&mode=debug&editio...
One reason is that would be a breaking change.
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.
Our codebase is full of this pattern and I love it. Every time I get clean up temporaries and expose an immutable variable outside of the setup, makes me way too happy.
A lot of the time it looks like this:
More significantly the new variables x and y in the block are Drop'd at the end of the block rather than at the end of the function. This can be significant if:
- Drop does something, like close a file or release a lock, or
- x and y don't have Send and/or Sync, and you have an await point in the function or are doing multi-threaded stuff
This is why you should almost always use std::sync::Mutex rather than tokio::sync::Mutex. std's Mutex isn't Sync/Send, so the compiler will complain if you hold it across an await. Usually you don't want mutex's held across an await.
oops: Of course the Mutex is Sync/Send, that's the whole point of a Mutex. It's the std::sync::MutexGuard that's not.
Can this also affect stack usage? Like if `x` gets dropped before `y` is introduced, can `y` reuse `x`'s stack space (let's assume they are same size/alignment). Or does the compiler already do that if it can see that one is not used after the other is introduced?
Conceivably, yes.
You can also de-mut-ify a variable by simply shadowing it with an immutable version of itself:
let mut data = foo(); data.mutate(); let data = data;
May be preferable for short snippets where adding braces, the yielded expression, and indentation is more noise than it's worth.
Variable shadowing felt wrong for a while because it's considered verboten in so many other environments. I use it fairly liberally in rust now.
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 = ...
})()'const result = config.whatever;
// carry on
return result; ```
Really wish block were expressions in more languages.
By the by, code blocks on here are denoted by two leading spaces on each line
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?)
Block expression https://doc.rust-lang.org/reference/expressions/block-expr.h...
Also in Kotlin, Scala, and nim.
This seems like a great way to group semantically-related statements, reduce variable leakage, and reduce the potential to silently introduce additional dependencies on variables. Seems lighter weight (especially from a cognitive load perspective) than lambdas. Appropriate for when there is a single user of the block -- avoids polluting the namespace with additional functions. Can be easily turned into a separate function once there are multiple users.
The first example given is not at all convincing. Its is clear as the sky that loading the config file should be be a separate function of its own. Coupling sending HTTP requests with it makes no sense.
The second example "erasure of mutability" makes more sense. But this effectively makes it a Rust-specific pattern.
It's essentially an inline function with only 1 client. Can be a preference for inline readability and automatically enforces there are no other clients of the "function".
Blocks being expressions is one of the features of the Rust language I really love (and yes I know it's not something Rust invented, but it's still not in many other popular languages).
That last example is probably my biggest use of it because I hate having variables being unnecessarily mutable.
In my opinion it's the 'correct' design, I don't see any advantage from not doing this.
Not mentioned in the article but kinda neat: you can label such a block and break out of it, too! The break takes an argument that becomes the value of the block that is broken out of.
I just learned this one, and am gradually starting to use it! It applies for loops too. I saw it in ChatGPT code, and had to stop and look it up. Rust is a big language, for worse and for better.
I wouldn't call Rust "a big language" because of labeled break. This is a pretty standard language feature, you can do the same in C (and therefore C++), Go, Javascript, Java, C#...
Very often if you think harder you realise you didn't want this, you should write say, a function (from which you can return) or actually you didn't want to break early at all. Not always, but often. If you write more "break 'label value" than just break then you are almost certainly Doing It Wrong™.
It's idiomatic in Kotlin as well!
https://kotlinlang.org/docs/scope-functions.html
So many options why oh why. let run with also apply
Each does different things, and Rust also has plenty of them. and_then(), or(), or_else(), then(), the list goes on. Kotlin just implements them more widely.
Actually, Kotlin's with() and apply() are more powerful than what Rust can provide. Then again, Rust isn't designed with OO in mind, so you probably shouldn't use those patterns in Rust anyway.
I agree, i started with (scope) blocks in Rust, but keep the habit in Kotlin win the run - scope-function. Since run takes no arguments, it feels like the closest equivalent to Rust scopes (compared to other Korlin scope functions, which also keep their local variables from polluting the rest of the function body).
I often employ this pattern in Ruby using `.tap` or a `begin` block.
It barely adds any functionality but it's useful for readability because of the same reasons in the OP.
It helps because I've been bitten by code that did this:
That's all fine until later on, probably in some obscure loop, `i_think_this_is_setup` is used without you noticing.Instead doing something like this tells the reader that it will be used again:
I now don't mentally have to keep track of what `setup_a` or `setup_b` are anymore and, since the writer made a conscious effort not to put it in the block, you will take an extra look for it in the outer scope.Clojure also has the threading macro -> and ->> which are great at converting exactly the same type of code into a stream of modifications instead of breaking out everything into variables. Naming things can be very useful sometimes but sometimes it is entirely gratuitous and distracting to have
let input = read_input(); let trimmed_input = input.trim(); let trimmed_uppercase_input = trimmed_input.uppercase();
...
The extra variable names are almost completely boilerplate and make it also annoying to reorder things.
In Clojure you can do
(-> (read-input) string/trim string/upcase)
And I find that so much more readable and refactorable.
This is also somewhat common in c++ with immediate-invoked lambdas
The same pattern can also be useful in Rust for early returning Result<_,_> errors (you cannot `let x = foo()?` inside of a normal block like that).
would fail to compile, or worse: would return out of the entire method if surrounding method would have return type Result<_,i32>. On the other hand, runs just fine.Hopefully try blocks will allow using ? inside of expression blocks in the future, though.
A blog post for it from a prominent c++er https://herbsutter.com/2013/04/05/complex-initialization-for...
GCC adds similar syntax as an extension to C: https://gcc.gnu.org/onlinedocs/gcc/Statement-Exprs.html
It's used all throughout the Linux kernel and useful for macros.
The best part of statement expressions is that a return there returns from the function itself, not from the statement expr.
I use that with with macros to return akins to std::expected, while maintaining the code in the happy-path like with exceptions.
This is one of those natural consequences of "everything is an expression" languages that I really like! I like more explicit syntax like Zig's labelled blocks, but any of these are cool.
Try this out, you can actually (technically) assign a variable to `continue` like:
let x = continue;
Funnily enough, one of the few things that are definitely always a statement are `let` statements! Except, you also have `let` expressions, which are technically different, so I guess that's not really a difference at all.
Reminds of Brian Wills OOP rant video from 2016. He advocates exactly for this pattern: https://www.youtube.com/watch?v=QM1iUe6IofM&t=2235s
I think the technique is important to have in your vocabulary, but I think the examples given are a weak sell.
In the example given, I would have preferred to extract to a method—-what if I want to load the config from somewhere else? And perhaps the specific of strip comments itself could have been extracted to a more-semantically-aptly named post-processing method.
I see the argument that when extracted to a function, that you don’t need to go hunting for it. But if we look at the example with the block, I still see a bunch of detail about how to load the config, and then several lines using it. What’s more important in that context—-the specifics of the loading of config, or the specifics of how requests are formed using the loaded config?
The fact that you need to explain what’s happening with comments is a smell. Properly named variables and methods would obviate the need for the comments and would introduce semantic meaning thru names.
I think blocks are useful when you are referencing a lot of local variables and also have fairly localized meaning within the method. For example, you can write a block to capture a bunch of values for logging context—-then you can call that block in every log line to get a logging context based on current method state. It totally beats extracting a logging context method that consumes many variables and is unlikely to be reused outside of the calling method, and yet you get delayed evaluation and single point of definition for it.
So yes to the pattern, but needs a better example.
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.
This is a great addition to the best patterns and practices in Rust. Worth noting and using. In JavaScript there's the proposal of "do expressions" which accomplish the same.
Obligatory use: it’s a block I guess
Voluntary use: I know this one. It’s a pattern now.