Live data from Hacker News

Patterns for Defensive Programming in Rust

corrode.dev

81–90 of 99 posts

Re: Patterns for Defensive Programming in Rust

#81
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…

Decomposing things just to have different equality notions doesn't generalize. How would you decompose a character string so that you could have a case-insensitive versus sensitive comparison? :)

Right, I did note that this decomposition isn’t always applicable. But it often is, and you should default to that when possible.

Re: Patterns for Defensive Programming in Rust

#82
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.

I don't think you can know what unwrap does and assume it is safe. Plus warnings about unwrap are very common in the Rust community, I even remember articles saying to never use it.

I have always been critical of the Rust hype but unwrap is completely fine. Is an escape hatch has legitimate uses. Some code is fine to just fail.

It is easy to spot during code review. I have never programmed Rust professional and even I would have asked about the unwrap in the cloudfare code if I had reviewed that. You can even enforce to not use unwrap at all through automatic tooling.

Re: Patterns for Defensive Programming in Rust

#83
This has already been hashed over a hundred thousand times, but there are also developer habits that we all need to defend against. One is pulling in needless crates.

Rust encourages that behavior. Sometimes rightly, but it does build a habit.

I spoke previously about how the Rust book uses the external rand create as a key example and it sets the tone for developers. I'm changing that stance somewhat since it was a decent strategic choice to have crypto packages plug-and-play. But tit still builds a habit.

Re: Patterns for Defensive Programming in Rust

#84
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.

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

Anyone who has learned how to program Rust knows that unwrap() will panic if the thing you are unwrapping is Err/None. It's not unassuming at all. When the only person who could be tripped up by a method name is a complete newbie to the language, I don't think it's actually a problem.

Similarly, assert() isn't immediately obvious to a beginner that it will cause a panic. Heck, the term "panic" itself is non obvious to a beginner as something that will crash the program. Yet I don't hear anyone arguing that the panic! macro needs to be changed to crash_this_program. The fact of the matter is that a certain amount of jargon is inevitable in programming (and in my view this is a good thing, because it enables more concise communication amongst practitioners). Unwrap is no different than those other bits of jargon - perhaps non obvious when you are new, but completely obvious once you have learned it.

Re: Patterns for Defensive Programming in Rust

#85

This has already been hashed over a hundred thousand times, but there are also developer habits that we all need to defend against. One is pulling in needless crates. Rust encourages that behavior. Sometimes rightly, but it does build a habit. I spoke previously about how the Rust book uses the external rand create as a key example and it sets the tone for developers. I'm changing that stance somewhat since it was a…

> I spoke previously about how the Rust book uses the external rand create as a key example and it sets the tone for developers. I'm changing that stance somewhat since it was a decent strategic choice to have crypto packages plug-and-play. But tit still builds a habit.

Yeah, that originally turned me off from the language entirely. I also changed my mind eventually.

Re: Patterns for Defensive Programming in Rust

#86

In the first example, the match feels extremely overkill. Vec.first() exposes the correct semantic (as does Vec.iter().nth(0) for the more general case), returning an Option.

I also think the first example has a solution which is worse than the purported problem it attempts to solve. If you're worried that someone might take the if statement out from around the vec index (I don't think this is actually a concern, but let's say it is for sake of argument), what's to stop someone from taking the match statement out from around your slice access? I can't see any reason why the solution isn't equally as vulnerable to the exact same problem. So the match approach doesn't seem to be adding value, while being much more verbose, and less clear.

As you said, calling first() is a far better approach.

Re: Patterns for Defensive Programming in Rust

#87
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 don’t see how your comment is relevant, none of things you mention are covered in the article. This was an article about logic bugs that can exist in spite of the borrow checker.

Re: Patterns for Defensive Programming in Rust

#88
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…

While better, a person modifying PizzaDetails might or might not expect this change to affect the downstream pizza deduplication logic (wherever it got sprinkled throughout the code). They might not even know that it exists.

Ideally, imho, a struct is a dumb data holder - it is there to pass associated pieces of data together (or hold a complex unavoidable state change hidden from the user like Arc or Mutex).

All that is to say that adding a field to an existing struct and possibly populating it sparsely in some remote piece of code should not changed existing behavior.

I wonder whether there's a way to communicate to whoever makes changes to the pizza details struct that it might have unintended consequences down the line.

Should one wrap PizzaDetails with PizzaComparator? Or better even provide it as a field in PizzaOrder? Or we are running into Java-esq territory of PizzaComparatorBuilderDefaultsConstructorFactory?

Should we introduce a domain specific PizzaFlavor right under PizzaDetails that copies over relevant fields from PizzaDetails, and PizzaOrder compares two orders by constructing and comparing their flavours instead? A lot of boilerplate.. but what is being considered important to the pizza flavor is being explicitly marked.

In a prod codebase I'd annotate this code with "if change X chaange Y" pre submit hook - this constraint appears to be external to the language itself and live in the domain of "code changes over time". Protobufs successfully folded versioning into the language itself though. Protobufs also have field annotations, "{important_to_flavour=true}" field annotation would be useful here.

Re: Patterns for Defensive Programming in Rust

#89

Wow that’s amazing. The partial equality implementation is really surprising. One question about avoiding boolean parameters, I’ve just been using structs wrapping bools. But you can’t treat them like bools… you have to index into them like wrapper.0. Is there a way to treat the enum style replacement for bools like normal bools, or is just done with matches! Or match statements? It’s probably not too important but i…

I think you can do something like impl defref but not sure that's a good idea hah. Maybe it's a different trait I'm thinking of

Re: Patterns for Defensive Programming in Rust

#90
post #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…

In actual Java-Java (as opposed to Kotlin or something), first line of defense should be a linter that tries to prove nullability properties. In situations where that doesn't work, well, I think I'm the world's only fan of Java's assert keyword. If you can't use assert the language feature, you can at least throw AssertionError, which is a non-Exception Throwable subclass that's more likely to make your program die instantly, as it should, instead of treating the contract violation as a recoverable condition.
Post reply on HN