Please tell me 'yeet' is not a thing in Rust!

Rust is only for the cool kids apparently :smile:

1 Like

I kinda understand why the Haskell weebs embrace monads, but their repeated application to literally every control flow feature regardless of ergonomics or sense is just tiring.

6 Likes

It's the same case as ?, which also does the .into() for you.

If yeet is going to be added, I think it's most understandable if r? is the same as

match r {
    Ok(v) => v,
    Err(e) => yeet e,
}

(In Result-to-Result context. Of course it wouldn't be that if r is an Option or …)

1 Like

Gaah! I had been trying not to learn what that β€˜word’ meant! >_<

:-p

1 Like

Monads are an absolutely terrible abstraction, which took root in Haskell mostly due to historical reasons and its unique limitations, and also because the more reasonable analogues are much harder to express as a library. Even if Rust could support monads, I would be against dragging them in. Thank gods it doesn't, so at least we don't need to fight that fight every day.

The reason Haskell uses monads is to have first-class reification of side effects, most important being mutable state. Rust's approach to mutable state blows anything monadic out of the water. As far as other side effects go, Haskell's approach scales so terribly that most projects just yeet proper side effect handling and reduce all architecture to pure core + IO boundary, which can do whatever.

There are much better approaches to side effects, like the algebraic effect systems of Koka and Eff, and that is the direction I'd want Rust to pursue. It is unclear whether some algebraic effect-like system can be properly retrofitted on Rust, but even if it can, it will take many years. Just look how hard it is to properly support even asynchrony.

Rust's Result type is a reification of exceptions as normal values. It is only natural to mirror exception-based language features in the capabilities of Result. We already have ? for explicit unwinding, but we don't have an analogue of raising the exception or scoping it to a block with try-catch. yeet performs the former, try blocks do the latter.

So the construct like yeet in all other languages is throw excn, raise excn and the likes of that, which are used in almost every modern language. The specific verb will be decided when it's time for stabilization. Perhaps it will be one of the common ones, to be more familiar. Perhaps it will be something entirely new, to avoid wrong preconceptions.

7 Likes

:rofl:

4 Likes

Not to mention that most of Rust's reinvention of monads are not quite monads. The try operator is more powerful than the flat_map based API because it performs a type conversion, which cannot be expressed as a monad. The same applies for Futures, they are mutable state machines which are infinitely more memory efficient than recursive callback based APIs. Plus, the do notation desugaring does not support control flow.

2 Likes

Note that this isn't entirely true: you can always write a fn that acts as the required scope by giving it the correct signature (in particular, its return value) and inline it. It would even act a lot like a more generic and powerful version of a try-block, and return Err(...) can act as yeet .... It's just that most people don't seem to be inclined to use fns for that purpose :slight_smile:

That's not always true, because it introduces a function boundary, and various analyses -- like initialization checking, move checking, and borrow checking -- are only intraprocedural.

1 Like

If I were to add anything to Rust's failure handling, it would be a way to define blocks that act as boundaries for enclosed ?. Let's say we had a catch macro like this:

fn f() -> () {
  let result = catch! {
    do_something()?;
    do_something_else()?;
    do_yet_another_thing()?
  }
  match result {
    Ok(_) => println!("Worked!")
    Err(_) => println!("Didn't work!")
  }
}

On application: I want to use ? in a closure, but want the error be captured inside the closure, not the surrounding function.

Best, Oliver

One way I've done that kind of thing on stable is to use an "IIFE" immediately invoked function expression. A closure capturing its outer scope but returning a result or option.

1 Like

To me, the Rust equivalent to exceptions are panics, not Result.

Scala has exceptions, but their use is unidiomatic except for fatal ones. For all other purposes, Scala has an Either type which works just like Rust's Result, except that Scala has no ?, so using Either is much much more ugly and cumbersome.

Whenever a Scala enthusiast wants to show what a genius language Scala is, they will show you how Either can be used to handle failures, and that it can be used like a collection (with zero or one element) in a for loop (or for comprehension, as they call it), and that a single for can even handle multiple Eithers at once. Isn't that great? It does improves things a little bit, but it is still cumbersome and ugly, not comparable to the simplicity and elegance of ?.

That's what try { ... } blocks will do, tracked here with the rest of the Try infrastructure.

2 Likes

You mean the closure calls a local function? Yeah, that's an easy workaround. I believe the local function can't capture anything, but you can of course always pass anything it needs as arguments.

I mean that:

fn foo() -> i32 {
    let r = || {
         call()?.thing()?.buzz()
    };
    r().unwrap_or_default()
}
1 Like

That's awesome!

Right - somehow I thought that wouldn't work, but turns out it does.

It has type inference issues. But it's a good poor man's try block.

Yes. Turns out we don't need the temporary r there either:

fn foo() -> i32 {
    (|| {
         call()?.thing()?.buzz()
    })().unwrap_or_default()
}

Call me old fashioned but this all starts to look a bit weird. Like Self-Invoking Functions in JavaScript.

3 Likes

"Self invoking/executing function" is another name for IIFE :wink: (see the Terminology paragraph)

1 Like