Hello, World!
In this tutorial, you will learn how to setup a new Rust crate and get a sense for what it includes. You will also learn how to build and run your new crate with cargo, a package manager and build system for Rust. You do not need to install cargo because it comes bundled with Rust.
When you first setup a Rust crate, you will have to choose between one of two types: binary crates and library crates. Binary crates are standalone applications or executables that you can run, like command line tools. Library crates are typically small bundles of code that are used in the development of other crates. In this tutorial, we will focus on building a binary crate.
To create a new binary crate, open a terminal and run the following:
$ cargo new hello-worldThis will create a new directory called hello-world that you can then navigate into using cd hello-world. Once inside that directory, you will find all of the following:
hello-world/
├── Cargo.toml
├── Cargo.lock
└── src/
└── main.rs
The Cargo.toml file is a document in which you specify metadata and dependencies for your crate similar to an R package’s DESCRIPTION. The Cargo.lock file maintains copies of those dependencies, similar to renv.lock. A lot more can be said about those two files, which are absolutely crucial to programming in Rust, but for now let’s focus on the Rust file in the source directory. If you open that file in Positron (or your preferred IDE), you will see the following text:
src/main.rs
fn main() {
println!("Hello, world!");
}In just those three short lines, we see examples of many Rust coding patterns:
- Functions are declared using the
fnkeyword, - The
main()function defines what happens when you run your binary, - Blocks of code are delimited using curly brackets (just like R), and
- Statements end with
;.
Right now, our app uses the println!() macro to print “Hello, world!” to the terminal. When a program writes to the console, it does so through file connections called standard output (stdout) and standard error (stderr). In R, when we print a message with print() or message(), we print to stdout. Similarly, when we make a warning or error using stop() or warning(), that is writing to stderr. The Rust println!() macro also has the nice feature of supporting string interpolation like sprintf() and glue():
// indirect interpolation
let name = "Josiah";
println!("Hello, {}!", name);
// direct interpolation
let name = "Josiah";
println!("Hello, {name}!");If we add one or the other of those lines to main() like so
src/main.rs
fn main() {
let name = "Josiah";
println!("Hello, {name}!");
}we can then call the application and get it to execute in the terminal:
$ cargo run
Compiling hello_world v0.1.0 (/hello-world)
Finished dev [unoptimized + debuginfo] target(s) in 0.33 secs
Running `target/debug/hello_world`
Hello, Josiah!Notice that cargo provides detailed information about the build process (printed to stderr) before actually running the program (and printing to stdout). This is a useful reminder that Rust, unlike R, is a compiled language - Rust programs have to be built before they can be run. For those coming from R, that might sound rather mysterious, and in some respects it is, but we want to emphasize how easy it was to get a program up and running. It was just three lines of code in the terminal:
$ cargo new hello-world
$ cd hello-world
$ cargo runThat’s it! Those three lines were sufficient to build and run our simple binary. Now we can move on to learning about basic data types in Rust.