Rust is only for the cool kids apparently
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.
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 β¦)
Gaah! I had been trying not to learn what that βwordβ meant! >_<
:-p
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.
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.
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
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.
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.
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 Either
s 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.
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()
}
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.