← Back to context

Comment by monocasa

3 hours ago

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.

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!)

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.

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

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.

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.

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.

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

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