Contributing to Rust std

cfg(doc) vs doc(cfg)

Recently, I sent a pull request adding abstract namespace support to Unix domain sockets in Rust and I wanted to aggregate notes on the experience for future reference. Once the feature gets merged, I'll do another post describing the ins and outs of it.

Setup was easy #

Building up my local environment (on a x86-64 linux machine) was thankfully fast, competitive with any other project (open or closed) I've contributed to. Given that the repo includes the compiler, stdlib and docs, I was honestly expecting to spend a few hours on just setting up a productive feedback loop. Yet, the time from git clone to writing meaningful code took probably ~20 minutes and most of that was spent pulling the repo and related crates.

When rebasing master off upstream, useful reminder to also run:

$ git submodule update --recursive

The x.py cli #

Rust provides a cli to do common contributor tasks. I actually didn't realize how versatile the tool is until much later. As an example, it can generate the rust docs for you so you can verify the comments you wrote:

$ python3 ./x.py doc

As well as rustfmt your changes:

$ python3 ./x.py fmt

Building and testing #

You'll also use x.py to build and test your changes. You can specify args to quicken the tasks. What I used to build quickly:

$ python3 ./x.py build --stage 0 library/std

And to test:

$ python3 ./x.py test --stage 0 library/std --test-args unix

The --test-args help when you're wanting to run just your tests rather than the all of std's test suites.

Custom toolchain #

The 2 commands above are the ones I ran the most but if you actually want to use your new stdlib in another project you can set up a custom toolchain using rustup. First do a complete build, then link it:

$ python3 ./x.py build --stage 1
$ rustup toolchain link mylocalrust ./build/x6_64-unknown-linux-gnu/stage1

I usually like to verify against stage 1 but you can also specify other stages (e.g. 0, 2).

And now can you compile your Rust programs against your local toolchain with the + arg:

$ cargo +mylocalrust build

Cross-platform compilations #

So this tip would've saved me some time in the beginning. Currently when you send a PR to Rust it seems that the branch is built & tested against one platform (OS, architecture). If your branch passes that doesn't necessarily mean you're safe. When it gets approved and an eventual rollup occurs, a branch including your code will be built & tested against multiple platforms.

To save some of the potential slowdown going through a back & forth with the CI & reviewers with cross-platform issues, it would be wise to try to build & test the branch yourself across different machines. After a bit of going through trial and error myself, I eventually was able to run my linux build safely on my old Macbook Pro machine. Nice.

Tricky: using cfg(any(doc)) and doc(cfg(any)) #

Here is a specific cross-platform issue I learned about that seems to be particularly relevant to stdlib contributing:

If you're working on a platform-specific issue then you'll want to tell the compiler about it through #[cfg(...)]. For example,

#[cfg(target_os = "linux")]
fn my_cool_new_feature() {}

But since you know the value of documentation you'll also want this feature shown in the docs so you add any(doc):

/// Super dope docs
///
/// ```
/// let super_dope_example = my_cool_new_feature();
/// assert!(super_dope_example, is_cool);
/// ```
#[cfg(any(doc, target_os = "linux"))]
fn my_cool_new_feature() {}

From your single Linux machine perspective, you're done right? The feature works and the docs with doctests looks great.

But no. Because even though other platforms may be safe from your platform-specific feature code, they aren't safe from your platform-specific doctests example. If another platform tries to build & run your doctests, it'll fail because your doctests are using your new cool feature.

The current work around is adding #[doc()]:

/// Super dope docs
///
/// ```
/// let super_dope_example = my_cool_new_feature();
/// assert!(super_dope_example, is_cool);
/// ```
#[doc(cfg(target_os = "linux"))]
#[cfg(any(doc, target_os = "linux"))]
fn my_cool_new_feature() {}

This will prevent those doctests from being compiled & ran on another platform as well as add a banner to the documentation stating that this feature is platform-specific.

I'll try to keep this page updated as I continue learning more about the contribution process.

Further resources to look into #

If you see any inaccuracies, typos or have comments, please feel welcomed to reach out @mdaverde.