Readit News logoReadit News
koakuma-chan · 2 months ago
Sytten · 2 months ago
I want that stabilized so bad but it's not been really moving forward.
mbrubeck · 2 months ago
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

mmastrac · 2 months ago
I was not a fan when I first saw it but I'm becoming desperate to have it the more Rust I write.
koakuma-chan · 2 months ago
#![feature(try_blocks)]

You only live once.

stouset · 2 months ago
Out of curiosity why can’t a block just do this natively?
loeg · 2 months ago
Can this just be done as a lambda that is immediately evaluated? It's just much more verbose.

    let x = (|| -> Result<i32, std::num::ParseIntError> {
         Ok("1".parse::<i32>()?
          + "2".parse::<i32>()?
          + "3".parse::<i32>()?)
    })();

rendaw · 2 months ago
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.
schneems · 2 months ago
Wouldn't that also move any referenced variables too? Unlike the block example that would make this code not identical to what it's replacing.
saghm · 2 months ago
My instinct is this would get hairy much faster if you want to actually close over variables compared to using a block.
tayo42 · 2 months ago
Why does this need special syntax? Couldn't blocks do this if the expression returns a result in the end?
bobbylarrybobby · 2 months ago
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.
mwcz · 2 months ago
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.
jeroenhd · 2 months ago
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...

koakuma-chan · 2 months ago
One reason is that would be a breaking change.
valcron1000 · 2 months ago
One of the first things I tried in Rust a couple of years ago coming from Haskell. Unfortunately it's still not stabilized :(
oniony · 2 months ago
Now that is pretty cool.
satvikpendem · 2 months ago
Ah yes, do-notation.
emtel · 2 months ago
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.
nemo1618 · 2 months ago
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.

saghm · 2 months ago
Unless I'm misunderstanding, you'd have the same lifetime issue if you tried to move the block into a function, though. I think the parent comment's point is that it causes fewer issues than abstracting to a separate function, not necessarily compared to inlining everything.
adrianN · 2 months ago
Avoiding that kind of use after free problem is exactly why people choose Rust, isn’t it?
janquo · 2 months ago
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

bryanlarsen · 2 months ago
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.

bryanlarsen · 2 months ago
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.
defen · 2 months ago
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?
loeg · 2 months ago
Conceivably, yes.
tstenner · 2 months ago
I have been using this in a web application that acquires a lock, retrieves and returns a few variables to the outer scope an then immediately unlocks the mutex again
JDye · 2 months ago
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
  };

bobbylarrybobby · 2 months ago
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.

bryanlarsen · 2 months ago
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.
kibwen · 2 months ago
It helps that the specific pattern of redeclaring a variable just to change its mutability for the remainder of its scope is about the least objectionable use of shadowing possible.
ziml77 · 2 months ago
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.

ghosty141 · 2 months ago
In my opinion it's the 'correct' design, I don't see any advantage from not doing this.
nadinengland · 2 months ago
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.

dwattttt · 2 months ago
By the by, code blocks on here are denoted by two leading spaces on each line

  like
  this

nadinengland · 2 months ago
ah, much appreciated!
notpushkin · 2 months ago
Interesting that you can use blocks in JS:

  {
    const x = 5;
    x + 5
  }
  // => 10
  x
  // => undefined
But I don’t see a way to get the result out of it. As soon as you try to use it in an expression, it will treat it as an object and fail to parse.

charleszw · 2 months ago
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?)
paavohtl · 2 months ago
At least in simple cases the compiler will just inline the closure, as if it never existed. There shouldn't be any measurable overhead.
pwdisswordfishy · 2 months ago
https://github.com/tc39/proposal-do-expressions

(Not to be confused with do notation)

esafak · 2 months ago
Block expression https://doc.rust-lang.org/reference/expressions/block-expr.h...

Also in Kotlin, Scala, and nim.

IshKebab · 2 months ago
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).
afdbcreid · 2 months ago
The only things in Rust that are real statements are `let` statements, and item statements (e.g. declaring an `fn` inside a function). All other statements are in fact expressions, although some always return `()` so they're not really useful as such.