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? :)
Patterns for Defensive Programming in Rust
81–90 of 99 posts
Re: Patterns for Defensive Programming in Rust
#82Earlier 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 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
#83Rust 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
#84Earlier 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.
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
#85This 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…
Yeah, that originally turned me off from the language entirely. I also changed my mind eventually.
Re: Patterns for Defensive Programming in Rust
#86In 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.
As you said, calling first() is a far better approach.
Re: Patterns for Defensive Programming in Rust
#87What'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…
Re: Patterns for Defensive Programming in Rust
#88Good 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…
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
#89Wow 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…
Re: Patterns for Defensive Programming in Rust
#90The 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…