Hacker Dudes// alternate deck. same feed.
FEED ~8/h Δ0 ────:──── LINK ░ SIGNAL
100%
SPOOLED 0/45 CYCLE 61339.865.151.44
Rust Style Guidegoogle.github.io
390pts/230 comments/5d/─
by kaycebasques / hn> / « feed
▲▼ Thanemate19:35 hn>

Not trying to rain on someone's parade but, the only reason why would care about style guides nowadays is to feed them to our model of choice and let it do what it's supposed to be doing.

So it's not like I am willing to read through this and think to myself "I cannot wait to pick up some new ways of doing things". I'm the general to my agentic army now.

I guess you would code manually every now and then for your own enjoyment but at that point do you care about style guides written for scale?

▲▼ gatlin19:45 hn>

Seems very much like your goal was to rain on a parade.

▲▼ u1hcw9nx19:47 hn>

Not trying to rain on someone's parade, but the only reason I would care about HN comments nowadays is to feed them to my model of choice and let it generate the contrarian hot takes it's supposed to be generating. So it's not like I am willing to read through this thread and think to myself "I cannot wait to pick up some new ways of complaining about documentation." I'm the Editor-in-Chief to my synthetic pundit army now.

I guess you would type out a comment manually every now and then for your own enjoyment, but at that point do you really care about comments at scale?

---

EDIT (above is a joke) It's very important that every time you find something that is of no use to you, you write a comment explaining that this link is of no interest to you, discouraging people from things you have no interest in. Monoculture for all!

▲▼ anonymous90821319:48 hn>
>I'm the general to my agentic army now.

What have you produced with your agentic army? Would you like to share with the class?

▲▼ bigfishrunning20:04 hn>

my guess is a lot of heat, some water vapor....

▲▼ solyin20:33 hn>

Why would they when the class is full of dropouts who, generously, have a 0 percent chance of going "hey, y'know what, that's actually kinda cool! I'm going to change my mind about LLMs being useless" and a 100% chance of going "ur project is total shit and you should feel bad".

─ 9 more ─
▲▼ Thanemate06:21 hn>

I do not wish to share proprietary code that I'm being paid to boss agents around to write.

Since I'm detecting snarkiness in your words, let me clarify something important: How convincing something you read is doesn't have to match with how true it is.

▲▼ monocasa19:50 hn>

The point of style guides is to optimize for readability despite a cognitively diverse workforce coding in the same repos.

I would think they're probably even more meaningful in an agentic world.

Shitty generals can't review meaningfully review the work of their troops.

On top of that all, I just enjoy programming itself. It's less and less of the meat of a software job over time, but I was coding before I got paid for it, and I'll probably keep coding well after I can get paid for it.

▲▼ mkagenius20:11 hn>
>Shitty generals can't review meaningfully review the work of their troops.

Except that reviewing has become just asking your agent what that piece of code does in english. Reading code has a bit of cognitive load, that we will probably drop it in coming years

─ 2 more ─
▲▼ smoe19:51 hn>

First paragraph: “includes guidance on API and implementation design”

It seems to me that if the intent is not just to produce code but also to operate it, having the different pieces behave in a similar, predictable way is kind of nice. Whether the software is written by AI or humans.

▲▼ nimih19:55 hn>

I'm not sure I really agree with your main point (I think it's probably still important for LLM-generated code to be generally human-understandable, at least if you care about long-term maintainability), but setting that aside to answer this question:

>I guess you would code manually every now and then for your own enjoyment but at that point do you care about style guides written for scale?

I think they're still quite valuable in that case, for a couple reasons:

- When learning a new language (which happens much more frequently when writing code "for pleasure," as it were), "written for scale" guidelines still translate pretty well to "written for oneself" IME. At the very least, they tell you what to expect in code you read, and give you a reasonable starting point as you develop your own taste and stylistic preferences.

- I've noticed some open source projects beginning to institute anti-AI contribution guidelines, and such projects still benefit from codified style guides. Google obviously doesn't fall under this category themselves, but in other language ecosystems, their style guides tend to find use in OSS projects.

▲▼ petersellers19:56 hn>

Are you saying that you don't ever look at the code once it's been generated?

▲▼ bigstrat200320:54 hn>

AI bros think you can never look at code and make good software. They're wrong, of course, but they refuse to listen and will probably never listen until the shoddy quality of their work becomes too great to paper over.

─ 2 more ─
▲▼ yodsanklai19:59 hn>

This is what we do in my company, give the guidelines to the model. Unfortunately, the model isn't always following them and it's impossible to enforce them manually. There's too much code to review, the code is generally low quality, and is more often than not auto-approved by AI.

Unless you're working on your own project on which you have full control, or you have a strong authority figure in your company who cares about that, it's a lost battle.

It's sad for anyone who love their craft, but tbh I'm not even sure it matters anymore for most projects.

▲▼ scottyah22:12 hn>

Either you can put it in a linter and automate/force it, or people/agents will take liberties. I feel like there's usually exceptions where a well-meaning guideline doesn't properly fit a specific scenario.

▲▼ oddCopper6519:47 hn>

The enum-over-bool rule is the one I'd keep even solo, saved me a reversed flag in a cert validator once.

▲▼ sebastianconcpt20:00 hn>

This is a sub-case of the general one that is narrowing the interpretation of things. A big infinite amount of things can be true or false, only one can be a Rust variant.

▲▼ petcat19:55 hn>

At this point, is anybody writing new C or C++? It seems like they are sunset languages even at the big corps.

▲▼ tkz131219:58 hn>

I've been extracting a lot of verified C from theorem provers recently.

▲▼ neutronicus20:01 hn>

C++ seems alive and well in the defense space. The big contractors near me have tons of C++ job postings and I don't think I've seen a single one looking for Rust.

▲▼ thomashabets220:07 hn>

Do you know if the code bases are of the kind where dynamic memory (among many other features) is disallowed?

─ 2 more ─
▲▼ debo_00:02 hn>

C++ requires defensive programming, so I guess it fits that defense requires C++ programming.

▲▼ tormeh00:33 hn>

The new drone companies seem to favor Rust more. Helsing sponsors a lot of Rust conferences and stuff. Quantum Systems has job postings for embedded Rust. For companies that don't have a lot of legacy influencing their decisions, Rust is pretty much always going to be in their software stack. The big primes will probably come around to it in a decade or so.

─ 1 more ─
▲▼ loeg20:04 hn>

Plenty of new C++ going on at my BigCorp employer.

▲▼ fasterik20:04 hn>

I'm pretty sure most big software projects (Windows, Linux, Chrome, gcc, LLVM, AAA game engines, ...) are still writing a lot of new C and C++. Pretty much everyone in the embedded world is as well.

As a solo programmer, I still prefer C and C++. In fact, as I've incorporated LLMs in to my workflow, I've been increasingly drawn toward C and its simplicity. A lot of the QoL features of C++ simply aren't needed when you're writing less code manually.

▲▼ anthk23:19 hn>

Chrome should be ported to Rust long ago. on Windows, half of the UI can migrate tomorrow with ease.

─ 7 more ─
▲▼ loeg00:53 hn>

Using LLMs to write C feels a little silly to me. (Unless you really need C.) LLMs are still imperfect; you're still going to get a lot of UB bugs like human-authored C.

─ 2 more ─
▲▼ skohan20:13 hn>

C/C++ is still relevant for HPC, where you want to carefully optimize memory management.

▲▼ vlovich12320:59 hn>

Hate to break it to you but Rust is equally capable of doing all the same memory management optimizations.

─ 16 more ─
▲▼ IshKebab20:20 hn>

Yes mine is, because they have a huge amount of C++ already and none of it is publicly accessible so security isn't a concern. (Obviously there are many other reasons to prefer Rust, but that is a big one.)

▲▼ npalli20:26 hn>

Far from it. Until Rust can read C++ code seamlessly, there will be more code written in C++ than Rust for the foreseeable future. HPC, Finance, AI, Games, Databases, Hardware all continue to be written in C++.

▲▼ simonask23:05 hn>

Serious people in all of those industries are thinking about whether and how Rust is a fit for their needs.

Like, I agree that C++ will be around for a long while, but the writing is on the wall.

─ 19 more ─
▲▼ j1elo20:39 hn>

Today I went over a TODO comment from 2007 in a C++ codebase that is still evolving very vividly, year after year. Trust me, for well established projects C++ is there to stay.

▲▼ pjmlp20:54 hn>

Games, anything that depends on LLVM, GCC, CUDA, SYSCL,....

Ah and Rust's compiler backend dependency on LLVM and in the future GCC as well.

▲▼ xedrac21:00 hn>

Unfortunately for me, yes. Some companies are very risk adverse and would rather stick with the devil they know. They have no idea what they are sacrificing.

▲▼ pornel21:16 hn>

Cloudflare is all in on Rust, it's used everywhere by default, and C++ leftovers are being actively torn down.

▲▼ pjmlp06:44 hn>

So they will remove LLVM from rustc and throw away V8?

─ 5 more ─
▲▼ nchie07:22 hn>

Going all in on Rust doesn't have to mean you'll rewrite everyone's firmware drivers in Rust, nor does it mean rewriting everyone's OS in Rust or rewriting the platform on which Rust is compiling.

─ 1 more ─
▲▼ m00dy03:56 hn>

Rust is eating C/C++.

▲▼ pjmlp06:43 hn>

While depending on a compiler framework written in C++, the irony.

─ 13 more ─
▲▼ geertj04:27 hn>

In a few years, Claude Mythos will be remembered as the moment C++ died. I am serious, very senior people are really scared of LLM found vulnerabilities and are pushing strongly for Rust.

▲▼ zelphirkalt06:30 hn>

In a few years probably no one will be thinking or talking of Mythos any longer, as it is just the next marketing item in the never ending chain of items to be released as the next scary thing.

▲▼ redox9904:37 hn>

Console/PC gaming is mostly C++ (and scripting/blueprints/etc).

▲▼ 59nadir09:01 hn>

I have been porting our Odin code to C(23) lately because it's better than anything else I've found in terms of language simplicity + tooling quality. 75% of our new code right now is C and in my estimation that'll be 100% in about a month, and we'll likely be done porting ~150 kLoC into a similarly sized C code base.

Even disregarding tooling I'm actually happier writing C than (even) Odin because it's really just a natural end state of changes I'd made to how I write code over the past 7-8 years and what I expect from a language (to not do much, and to not get in the way).

It's hard to compete with the tangibles of writing/using C:

- Somehow it has shorter compile times than everything else still, particularly for release builds?

- Everything that matters (to me) has C bindings.

- All the tools just work and they work well (and a lot of them are made specifically for C anyway).

- The compilation model is actually better, more straight forward and easier to make fast than languages that try to "fix" it, unless you sit there fighting it and overcomplicate it with overwrought build systems. (`make` is better than I used to give it credit for, though).

I think Ada is the only language that could peel my attention away from C at the moment, which is ironic given my "Don't do much, get out of my way" stance. It turns out you can do more if you're sensibly designed, have the right escape hatches and actually provide real, useful features (basically every feature regarding arrays in Ada and their interplay with other features like index types, etc.). Ada is not as easy to port from Odin, though, and Ada would be a much bigger gamble than C in so many ways. We don't mind writing the code we use ourselves (we in fact prefer it), but in Ada a lot of that code would be decidedly alien to how Ada seems to work in many ways.

With that said, I do have an Ada port of a game I was working on that does illustrate a lot of upsides. It's a different possible end state.

P.S. I've tried Zig, I wrote Zig for several years before I started using Odin, it's what got me back to imperative, small, native languages. It's what started this journey back to C.

▲▼ tialaramex13:01 hn>

But why not write assembly then?

If you've sworn off actually getting any value from the programming language so much that you even went from Odin to C it feels like just writing raw assembler is a sensible choice at this point?

─ 4 more ─
▲▼ flossly21:22 hn>
>P.S. I've tried Zig [...]

Why not Zig then, and C instead?

As a C/Zig outsider, I'd much rather read Zig.

─ 1 more ─
▲▼ mahboi20:19 hn>

Google probably, also lots of video game devs

▲▼ monocasa19:59 hn>

Can anyone expand on the chromium specifc guidelines on super tiny crates, particularly no nesting of modules/source files?

That feels like the ~2010 era of people saying that functions should be as small as possible. That lead me to reviewing a lot of code where every function was one or two lines and you couldn't actually see what was going on anymore through the layers of unneeded abstraction.

I want to give chrome the benefit of the doubt, but that seems really goofy.

▲▼ loeg20:04 hn>

Maybe one or two line functions are a totally different scale than "all source code files in a crate"? So like, one is problematic in a way the other is not. And the guidance is "prefer small," not "always super tiny." See: "Larger crates are allowed" later on. And also see: "Rationale: Fine-grained library targets are recommended in Chromium due to the massive size of our codebase, non-local effects of adding dependencies in the build graph, and to improve performance of parallel compilation and build caching."

▲▼ dnautics20:11 hn>

Compilation speed?

▲▼ skohan20:12 hn>

It might have to do with optimizing compile times. A crate is the unit of compilation, so small crates increase the likelihood that you won't end up recompiling a lot of unrelated code.

I guess smaller crates and avoiding nested modules could also help optimize for making the code greppable by coding agents without blowing up context windows.

▲▼ evmar20:24 hn>

In Cargo-built Rust, a crate is costly in terms of abstraction because it needs its own directory, Cargo.toml, src/ subdir, etc. In the Google build systems I've worked with in the past (and it looks like Chromium follows) they instead put source files in the same directory mixing languages and with finer-grained libraries as they see fit.

Given that a crate is the unit of build parallelization in Rust, there's a benefit to more crates especially when they don't cost much cognitive overhead. You could imagine using crates in the way you'd typically use separate mods in a normal Rust project.

I dug around in their code search to find some examples but didn't see much Rust in there, seems still early. I think https://source.chromium.org/chromium/chromium/src/+/main:tes... has ~4 Rust files that end up separate crates though. (One of them defines a proc macro so even in a Cargo built Rust project it would need to be in its own separate crate, I think!)

▲▼ evmar20:26 hn>

It looks like there's some macro-based import that does import by path, so crate names are generated:

https://source.chromium.org/chromium/chromium/src/+/main:tes...

▲▼ i2talics20:39 hn>

The granularity of crates shouldn't really impact what kinds of abstractions you build. You can still have as many modules and types as you need, and as big of functions as you need, but its saying you should organize the modules into separate crates instead of one large crate. They even call out the trait coherence rules, saying that you can just make your library bigger instead of forcing a more awkward design in the pursuit of a smaller crate.

I think one benefit of small crates is that it gives you more granularity in linking and dependency graphs, which is especially relevant for rebuilds. At a previous job we had a monolithic "utilities" crate, and this caused problems when you tried to depend on it for the IP address utilities but this brought in the radio frequency utilities. When one of these submodules got an update the versioning would excessively propagate downstream to every consumer. If this was organized as small "net-utils" and "rf-utils" crates then maybe it would not have been as much of an issue.

▲▼ vvillena21:07 hn>

This being Google, I assume everything goes into a single gigantic monorepo that gets thrown at Blaze. If the unit of compilation is the crate, it makes sense for them to prefer that crates are as small as possible.

▲▼ pornel21:30 hn>

You get stricter enforcement of public APIs and dependencies. You can't import anything you haven't declared as a dep, and cycles are impossible. Rustdoc by default shows only your intended public API.

OTOH if you have one crate with lots of modules, it's tempting to import anything anywhere and use pub(crate). Rustdoc needs to be configured to show private items if you want to see internal APIs.

▲▼ tcdent21:46 hn>

Exactly. Being able to organize your code in a way that provides distinct responsibilities per crate is just good practice.

And then the ability to enforce a formalized layer between crates and the way that they assemble into your application (i.e. the public API) is powerful for comprehension and maintainability. I go as far as documenting each crate with its own README that presents it as a standalone component with usage instructions, much like an open source library, even if it's only ever used in a single system.

▲▼ skavi22:38 hn>

Forces better code structure in my experience (ignoring orphan rule). modules can have dependency cycles and things get tangled together. crates disallow cycles.

▲▼ hugmynutus02:55 hn>

blaze/bazel doesn't support incremental crate compilation. It also can't, by design, as incremental compilation depends on incremental change to local build artifacts which isn't deterministic.

So a single crate has to be as small as possible so builds are fast & cachable.

▲▼ JoeDohn20:04 hn>

This is nice, I have been lurking on google and meta rust projects, mimicking and picking style by prompting LLMs to distill it, taking a look at fmt & clippy configs helps too.

But it wasn't as coherent and reliable from project to project like this guide. Kudos to google on this one !

▲▼ i2talics20:29 hn>

Much of this is very standard across the Rust ecosystem but it's very helpful to have it all in one place.

▲▼ incrediblers20:33 hn>

Getters and setters is the one I'm most at disagreement with.

   // Mine
   let label = Label.text("Hello Wold");
   let label_text = label.get_text();

   // Theirs
   let label = Label.set_text("Hello Wold");
   let label_text = label.text();
A method is a way to accomplish a specific task. Emphasis on specific not generic. The getter task is to always return. The setter task is specific. So the method should be the setter - `text`, `color`, `fill`. In Rust the getter can't have same name, so prefix it. Not the other way around.

▲▼ i2talics20:43 hn>

Their style is pervasive in Rust, such as in the standard library, so I would personally apply a when-in-rome philosophy. FWIW something like `.text("hello")` I would expect to see in a builder-pattern but not in a setter for an object that's already built.

That being said I don't particularly understand your conception of a method. They are both methods. The getter ought to have a shorter name since it is probably called more frequently and because it inherently has less of an impact -- this goes along with the principle that names that demand attention should be larger and easier to notice.

▲▼ incrediblers21:04 hn>

My use case is a TUI framework (https://www.incredible.rs/). The setters configure elements in a chain-able manner. I grappled with the rust convention and decided to go the other way. Rational as mentioned.

But this made me think...

>"The getter ought to have a shorter name since it is probably called more frequently"

So i checked. Rough agent driven counts:

https://github.com/ronilan/incredible-flappy 407 setters and 183 getters.

https://github.com/ronilan/rusticon 546 setters and 5 getters

https://github.com/ronilan/shape-sorting 688 setters and 33 getters.

And in a code editor with a lot of ui configurations based on reusable elements 2865 setters and 206 getters.

In reactive apps on state handlers call on setters with state values. No getters involved...

─ 5 more ─
▲▼ trimbo20:55 hn>

This style is aligned with protocol buffers, which is aligned with the style of protos in C++.

https://protobuf.dev/reference/rust/rust-generated/

▲▼ epage21:17 hn>

I think which to use in general Rust code depends on the purpose of your type.

A builder type like `Command` will have unqualified setters and `get_` qualified getters.

For record-like-structs, I think unqualified getters becomes preferred and `set_` qualified setters.

▲▼ slaymaker190720:34 hn>

I have a huge disagreement here. You should absolutely be using Arc and Rc for any references *that live outside of the call stack.*

Don't do weird crap with lifetimes to try and convince the compiler that what you're doing is ok, just throw Rc/Arc on it and be done with it.

The Rc/Arc advice also applies to C++ IMO, though you use different constructs, std::shared obviously, but there is a strong argument to be made for a std::shared_st or something which is single threaded.

It would not surprise me if this advice comes from experience with C++ where there is no Rc so reference counting is very expensive. Even so, you can greatly reduce the expense by passing things by ref where lifetimes are simple which is the vast majority of the time.

▲▼ i2talics20:47 hn>

I think using refcounting can allow you to get lazy with your object graphs, throwing referencing to objects around everywhere and then losing the ability to conceptualize who owns what and who is responsible for who (so to speak). Sticking to a stricter ownership graph forces you to be more deliberate with how your types are designed and this can lead to better architecture in the long run.

▲▼ smj-edison15:38 hn>

The problem with lifetimes is they become a object tree, where Rc and co let you represent an object graph (well, a DAG at least). So when your problem resembles a tree, it's great, but some problems don't like being forced into that shape.

▲▼ wtetzner21:48 hn>

I find using lifetimes to be simpler and easier to follow than reference counting, unless you're in a situation where you actually need a shared lifetime.

Adding Rc/Arc is just another layer of indirection and noise that makes the code hard to read.

▲▼ mrbonner23:53 hn>

Agree. I think for the limited scopes such as in a mod/function, using Rust's borrow checker semantic makes sense as it is easier to fit in our mental model. If you have to pass those data structures around to the outside of your limited scopes, I would prefer Rc/ARc more. I also think the style guide has a catch all phrase there when it says "OK to use Rc/ARc depending on the complexity...". For me, having to pass a shared reference around is enough complexity to reason about.

▲▼ younglunaman20:37 hn>

ctrl+c > ctrl+v > into my AGENTS.md for my next rust project /s

▲▼ tcallred20:39 hn>

I really appreciate that they distinguish between library code and application code. Rust gets too much of a bad rap for its complicated generic code, but in most application scenarios you are not writing code nearly that complex or generic.

▲▼ holografix21:07 hn>

Things like this make me appreciate Gofmt so much

▲▼ japgolly02:46 hn>

But there's a rustfmt already. Anyway, the style guide is larger in scope than just code formatting.

▲▼ epage21:20 hn>
>Type names should be imported and used unqualified

This just pushes people to have redundant information in types (`IoError` instead of `io::Error`).

Unless a type is widely recognized (like `HashMap`), I prefer to have them fully qualified so I have the contextual meaning for it. If I am working with a generic name like `Source`, what does that mean? I shouldn't have to look up in the imports to understand the code (or only understand it through an LSP).

Reducing imports also reduces unnecessary merge conflicts.

▲▼ Blikkentrekker21:30 hn>

It says below that that in cases of things like `io::Error` it should be used as that though, as in “cases of ambiguities”. It specifically speaks of not using `regex::Regex` but just `Regex` which seems more reasonable to me.

▲▼ epage22:28 hn>

That guideline is in response to an API design. I'm speaking to how the default stance (import types) feeds back into API design.

▲▼ B-Con23:51 hn>

Google's style guides tend to reduce namespace redundancy.

Everyone remembers finding some hideous name like foo.widget.widgetBuilder.widgetFactory in Java or something. It's nice to have naming rules, or at least guidelines, focused on trimming redundancy and keeping the signal high.

─ 1 more ─
▲▼ wtetzner21:39 hn>

Honestly I think having blanket rules in either direction is a bad idea. Sometimes it makes sense to qualify things, sometimes it's clearer if you don't.

▲▼ boxed07:34 hn>

Having (hobby) worked with ObjectiveC/Cocoa and then later in Swift, I must say that although NSString looks ugly, it's glorious when it comes to trying to search for anything compared to String.

▲▼ egl202020:47 hn>

"This rule largely exists to avoid reviewer disagreements."

▲▼ mmooss21:44 hn>
>Usage of lowercase alphabets in hex literals (0xab123cdef as opposed to 0xAB123CDEF): hex_literal_case = Lower

Anytime numbers and letters are mixed, it's more readable if the letters are lowercase. It's easier to read the variable heights - numbers are usually capital letter height, sometimes with descenders - and avoids confusing 0O, I1, even 8B and 5S (think of a degraded printed serial number, poor lighting, etc.). There's only l problem that persists.

▲▼ IX-10322:41 hn>

OT, but it was interesting. Some old typewriters didn't have a separate 1 a lower case L key. They had keys for the other numbers, but you were expected to just type a lower case L for 1.

▲▼ egl202020:57 hn>

I learned to touch type using lower case l for 1. Later on I was "Look, ma, there's an actual number 1 on this computer keyboard."

▲▼ preommr21:52 hn>

Google has lost a lot of it's sheen for me. It's not really the 2010s anymore where their brand really used to mean something as a step above all the other tech companies.

Nowadays, I care about their recommendations as much as the ones from microsoft, or ibm.

Still big companies, lots of smart people, but nothing above norm.

▲▼ nsib9604:45 hn>

Which companies would you look up to nowadays? Genuinely curious

▲▼ yhavin05:41 hn>

Deserves its own Ask HN.

▲▼ chungus08:42 hn>

I'll kick it off:

Oxide computing.

▲▼ mahboi20:18 hn>

Cloudflare, OpenAI, Anthropic, and even Amazon each in its own way

▲▼ mahboi20:29 hn>

The impression I got when I worked at Google is that most their stuff was state of the art in like 2008 at the latest. There's always something they excel in at some point in time, like TF 1.0, but they're behind on all the less hyped areas and keep saying NIH.

▲▼ xiaodai22:22 hn>

why is google using github, a Micro$oft product?

▲▼ shaftway22:56 hn>

I 100% guarantee that this source of truth for this is either in google3 or git-on-borg and is being mirrored to GitHub for external use. There aren't a lot of public catch-all repos or public micro repos published by Google. Given that, GitHub is a reasonable choice.

▲▼ afdbcreid23:50 hn>

Here's a tip most Rust users don't know about extension traits: if you put `#[rust_analyzer::prefer_underscore_import]` above your trait (and you're using rust-analyzer and not RustRover), autocompletion and the import assist will import the trait `as _` by default, which is very useful for extension traits.

▲▼ hitekker00:15 hn>
>Don’t use async / .await in Chromium.

That's pretty direct. IIRC, the Rust for Linux people also have that refrain informally

▲▼ manlymuppet01:27 hn>

How useful would this be to simply feed to an LLM and say to follow strictly?

▲▼ threethirtytwo01:29 hn>

Not useful at all. It uses up context. You need all the context you can get for the LLM read the code later for you later.

▲▼ JimTheMan01:40 hn>

Could you just get a separate agent to do a pass over existing code and make conform? Like super linting.

Not a professional programmer, so it's a genuine question rather than a statement.

─ 2 more ─
▲▼ japgolly02:43 hn>

Apply #[non_exhaustive] to publicly-exposed enums seems like a crazy recommendation to me. So much of the power of enums is that when you add a new case the compiler will flag all pattern matches that you need to update, which is a good thing because you're forced to consider each case. Having a catch-all saves dev-time but it risks not appropriately handling new cases becoming a runtime issue instead of a compile time error.

▲▼ ninkendo03:29 hn>

The corollary to your point is that you have to be extremely cautious what enums you make into public API if you want to be serious about backwards compatibility… likely wanting to include more flexible variants like “unknown” or “generic”, to make your API more flexible. But this ends up being the same problem by a different name, because it still turns things into runtime bugs, just like non-exhaustive enums do.

It’s the eternal struggle between flexibility and strictness that doesn’t really have a silver bullet answer. Or maybe it’s better framed as “flexibility, safety, backwards compatibility, pick any two”?

▲▼ akshayshah03:36 hn>

Sensible attitude for a one-team codebase, but much harder in a google3-sized megarepo…plus the policy matches protobuf enum semantics.

▲▼ ekidd11:17 hn>

I can't speak for Google-sized organizations, but Protobuf enum semantics are an invitation to broken, flaky and incorrect systems.

There are reasons why Google pays this price! But I think it's one of the worst practices for much smaller organizations to adopt.

▲▼ i2talics03:41 hn>

It's a double edged sword because without #[non_exhaustive], adding a variant becomes a major semver breaking change. It depends on your priorities. I think that there is probably a subtle difference between enums that are expected to gain variants in minor releases (a common example is error types) and enums that are expected to be closed (something like Option or SocketAddr).

Honestly what I tend to see is that most enums are not exposed publicly at all besides maybe error types and configuration options (where the user usually constructs but does not match on them). In both cases #[non_exhaustive] is valid. For all of the non-public enums you get to enjoy the compiler exhaustiveness enforcement.

▲▼ hmry12:03 hn>

Without #[non_exhaustive], adding a variant is always a semver breaking change.

With #[non_exhaustive], adding a variant is not a breaking change, but only if that variant can safely be ignored. If it can't be, then it's still a breaking change, BUT you don't get a compiler error. That's a very big drawback IMO.

This makes me think of how the PNG format handles this. For context, PNG files consist of a list of chunks of different types (image data, color profile, text comment, etc). Each chunk has some flag bits that tell you whether it can be safely ignored when decoding the image, safely retained when the image data is changed, etc. That way the decoder can ignore unknown ignorable chunks and error on unknown non-ignorable ones.

▲▼ zeroCalories04:50 hn>

Why? Their justification seems reasonable to me. If other teams depend on your enum then you could end up in two really bad cases. First is that you end up having to fix a ton of code just to update your enum, which has it's own headaches. Second is another team's build starts to fail when they update dependencies. Horrible churn to cause for your org. Better to just have them design around non_exhaustive.

▲▼ alfiedotwtf06:20 hn>
>you could end up in two really bad cases. First is that you end up having to fix a ton of code just to update your enum, which has it's own headaches. Second is another team's build starts to fail when they update dependencies

If you really don’t care about compiler warnings, what’s the actual point of using Rust?

▲▼ zelphirkalt06:20 hn>

The first one is not a "really bad case", but the intention of having a strict enum. You want to be force to update, in order to not miss any places, where the change could cause an error. That's the very benefit of it.

Second case is the same. The other team needs to definitely know, that their assumptions expressed in their current code are broken and need to be adjusted.

We just need to stop to take whatever shit Google is doing as the one bar to measure all others, and need to understand that they may be different requirements than a typical business. We are not Google.

─ 4 more ─
▲▼ Pavilion209506:24 hn>

There is already a catch-all option if you don't need exhaustiveness in some part of your code.

▲▼ Groxx05:05 hn>

It's usually not too hard to work around stuff like this - wrap it and map it to an exhaustive type, and use tests to ensure that your `unreachable!()` is correct.

▲▼ quotemstr05:50 hn>

This is from the people who make mandatory protobuf fields "optional".

I just fundamentally disagree with Google on how to evolve data structures. Sloppy open-domain protobuf field presence, enum values, whatever are illusory extension points. Better to have discrete versions of a data structure --- Foo1, Foo2, Foo3, etc., and rigid checking in each one than to have just one "Foo" with optional-everything that changes implicitly over time.

▲▼ rendaw06:00 hn>

What people seem to be missing here is that enums are just a reflection of functionality. If you make your enum non exhaustive then add a field, and something stops working because it's now missing critical messages, it's still a breaking change!

It's always up to callers to decide to handle everything, or decide that additions are non-fatal and use a wildcard branch to ignore them. The same enum could need to be exhaustive for one use case, or subset matched by another.

▲▼ alfiedotwtf06:16 hn>

Adding a field and it stops compiling > adding a field and it stops working

▲▼ froh07:23 hn>

can't you explicitly express a catch-all in a check?

edit: I don't know rust. in other languages he's such catch all patterns exist and you have to either capture each possible value explicitly or you express "here in this fu, I take care of these and all others I do that..."

─ 2 more ─
▲▼ eptcyka07:51 hn>

Swift gas a facility for this type of thing, Rust could adopt it too.

▲▼ Thom200006:16 hn>

FWIW this future lint `non_exhaustive_omitted_patterns` will help a bit: https://github.com/rust-lang/rust/issues/89554

▲▼ smallnamespace09:29 hn>
>the compiler will flag all pattern matches that you need to update

At Google, the bottleneck is that a team changing a commonly used enum will not know how to update all the broken use sites, since it can be affecting dozens or hundreds of teams.

It is better to decouple the enum change from the use site change and let consuming teams update on their own schedule.

▲▼ berkes09:40 hn>

I am not familiar with this flag, but will it force, or at least alert these " consuming teams" that the enum they expected has changed?

─ 1 more ─
▲▼ demurgos09:56 hn>

At scale, `#[non_exhaustive]` is the right default for public enums that are part of your API model when working on a distributed system. You should still be able to opt into exhaustive enums when needed but in practice it's way rarer.

I'm working at a company with a very large Rust codebase with thousands of endpoints. We use multiple repos and each service publishes its API as crates. A few years ago those were manually written and teams used exhaustive enums frequently in their API. It did not prevent runtime issues as older clients could receive responses with new variants from newer servers. It also made versioning painful because semver would dictate a major bump, but Rust's nominal typing means that types from different major versions get different identities, making it very inconvenient to integrate with these types (think shared helper functions, extension traits, ...). This caused people to release new variants in feature bumps and wreck havoc in the dependency tree by breaking compilation of intermediate libs.

Since then we switched to code generation from an IDL for the API model. I was leading this project and decided to produce non_exhaustive enums for the Rust code: it was 2 years ago and the situation drastically improved.

We have a CI step running lints with `non_exhaustive_omitted_patterns` mentionned in an other comment. That's the right approach for such a public API. The big difference is that is that warnings are local to the crate you're working on, so they're more actionable than errors.

This was all in the context of modeling the API of a distributed system. I still tend to use non_exhaustive more often now even for regular helper libraries. It always requires some thought as you need to plan for the forward compat of your code.

▲▼ techsystems13:20 hn>

Interesting! Thanks for the share

▲▼ infogulch14:45 hn>

Same-binary / external service seems to be the split.

If you're building one binary you can choose when to update and fix all the referents at the same time. A distributed system does not have that luxury and it makes more sense to require forward compatibility.

I guess another option is a two-phase update where you first update all the services to accept the new enum variant without ever emitting it, then you can phase in services that use it. The downside with this strategy is that you're locked into upgrades and you can't roll back individual service versions, so to be safe you probably need to delay use of the new enum variant by 2-3 releases (at some point rolling back is not a viable option and you have to publish fixes in a new release anyway).

So in a service world the only options are either "require forward compatibility" or "delay rollout of a new feature to 3 release cycles after all services in distributed system are updated" which is like a month minimum. I can see why forward compatibility is the default, and also that you might want to keep the two-phase deployment strategy for certain cases.

▲▼ kibwen13:15 hn>

It's important to note that within the compilation unit where the enum is defined, a non_exhaustive enum doesn't require the catch-all arm when matching. In other words, this code compiles and checks for exhaustiveness as usual:

    #[non_exhaustive]
    enum Foo {
        Bar,
        Baz
    }
    
    fn main() {
        let foo = Foo::Bar;

        match foo {
            Foo::Bar => (),
            Foo::Baz => ()
            // no `_` arm necessary here
        }
    }
The non_exhaustive attribute only applies to users outside of the compilation unit, because it's a tool for expressing a promise about the external API.

▲▼ williamdclt13:18 hn>

I agree with you, although other responders make good points.

Something I've been wanting in Typescript (and I guess it's the same thing here in Rust) is to type-check exhaustiveness of the known enum values, but also enforce that unknown values are handled. I've made utils for that but they're a bit awkward to use compared to native constructs like switch/case.

▲▼ wocram05:17 hn>

There's something ironic with having project specific variants in a style guide.

▲▼ eviks05:20 hn>
>out of automatic formatting using #[rustfmt::skip], but only in the following situations:
>Manual alignment

Guess, another decade before Rust robots can do columns?

▲▼ eugenedonin2509:00 hn>

Very nice post

▲▼ solatic11:26 hn>

How many of these could be enforced with a linter?

"Prefer enum over bool in APIs: Use enum function parameters instead of bool in cases where the purpose of the parameter is not obvious. If you do use bool, consider using an inline comment to document the purpose of the bool in an argument list."

So.... build a linter that refuses boolean parameters, and if there's an exception, then it'll be in the comment that disables the linter for that particular parameter?

And if it's in a linter then you find out waaaaaay faster compared to rolling the dice on whether the human reviewer remembers the rule or not?

▲▼ durdn14:44 hn>

and even more interesting, how many that can't be enforced by a linter can be enforced by Typesafe's Jev?

▲▼ dathinab13:44 hn>

Most important reading context:

- this are rules created to be (kinda/mostly) uniformly applied across all projects at a Google Scale company

Having used rust at work since roughly ~1.0 days I have come to a realization:

- the style guides which work best for "blindly uniformly applied across all kind of projects/teams/people" are not the same as "my preferred style" _in a lot of ways_

- similar they are often not "the best" in every case but they are "uniformly applicable without having to make opinionated decisions" i.e. they don't cause frictions in review or when writing the code due to "style only"-decision without a clear good answer

As a more generic example. There are a lot of frameworks/style guides which lead to related logic being split across 3+ small files in different folders instead of having related code "close together". And this can be a huge pain and isn't how I would manage personal projects. BUT putting code close together will sometimes just not work for various reasons (often shared logic/cross cutting concerns) at which point you will have to make some compromise where/how to place the code which has no simple "no brain used schema-f" solution you can blindly apply no matter what. This can lead to friction. Once too many people touch the same code this tend to become a huge mess, too. So the companies use style guides which split things apart. Even through the outcome is for all the simple cases noticeable worse to work with. As it does scale to large teams, very varying skill levels and many people working on the same code.

▲▼ antonchekhov15:43 hn>

I'm reminded of Rob Pike (one of the inventors of Go) saying "Gofmt's style is no one's favorite, yet gofmt is everyone's favorite."

▲▼ flossly21:02 hn>

Best part of Golang ihmo; first time I saw a language mandate it's formatting. Fast compiles are also nice, but not exclusive to Go (C, C++, OCaml, also compile fast).

I find it interesting that Google uses Rust. I know they work on Rust-based kernel code for Android. I'm curious to know if they also use it for networked application code, and why they picked it over Go.

Personally I prefer Rust over Go for it's types, and I never use it in big projects, so compile time does not bother me. But Google basically created Go for it's networked application code.

▲▼ TimTimTim123420:53 hn>

Is this good? Do i give this to my agent or nah?