Rust: an IDE for your project

Next: Coming soon
Previous: Your first package

So, you’ve built your first package with Cargo. It doesn’t take long before the lack of an IDE really becomes a problem. As is customary these days, there are many choices. I’ve been using VS Code.

https://code.visualstudio.com/

Go ahead and install that. Note that Code is not really an IDE in the traditional sense. Even though it shares a name with Visual Studio, it really has nothing to do with Visual Studio. Rather, it is more of a do-it-yourself IDE builder if you will. Still, it’s pretty neat and is better than the alternatives I have come across. Next, you will want to install the C/C++ extension by Microsoft. This will enable things like debugging support.

And then install the Rust (rls) extension.

This provides basic IntelliSense (when it works) as well as IDE support for things like building and debugging.

It’s early days for the IDE support, but you’ll get syntax highlighting and something that feels like an IDE. I’ll talk about debugging and IntelliSense, building and much more in due course. But first, what’s more pertinent is how to set up your environment to model what a traditional C++ developer on Windows might think of as a collection of projects. Think of a Visual Studio solution and the enormous complexity that Visual Studio attempts to orchestrate when it presents a set of projects in its Solution Explorer, with project references and build dependencies all administered by msbuild. VS Code certainly has no notion of a solution in that sense, but what it does offer is surprisingly refreshing and combined with Rust’s built-in support for dependencies turns out to be superior in many ways. So, find an empty folder and give this a try.

Start by creating a binary package:

C:\projects>cargo new app
     Created binary (application) `app` package

Now create a library package that the app will use:

C:\projects>cargo new --lib fruit
     Created library `fruit` package

Notice I used the –lib option to tell Cargo to use its library template rather than the default binary template. Recall that Rust infers the type of package based on the presence of a file named src/main.rs or src/lib.rs respectively. So these two projects are almost identical so far. And just for good measure, let’s create a third package:

C:\projects>cargo new --lib apples
     Created library `apples` package

Right, you should now have three folders that look something like this:

C:\projects>dir
    <DIR>          app
    <DIR>          apples
    <DIR>          fruit

Notice there’s no “solution” file, but VS Code is quite happy to simply to open a folder directly:

C:\projects>code .

Here I’m using the “.” shortcut to indicate the current folder.

Notice that VS Code’s Explorer panel simply presents the three folders and their contents and as you can see, the only difference between the binary and library packages is the name of the source file in each package’s src folder. Before looking at the code, let’s set up our package dependencies. We would like the app to depend on the fruit library and the fruit library in turn to depend on the apples library package. Such dependencies are described in the Cargo.toml file that each package contains in its package root.

Open app/Cargo.toml and you’ll see that Cargo filled in some basics. Add the fruit dependency such that app/Cargo.toml looks something like this:

[package]
name = "app"
version = "0.1.0"

[dependencies]
fruit = { path = "../fruit" }

Cargo lets you specify dependencies in a variety of ways, but this simply tells Cargo that this package depends on a package in a relative location in the file system. Cargo will thus ensure that the fruit package is built and ready for consumption by the app package. Now you should know how to update fruit/Cargo.toml to depend on the apples package:

[package]
name = "fruit"
version = "0.1.0"

[dependencies]
apples = { path = "../apples" }

And that’s it. In Visual Studio parlance, you’ve set up a solution with a few projects that include build dependencies. Don’t believe me? Change to the app folder and let’s see what happens when we build it:

C:\projects>cd app

We can now simply tell Cargo to build the app package as we learned previously. But notice what happens:

C:\projects\app>cargo build
   Compiling apples v0.1.0 (C:\projects\apples)
   Compiling fruit v0.1.0 (C:\projects\fruit)
   Compiling app v0.1.0 (C:\projects\app)
    Finished dev [unoptimized + debuginfo] target(s) in 0.99s

Cargo chased down the package dependencies recursively, first building the apples package, then the fruit package, and finally the app itself. As you might expect, any time you make a change to any source in any of these packages, Cargo will automatically rebuild as needed. On the other hand, if nothing has changed it will simply reuse build artifacts or do the equivalent of an incremental build:

C:\projects\app>cargo build
    Finished dev [unoptimized + debuginfo] target(s) in 0.08s

Now for good measure, let’s see if we can actually call some code down through these package dependencies. We’ll start at the bottom. Open apples/src/lib.rs and replace whatever’s there with a simple apple function:

pub fn apple() -> &'static str {
    "Granny Smith"
}

Don’t worry too much about the syntax at this stage. We will shortly switch gears and talk about the Rust language itself. Essentially, this is just saying that we would like a public (pub) function (fn) called apple that doesn’t have any parameters and returns a static string slice, typically a string literal.

Now open fruit/src/lib.rs and replace whatever’s there with a fruit function:

pub fn fruit() -> &'static str {
    apples::apple()
}

This function has the same signature as before, but simply calls the apple function and forwards on its return value. Finally, we can call the fruit function from our app’s main function inside app/src/main.rs and replacing whatever’s there with this main function:

fn main() {
    println!("Hello, {}!", fruit::fruit());
}

And that’s it. Save your work and go back to the command prompt and build the app:

C:\projects\app>cargo build
   Compiling apples v0.1.0 (C:\projects\apples)
   Compiling fruit v0.1.0 (C:\projects\fruit)
   Compiling app v0.1.0 (C:\projects\app)
    Finished dev [unoptimized + debuginfo] target(s) in 0.79s

You’ll notice that Cargo once again understands that it should rebuild all dependencies since they have all changed. As I mentioned before, rather than Cargo’s build command we could more easily just use Cargo’s run command to both build and run the app:

C:\projects\app>cargo run
    Finished dev [unoptimized + debuginfo] target(s) in 0.02s
     Running `target\debug\app.exe`
Hello, Granny Smith!

Well hello there, Granny Smith. Wasn’t that fun! We’ve gone from building individual Rust source files, to building a Rust package, to being able to build a set of Rust packages with really very little effort on our part. VS Code is not without issues and there are certainly more and different ways to manage packages and editors, but this is enough to get you started.

Join me next time for the adventures of a C++ developer learning Rust.

3 thoughts on “Rust: an IDE for your project

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s