zlacker

[parent] [thread] 11 comments
1. elbear+(OP)[view] [source] 2025-12-06 07:08:14
The point is Rust provides more safety guarantees than C. But unwrap is an escape hatch, one that can blow up in your face. If they had taken the Haskell route and not provide unwrap at all, this wouldn't have happened.
replies(4): >>openun+P2 >>antonv+W8 >>joseph+Ni >>sapiog+rq
2. openun+P2[view] [source] 2025-12-06 08:01:11
>>elbear+(OP)
https://hackage.haskell.org/package/base/docs/Data-Maybe.htm...

"The fromJust function extracts the element out of a Just and throws an error if its argument is Nothing."

replies(1): >>elbear+ty
3. antonv+W8[view] [source] 2025-12-06 09:25:41
>>elbear+(OP)
Haskell’s fromJust, and similar partial functions like head, are as dangerous as Rust’s unwrap. The difference is only in the failure mode. Rust panics, whereas Haskell throws a runtime exception.

You might think that the Haskell behavior is “safer” in some sense, but there’s a huge gotcha: exceptions in pure code are the mortal enemy of lazy evaluation. Lazy evaluation means that an exception can occur after the catch block that surrounded the code in question has exited, so the exception isn’t guaranteed to get caught.

Exceptions can be ok in a monad like IO, which is what they’re intended for - the monad enforces an evaluation order. But if you use a partial function like fromJust in pure code, you have to be very careful about forcing evaluation if you want to be able to catch the exception it might generate. That’s antithetical to the goal of using exceptions - now you have to write to code carefully to make sure exceptions are catchable.

The bottom line is that for reliable code, you need to avoid fromJust and friends in Haskell as much you do in Rust.

The solution in both languages is to use a linter to warn about the use of partial functions: HLint for Haskell, Clippy for Rust. If Cloudflare had done that - and paid attention to the warning! - they would have caught that unwrap error of theirs at linting time. This is basically a learning curve issue.

replies(2): >>elbear+jz >>joseph+yt1
4. joseph+Ni[view] [source] 2025-12-06 11:31:29
>>elbear+(OP)
> The point is Rust provides more safety guarantees than C. But unwrap is an escape hatch

Nope. Rust never makes any guarantees that code is panic-free. Quite the opposite. Rust crashes in more circumstances than C code does. For example, indexing past the end of an array is undefined behaviour in C. But if you try that in rust, your program will detect it and crash immediately.

More broadly, safe rust exists to prevent undefined behaviour. Most of the work goes to stopping you from making common memory related bugs, like use-after-free, misaligned reads and data races. The full list of guarantees is pretty interesting[1]. In debug mode, rust programs also crash on integer overflow and underflow. (Thanks for the correction!). But panic is well defined behaviour, so that's allowed. Surprisingly, you're also allowed to leak memory in safe rust if you want to. Why not? Leaks don't cause UB.

You can tell at a glance that unwrap doesn't violate safe rust's rules because you can call it from safe rust without an unsafe block.

[1] https://doc.rust-lang.org/reference/behavior-considered-unde...

replies(2): >>Measte+Yl >>elbear+6z
◧◩
5. Measte+Yl[view] [source] [discussion] 2025-12-06 12:10:02
>>joseph+Ni
> In debug mode, rust programs also crash on unsigned integer overflow.

All integer overflow, not just unsigned. Similarly, in release mode (by default) all integer overflow is fully defined as two's complement wrap.

6. sapiog+rq[view] [source] 2025-12-06 12:58:49
>>elbear+(OP)
Haskell is far more dangerous. It allows you to simple destruct the `Just` variant without a path for the empty case, causing a runtime error if it ever occurs.
◧◩
7. elbear+ty[view] [source] [discussion] 2025-12-06 14:08:14
>>openun+P2
I forgot about that one. Oops. So, ignore the part about Haskell and keep the rest.
◧◩
8. elbear+6z[view] [source] [discussion] 2025-12-06 14:14:12
>>joseph+Ni
I never said Rust makes guarantees that code is panic-free. I said that Rust provides more safety guarantees than C. The Result type is one of them because you have to handle the error case explicitly. If you don't use unwrap.

Also, when I say safety guarantees, I'm not talking about safe rust. I'm talking about Rust features that prevent bugs, like the borrow checker, types like Result and many others.

replies(1): >>joseph+dn1
◧◩
9. elbear+jz[view] [source] [discussion] 2025-12-06 14:16:43
>>antonv+W8
I forgot about fromJust. On the other hand, fromJust is shunned by practically everybody writing Haskell. `unwrap` doesn't have the same status. I also understand why. Rust wanted to be more appealing, not too restrictive while Haskell doesn't care about attracting developers.
replies(1): >>antonv+vm1
◧◩◪
10. antonv+vm1[view] [source] [discussion] 2025-12-06 21:05:04
>>elbear+jz
It's not just fromJust, there many other partial functions, and they all have the same issue, such as head, tail, init, last, read, foldl1, maximum, minimum, etc.

It's an overstatement to say that these are "shunned by practically everybody". They're commonly used in scenarios where the author is confident that the failure condition can't happen due to e.g. a prior test or guard, or that failure can be reliably caught. For example, you can catch a `read` exception reliably in IO. They're also commonly used in GHCi or other interactive environments.

I disagree that the Rust perspective on unwrap is significantly different. Perhaps for beginning programmers in the language? But the same is often true of Haskell. Anyone with some experience should understand the risks of these functions, and if they don't, they'll eventually learn the hard way.

One pattern in Rust that may mislead beginners is that unwrap is often used on things like builders in example docs. The logic here is that if you're building some critical piece of infra that the rest of the program depends on, then if it fails to build the program is toast anyway, so letting it panic can make sense. These examples are also typically scenarios where builder failure is unusual. In that case, it's the author's choice whether to handle failure or just let it panic.

◧◩◪
11. joseph+dn1[view] [source] [discussion] 2025-12-06 21:11:10
>>elbear+6z
Ah thanks for the clarification. That wasn’t clear to me reading your comment.

You’re right that rust forces you to explicitly decide what to do with Result::Err. But that’s exactly what we see here. .unwrap() is handling the error case explicitly. It says “if this is an error, crash the program. Otherwise give me the value”. It’s a very useful function that was used correctly here. And it functioned correctly by crashing the program.

I don’t see the problem in this code, beyond it not giving a good error message as it crashed. As the old joke goes, “Task failed successfully.”

◧◩
12. joseph+yt1[view] [source] [discussion] 2025-12-06 22:10:55
>>antonv+W8
> The difference is only in the failure mode. Rust panics, whereas Haskell throws a runtime exception.

Fun facts: Rust’s default panic handler also throws a runtime exception just like C++ and other languages. Rust also has catch blocks (std::panic::catch_unwind). But its rarely used. By convention, panicking in rust is typically used for unrecoverable errors, when the program should probably crash. And Result is used when you expect something to be fallable - like parsing user input.

You see catch_unwind in the unit test runner. (That’s why a failing test case doesn’t stop other unit tests running). And in web servers to return 50x. You can opt out of this behaviour with panic=abort in Cargo.toml, which also makes rust binaries a bit smaller.

[go to top]