Swatinem Blog Resume

Documentation Driven Development

… and a wishlist for better Rust Tooling

— 6 min

I have been working on documenting a growing rust project, and ran into some papercuts along the way. Also, writing good documentation is actually very hard and sometimes tedious work, but I think it is very much worth it.

# Why I love Doctests

I can’t really stress how important both testing, and documentation is! Testing makes sure that your project meets its goals and certain quality criteria. And documentation makes sure that your potential users know how to effectively use your project. Without documentation (and marketing), a project will struggle hard to grow its user base.

However, maintaining good documentation is really hard. And depending on the language ecosystem, it is too easy for your code and documentation to drift apart and become broken / outdated. That is one reason why I really love the concept of doctests. On a high level, they are just example code snippets that you want to present to your users. But they also run as part of your testsuite, just like any other tests. So they can’t drift apart. It is a really good way to give usage examples of your API, and document corner cases all at the same time.

# Rusts Unique Position

Rust actually is in a very good position here. I originally come from the TypeScript ecosystem, and it is actually a huge pain that TypeScript suffers a lot from the “new day, new framework” syndrome. There is way too much choice among linters, build systems / bundlers, testing frameworks, benchmark frameworks, and probably documentation tools, although I haven’t really used any of those. It is quite tiring to wade through all the options. At least that ecosystem now has a code formatter that everybody agrees on.

It is really refreshing that Rust is super opinionated in this regard. It ships with a default build system (which also has workspace support), testing tool, a linter, a benchmarking tool (with support for custom frameworks) and a first class documentation tool. You get all of this out of the box (or rather, via rustup) and don’t have to worry about any of this. What a way to boost productivity!

Also, rusts tools are really good, even though they have some shortcomings as we will see.

# My Wishlist for better Tools

But unfortunately, testing and writing doctests is a bit painful right now. But as I see it, those obstacles should be fairly straight forward to overcome. Here is just a short wishlist of things I would love to see in the Rust ecosystem that would make my life a bit easier ;-)

Some of the mentioned issues are already implemented on nightly versions, and are just pending stabilization. Others are quite big and complex, or seem to stuck in bikeshedding discussions. But one thing that actually sounds quite doable is to have rustfmt format and re-wrap markdown. I might even look into this if I have too much free time on my hands, or as something of a hackweek project.

# Forcing Features in Tests

So the crate I am working on right now has quite some feature flags, and I would like to refer to those feature-flagged items in my doctests. But that breaks the tests when run with --no-default-features, or in general if the features I want to use are not default.

Also, you can’t #[cfg(test)] those items, since doctests won’t pick those up due to rust#45599 and rust#67295. But I have found a rather nice workaround for that. Whereas Rust disallows normal dependency cycles. Defining circular dev-dependencies actually works fine, which is quite magical. So you can just dev-depend on yourself, with certain features. The same also works across circular workspace crates.

[package]
name = "doctests"
version = "0.1.0"
edition = "2018"

[features]
featured = []

[dev-dependencies]
doctests = { path = ".", features = ["featured"] }
/// ```
/// doctests::featured();
/// ```
pub fn doctest() {}

#[cfg(feature = "featured")]
pub fn featured() {}

#[test]
fn unittest() {
    crate::featured();
}