Embedding Rustup into Cargo and the Wrapper

Part 2 of the Future of Cargo series.

What are we trying to solve?

TLDR; Less manual steps for building a Rust project and making sure everyone run the same way (same toolchain, same version of cargo, etc..).

One of my issue when building a project from scratch is the amount of manual steps required to be able to run a simple project. This is not so much a concern when you are a single developer but the larger the team, the most obvious it becomes.

So let’s say I have a new developer in my team with a brand new laptop. Here are the steps he will have to follow:

  1. Install git
  2. git clone XXX && cd XXX
  3. Install rustup form here: https://rustup.rs/
  4. rustup install stable
  5. cargo build
  6. Make sure to update your toolchain

In an ideal world with only good and independent developer, it wouldn’t be much of a problem. After experiencing it on large team I can tell that at least step 3 and 6 will go wrong, frequently. It’s a manual process so the reliability of it depends on humans and if you are responsible for it, which I have been, you will want to automate as much as you can before you get mad.

How do we solve it?

The title should give you a hint…

The funny part of this problem is that I have never seen it as a problem until I got offered a solution. Back then I introduced Gradle v0.9 in my company and at the same time discovered the Gradle wrapper. The idea is simple, only one developer needs gradle installed, this developer execute gradle wrapper which generates wrapper scripts for different platforms. All the other developers will execute gradle through those scripts (./gradlew) which will make sure that gradle is installed, at the right version etc. Simple! Nothing to install and you are always at the right version. You can even have two branches running on different versions, not a problem, you won’t even have to think about it… Since then wrapper scripts became popular and at least sbt and maven have a de-facto equivalent.

In fact, there’s a high chance that you have already used a wrapper script since that’s roughly how rustup installer works…

So… Here is what I am suggesting for my new team member :

  1. Install git
  2. git clone XXX && cd XXX
  3. ./cargow build

What needs to be done is a Cargo subcommands cargo wrapper that generates this cargow script and potentially such a subcommand could be created right now (and it is actually on my TODO list…). That implies this tool, or Cargo, to do rustup's job.

How is it Cargo’s problem?

If it can be solved without changing Cargo, why should Cargo care?

Because I believe it should be in the core of Cargo.

First, I have now been using wrapper scripts for 8+ years now and I still can’t see the downside. Never had a problem with it and it definitely makes everyone’s life easier. Let’s promote it, lets make it the default, not just some random external and unsupported plugin.

Then I believe that Cargo should be aware of toolchains context (target architecture[s], rustc version, std lib version, …). Too many things can go wrong at the moment, the “It works on my machine”™ problem is hard to avoid.

That inevitably leads to Cargo embedding rustup. It doesn’t mean that rustup, the tool, will disappear, in some use case you actually want to mess with toolchains but you shouldn’t need to do it for the default use case. It also means that you will be able to specify toolchain-related information into Cargo.toml.

Turns out that the Rust team is already thinking about rustup integration. Stay tuned!

PS: I expect some comments around “why aren’t you using Docker (or something similiar)? ”. For many reasons… It doesn’t hurt to make Cargo dealing with that, installing Docker is a manual step, building image is expensive, IDE integration is usually harder when the compiler is running in a container, you can always have docker on top if you want, … Turns out I actually use docker for my CI build.