Live data from Hacker News

Patterns for Defensive Programming in Rust

corrode.dev

71–80 of 99 posts

Re: Patterns for Defensive Programming in Rust

#71
post #53

Earlier quoted context omitted.

> Cloudflare had its unwrap fiasco, Was it a fiasco? Really? The rust unwrap call is the equivalent to C code like this: int result = foo(…); assert(result >= 0); If that assert tripped, would you blame the assert? Of course not. Or blame C? No. If that assert tripped, it’s doing its job by telling you there’s a problem in the call to foo(). You can write buggy code in rust just like you can in any other language.

I think it's because unwrap() seems to unassuming at a glance. If it were or_panic() instead I think people would intuit it more as extremely dangerous. I understand that we're not dealing with newbies here, but everyone is still human and everything we do to reduce mistakes is a good thing.

It's not unassuming. Rust is superior to many other languages for making this risky behaviour visually present in the code base.

You can go ahead and grep your codebase for this today, instead of waiting for an incident.

I'm a fairly new migrant from Java to C#, and when I do some kind of collection lookup, I still need to check whether the method will return a null, throw an exception, expect an out+variable, or worst of all, make up some kind of default. C#'s equivalent to unwrap seems to be '!' (or maybe .Val() or something?)

Re: Patterns for Defensive Programming in Rust

#72
post #71

Earlier quoted context omitted.

I think it's because unwrap() seems to unassuming at a glance. If it were or_panic() instead I think people would intuit it more as extremely dangerous. I understand that we're not dealing with newbies here, but everyone is still human and everything we do to reduce mistakes is a good thing.

It's not unassuming. Rust is superior to many other languages for making this risky behaviour visually present in the code base. You can go ahead and grep your codebase for this today, instead of waiting for an incident. I'm a fairly new migrant from Java to C#, and when I do some kind of collection lookup, I still need to check whether the method will return a null, throw an exception, expect an out+variable, or wor…

Whether the value is null (and under which conditions) is encoded into the nullability of return value. Unless you work with a project which went out of its way to disable NRTs (which I've sadly seen happen).

Re: Patterns for Defensive Programming in Rust

#73
post #54

Earlier quoted context omitted.

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.

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."

I forgot about that one. Oops. So, ignore the part about Haskell and keep the rest.

Re: Patterns for Defensive Programming in Rust

#74
post #64
post #54

Earlier quoted context omitted.

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.

> 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 prev…

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.

Re: Patterns for Defensive Programming in Rust

#75
post #58
post #54

Earlier quoted context omitted.

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.

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 cat…

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.

Re: Patterns for Defensive Programming in Rust

#76
post #41

What's really nice is where you don't need defensive programming in Rust. If your function gets ownership of, or an exclusive reference to an object, then you know for sure that this reference, for as long as it exists, is the only one in the entire program that can access this object (across all threads, 3rd party libraries, recursion, async, whatever). References can't be null. Smart pointers can't be null. Not mer…

Article mostly focuses on code practices to avoid making logical mistakes when iterating on your program.

Re: Patterns for Defensive Programming in Rust

#77
post #41

What's really nice is where you don't need defensive programming in Rust. If your function gets ownership of, or an exclusive reference to an object, then you know for sure that this reference, for as long as it exists, is the only one in the entire program that can access this object (across all threads, 3rd party libraries, recursion, async, whatever). References can't be null. Smart pointers can't be null. Not mer…

I fully agree with the actually great thing being what not to have to look out for and my first thought when seeing the headline was: "Doesn't the type system handle most of that stuff?"

In other languages I get most of the benefits by sticking to functional programming practices and not mutating stuff all over the place. Rust's type system sort of encodes that, and maybe a little more, by making safe mutation a known non-interfering thing.

Re: Patterns for Defensive Programming in Rust

#78
post #63

Earlier quoted context omitted.

I think it's because unwrap() seems to unassuming at a glance. If it were or_panic() instead I think people would intuit it more as extremely dangerous. I understand that we're not dealing with newbies here, but everyone is still human and everything we do to reduce mistakes is a good thing.

I'm sure lots of bystanders are surprised to learn what .unwrap() does. But reading the post, I didn't get the impression that anyone at cloudflare was confused by unwrap's behaviour. If you read the postmortem, they talk in depth about what the issue really was - which from memory is that their software statically allocated room for 20 rules or something. And their database query unexpected returned more than 20 ite…

Looking at that unwrap as a Result handler, the arguable issue with the code was the lack of informative explanation in the unexpected case. Panicking from the ill-defined state was desired behaviour, but explicit is always better.

The argument to the contrary is that reading the error out-load showed “the config initializer failing to return a valid configuration”. A panic trace saying “config init failed” is a minor improvement.

If we’re gonna guess and point fingers, I think the configuration init should be doing its own panicking and logging when it blows up.

Re: Patterns for Defensive Programming in Rust

#79

The tech industry is full of brash but lightly-seasoned people resurrecting discredited ideas for contrarianism cred and making the rest of us put down monsters we thought we'd slain a long time ago. "Defensive programming" has multiple meanings. To the extent it means "avoid using _ as a catch-all pattern so that the compiler nags you if someone adds an enum arm you need to care about", "defensive" programming is go…

The Java one can actually be quite helpful, for a couple of reasons:

1. It tells you which variable is null. While I think modern Java will include that detail in the exception, that's fairly new. So if you had `a.foo(b.getBar(), c.getBaz())`, was a, b, or c null? Who knows!

2. Putting it in the constructor meant you'd get a stack trace telling you where the null value came from, while waiting until it was used made it a lot harder to track down the source.

Not applicable to all situations, but it could be genuinely helpful, and has been to me.

Re: Patterns for Defensive Programming in Rust

#80
post #21
post #11

Good article, but one (very minor) nit I have is with the PizzaOrder example. struct PizzaOrder { size: PizzaSize, toppings: Vec , crust_type: CrustType, ordered_at: SystemTime, } The problem they want to address is partial equality when you want to compare orders but ignoring the ordered_at timestamp. To me, the problem is throwing too many unrelated concerns into one struct. Ideally instead of using destructuring t…

You have a good point there, that is better. But it is still, well honestly, wrong. Two orders ordered at different times are just not the same order, and using a typeclass approach to say that they most definitely are is going to bite you in the back seat. PartialEq and Eq for PizzaDetails is good. If there is a business function that computes whether or not someone orders the same thing, then that should start by p…

> But it is still, well honestly, wrong. Two orders ordered at different times are just not the same order

I probably don't have enough context but whatever identity makes up "your order" goes in the PizzaOrder and not the PizzaDetails. The delivery address, for example, goes in the PizzaOrder.

Post reply on HN