[HN Gopher] Patterns for Defensive Programming in Rust
       ___________________________________________________________________
        
       Patterns for Defensive Programming in Rust
        
       Author : PaulHoule
       Score  : 160 points
       Date   : 2025-12-05 16:34 UTC (6 hours ago)
        
 (HTM) web link (corrode.dev)
 (TXT) w3m dump (corrode.dev)
        
       | empath75 wrote:
       | This is one of the best Rust articles I've ever read. It's
       | obviously from experience and covers a lot of _business logic_
       | foot guns that Rust doesn't typically protect you against without
       | a little bit of careful coding that allows the compiler to help
       | you.
       | 
       | So many rust articles are focused on people doing dark sorcery
       | with "unsafe", and this is just normal every day api design,
       | which is far more practical for most people.
        
       | brohee wrote:
       | The very useful TryFrom trait landed only in 1.34, so hopefully
       | the code using unwrap_or_else() in From impl predates that...
       | 
       | Actually the From trait documentation is now extremely clear
       | about when to implement it (https://doc.rust-
       | lang.org/std/convert/trait.From.html#when-t...)
        
         | rolandog wrote:
         | As someone unfamiliar with Rust (yet! it's on my ever growing
         | list of things I'd like to absorb into my brain),
         | unwrap_or_else() sounds like part of the "What You See Is What
         | I Threatened the Computer To Do" paradigm.
        
           | Y_Y wrote:
           | > INTERCAL has many other features designed to make it even
           | more aesthetically unpleasing to the programmer: it uses
           | statements such as "READ OUT", "IGNORE", "FORGET", and
           | modifiers such as "PLEASE". This last keyword provides two
           | reasons for the program's rejection by the compiler: if
           | "PLEASE" does not appear often enough, the program is
           | considered insufficiently polite, and the error message says
           | this; if it appears too often, the program could be rejected
           | as excessively polite.
        
             | strbean wrote:
             | Immediately thought of INTERCAL :)
        
           | wongarsu wrote:
           | There are also the equally threatening and useful
           | `map_or_else` (on Result and Option) and `ok_or_else` (on
           | Option and experimentally on bool)
        
       | J_Shelby_J wrote:
       | 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 if we could treat them like
       | normal bools it'd feel nicer.
        
         | jvanderbot wrote:
         | I almost always prefer enums and matches! vs bool parameters.
         | Another way is to implement a Trait that you find useful that
         | encapsulates the logic. And don't forget you can do impl <Enum>
         | {} blocks to add useful functions that execute regardless of
         | which member of the enum you got.                   enum
         | MyType{                  ...                  }
         | impl MyType{             pub fn is_useable_in_this_way(&self)
         | -> bool{                 // possibly ...                 match
         | self {...}             }         }
         | 
         | and later:                   pub fn use_in_that_way(e: MyType)
         | {             if e.is_useable_in_this_way() {...}         }
         | 
         | Or if you hate all that there's always:                   if
         | let MyType::Member(x) = e {             ...         }
        
           | J_Shelby_J wrote:
           | If let is probably the closest to a regular bool.
           | 
           | For ints you can implement the deref trait on structs. So you
           | can treat YourType(u64) as a u64 without destructing. I
           | couldn't figure out a way to do that with YouType(bool).
        
       | ggirelli wrote:
       | Loved the article, such a nice read. I am still slowly ramping up
       | my proficiency in Rust and this gave me a lot of things to think
       | through. I particularly enjoyed the temporary mutability pattern,
       | very cool and didn't think about it before!
        
         | aw1621107 wrote:
         | > I particularly enjoyed the temporary mutability pattern, very
         | cool and didn't think about it before!
         | 
         | It's not too uncommon in other languages (sometimes under the
         | name "immediately invoked function expression"), though
         | depending on the language you may see lambdas involved. For
         | example, here's one of the examples from the article ported to
         | C++:                   auto data = []() {             auto data
         | = get_vector();             auto temp = compute_something();
         | data.insert_range(data.end(), temp);
         | std::ranges::sort(data);             return data;         }();
        
       | stouset wrote:
       | Good article, but one (very minor) nit I have is with the
       | PizzaOrder example.                   struct PizzaOrder {
       | size: PizzaSize,             toppings: Vec<Topping>,
       | 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 to compare only
       | the specific fields you care about, you'd decompose this into two
       | structs:                   #[derive(PartialEq, Eq)]
       | struct PizzaDetails {             size: PizzaSize,
       | toppings: Vec<Topping>,             crust_type: CrustType,
       | ... // additional fields         }              #[derive(Eq)]
       | struct PizzaOrder {             details: PizzaDetails,
       | ordered_at: SystemTime,         }              impl PartialEq for
       | PizzaOrder {             fn eq(&self, rhs: &Self) -> bool {
       | self.details == rhs.details             }         }
       | 
       | I get that this is a toy example meant to illustrate the point;
       | there are certainly more complex cases where there's no clean
       | boundary to split your struct across. But this should be the
       | first tool you reach for.
        
         | hurril wrote:
         | 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 projecting the
         | details.
        
           | zozbot234 wrote:
           | You can solve this in the general case by implementing the
           | typeclass for the coarser equality relation over an ad-hoc
           | wrapper newtype.
        
             | hurril wrote:
             | Well it isn't a good call. This is the kind of code that
             | OOP makes people write.
        
           | stouset wrote:
           | I do agree that implementing PartialEq on orders in this way
           | is a bad fit. But it _is_ a synthetic example to make a
           | point, so I tried to keep it in the spirit of the original
           | article (while ironically picking nits in the same vein
           | myself).
        
           | tialaramex wrote:
           | Yeah, I immediately twitched when I saw the PartialEq
           | implementation. Somebody is going to write code which finds
           | the "correct" order and ends up allowing someone to order the
           | same pizza but get yours, while you have to wait for it to be
           | made and cooked again.
           | 
           | It's not difficult to write the predicate same_details_as()
           | and then it's obvious to reviewers if that's what we meant
           | and discourages weird ad-hoc code which might stop working
           | when the PizzaDetails is redefined.
        
       | perching_aix wrote:
       | This made me wonder, why aren't there usually teams whose job is
       | to keep an eye on the coding patterns used in the various
       | codebases? Similarly like how you have an SOC team who keeps
       | monitoring traffic patterns, or an Operations Support team who
       | keeps monitoring health probes, KPIs, and logs, or a QA who keeps
       | writing tests against new code, maybe there would be value to
       | keeping track of what coding patterns develop into over the
       | course of the lifetime of codebases?
       | 
       | Like whenever I read posts like this, they're always fairly
       | anecdotal. Sometimes there will even be posts about how large
       | refactor x unlocked new capability y. But the rationale always
       | reads somewhat retconned (or again, anecdotal*). It seems to me
       | that maybe such continuous meta-analysis of one's own codebases
       | would have great potential utility?
       | 
       | I'd imagine automated code smell checking tools can only cover so
       | much at least.
       | 
       | * I hammer on about anecdotes, but I do recognize that sentiment
       | matters. For example, if you're planning work, if something just
       | _sounds_ like a lot of work, that 's already going to be
       | impactful, even if that judgement is incorrect (since that
       | misjudgment may never come to light).
        
         | vlovich123 wrote:
         | There are. All the big tech companies have them. It's just
         | difficult to accomplish when you have millions of lines of
         | code.
        
           | perching_aix wrote:
           | Is there an industry standard name for these teams that I
           | somehow missed then?
        
             | tczMUFlmoNk wrote:
             | You may wish to search for "readability at Google". Here is
             | one article:
             | 
             | https://www.moderndescartes.com/essays/readability/
             | 
             | (I have not read this article closely, but it is about the
             | right concept, so I provide it as a starting point since
             | "readability" writ large can be an ambiguous term.)
        
               | mattarm wrote:
               | See https://abseil.io/tips/ for some idea of the kinds of
               | guidance these kinds of teams work to provide, at least
               | at Google. I worked on the "C++ library team" at Google
               | for a number of years.
               | 
               | These roles don't really have standard titles in the
               | industry, as far as I'm aware. At Google we were part of
               | the larger language/library/toolchain infrastructure org.
               | 
               | Much of what we did was quasi-political ... basically
               | coaxing and convincing people to adopt best practices,
               | after first deciding what those practices are. Half of
               | the tips above were probably written by interested people
               | from the engineering org at large and we provided the
               | platform and helped them get it published.
               | 
               | Speaking to the original question, no, there were no
               | teams just manually reading code and looking for
               | mistakes. If buggy code could be detected in an automated
               | way, then we'd do that and attempt to fix it everywhere.
               | Otherwise we'd attempt to educate and get everyone to
               | level up their code review skills.
        
               | perching_aix wrote:
               | This is a really cool insight, thank you!
               | 
               | > Half of the tips above were probably written by
               | interested people from the engineering org at large and
               | we provided the platform and helped them get it
               | published.
               | 
               | Are you aware how those engineers established their
               | recommendations? Did they maybe perform case studies? Or
               | was it more just a distillation of lived experience type
               | of deal?
        
       | schneems wrote:
       | This was posted with a (mostly) healthy discussion on lobste.rs,
       | here's the link
       | https://lobste.rs/s/ouy4dq/patterns_for_defensive_programmin...
        
       | emschwartz wrote:
       | Indexing into arrays and vectors is really wise to avoid.
       | 
       | The same day Cloudflare had its unwrap fiasco, I found a bug in
       | my code because of a slice that in certain cases went past the
       | end of a vector. Switched it to use iterators and will definitely
       | be more careful with slices and array indexes in the future.
        
         | 1718627440 wrote:
         | Funny, it's really the same thing, why Rust people say we
         | should abandon C. Meanwhile in C, it is also common to hand out
         | handle instead of indices precisely due to this problem.
        
           | kstrauser wrote:
           | It's pretty similar, but writing `for item in container {
           | item.do_it() }` is a lot less error prone than the C
           | equivalent. The ha-ha-but-serious take is that once you get
           | that snippet to compile, there's almost nothing you could
           | ever do to break it without also making the compiler scream
           | at you.
        
       | Dowwie wrote:
       | I'm not reading a solid argument as to not use "..Defaults()"
       | because doing so suggests that you may introduce a bug and
       | therefore should be explicit about EVERYTHING instead? Ugh. Hard
       | disagree.
        
       | quotemstr wrote:
       | 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 good.
       | 
       | That said, I wouldn't use the word "defensive" to describe it.
       | The term lacks precision. The above good practice ends up getting
       | mixed up with the bad "defensive" practices of converting
       | contract violations to runtime errors or just ignoring them
       | entirely --- the infamous pattern in Java codebases of scrawling
       | the following like of graffiti all over the clean lines of your
       | codebase:                 if (someArgument == null) {
       | throw new NullPointerException("someArgument cannot be null");
       | }
       | 
       | That's just noise. If someArgument can't be null, let the program
       | crash.
       | 
       | Needed file not found? Just return ""; instead.
       | 
       | Negative number where input must be contractually not negative?
       | Clamp to zero.
       | 
       | Program crashing because a method doesn't exist? if not:
       | hasattr(self, "blah") return None
       | 
       | People use the term "defensive" to refer to code like the above.
       | They programs that "defend" against crashes by misbehaving. These
       | programs end up being flakier and harder to debug than programs
       | that are "defensive" in that they continually validate their
       | assumptions and _crash_ if they detect a situation that should be
       | impossible.
       | 
       | The term "defensive programming" has been buzzing around social
       | media the past few weeks and it's essential that we be precise
       | that
       | 
       | 1) constraint verification (preferably at compile time) is good;
       | and
       | 
       | 2) avoidance of crashes at runtime at all costs after an error
       | has occurred is harmful.
        
         | kstrauser wrote:
         | For a second I thought you were advocating for something of
         | those, and I had a rant primed up.
         | 
         | Yes. Defensively handle all the failure modes you know how to
         | handle, _but nothing else_. If you 're writing a service daemon
         | and the user passes in a config filename that doesn't exist,
         | _crash_ and say why. Don 't try to guess, or offer up a default
         | config, or otherwise try to paper over the idea that the user
         | asked you to do something impossible. Pretty much anything you
         | try other than just crashing is guaranteed to be wrong.
         | 
         | And for the love of Knuth, don't freaking clamp to zero or
         | otherwise convert inputs into semantically different value than
         | specified. (Like, it's fine to load a string representation of
         | a float into an IEEE754 datatype if you're not working with
         | money or other exact values. But don't parse 256 as 255 and
         | call it good enough. It isn't.)
        
       | Sunscratch wrote:
       | Nice article. The problem of multiple booleans is just one
       | instance of a more general problem: when a function takes
       | multiple arguments of the same type (i32, String, etc.). The
       | newtype pattern allows you to create distinct types in such cases
       | and enforce correctness at compile time.
        
       | lilyball wrote:
       | In Pattern: Defensively Handle Constructors, it recommends using
       | a nested inner module with a private Seal type. But the Seal type
       | is unnecessary. The nested inner module is all you need, a
       | private field `_private: ()` is only settable from within that
       | inner module, so you don't need the extra private type.
        
       | thatoneengineer wrote:
       | Aside from just being immensely satisfying, these patterns of
       | defensive programming may be a big part of how we get to quality
       | GAI-written code at scale. Clippy (and the Rust compiler proper)
       | can provide so much of the concrete iterative feedback an agent
       | needs to stay on track and not gloss over mistakes.
        
       ___________________________________________________________________
       (page generated 2025-12-05 23:00 UTC)