Making Your First Rust App
We are going to build an application in Rust to get a feel for the language and ecosystem. The first step for all new Rust projects is generating a new project. Let's create a new project called
Cargo is the package manager for Rust. It is used as a command line tool to manage dependencies, compile your code, and make packages for distribution. Running
cargo new project_name by default is equivalent to
cargo new project_name --bin which generates a binary project. Alternatively, we could have run
cargo new project_name --lib to generate a library project.
Binary vs. library#
A binary project is one which compiles into an executable file. For binary projects, you can execute
cargo run at the root of your application to compile and run the executable.
A library project is one which compiles into an artifact which is shareable and can be used as a dependency in other projects. Running
cargo run in a library project will produce an error as cargo cannot figure out what executable you want it to run (because one does not exist). Instead, you would run
cargo build to build the library.
There are different formats which the Rust compiler can generate based on your configuration settings depending on how you wish to use your library.
The default is to generate an
rlib which is a format for use in other Rust projects. This allows your library to have a reduced size for further distribution to other Rust projects while still being able to rely on the standard library and maintain enough information to allow the Rust compiler to type check and link to your code.
Alternative library formats exist for more specialized purposes. For example, the
cdylib format is useful for when you want to produce a dynamic library which can be linked with C code. This produces a
.dll depending on the target architecture you build for.
The generated project#
Let's enter the directory for our newly generated Rust project to see what is created:
The generated structure is:
Rust code organization relies primarily on convention which can be overridden via configuration, but for most use cases the conventions are what you want.
For a binary project, the entry point is assumed to be located at
src/main.rs. Furthermore, inside that file, the Rust compiler looks for a function named
main which will be executed when the binary is run. Cargo has generated a
main.rs file which contains a simple "Hello, world!" application:
The syntax here says define a function (
fn) with the name
main which takes zero arguments and returns the empty tuple
Leaving off the return type is equivalent to writing
-> ()after the argument list of the function. All function calls are expressions which must return a value. The empty tuple
()is a marker for no value, which is what a function with no return type implicitly returns.
The body of the function calls a macro
println which prints its argument
"Hello, world!" to standard out followed by a newline.
We will cover macros more later, but for now we will mention a few basics. We know it is a macro invocation and not a normal function call because of the trailing
!in the name. Macros are a powerful form of meta-programming in Rust which you will use frequently but probably rarely find the occasion to have to write. Rust implements
printlnas a macro instead of as a regular function because macros can take a variable number of arguments, while a regular function cannot.
The syntax of Rust is superficially similar to C++ which we can see as curly braces are used for denoting blocks and statements are semicolon terminated. However, there is quite a bit more to the Rust grammar that we will cover as we go along.
Cargo.toml file is the manifest file for the project which uses the TOML format. This is the entry point for describing your project as well as specifying dependencies and configuration. The initial generated file contains the bare essentials for describing your project:
The blank section for dependencies is included because nearly every project includes some dependencies. One feature of Rust has been to keep the core language and standard library relatively slim and defer a lot of extra functionality to the community. Therefore relying on third party dependencies is encouraged.
Binary Rust projects are also called crates so they do not solely represent shared library code. Furthermore, a crate can contain both a library and an executable.
It is often difficult to foresee how other's will want to use your software. A common practice in the Rust community is to create dual library/binary crates even when the primary intention of a project is to produce an executable. This can have positive effects on the API design of your code knowing that it should be suitable for external consumption. The binary part of the crate is typically responsible for argument parsing and configuration, and then calls into the functionality exposed by the library part of the crate. Writing all of your code only as an executable and then trying to extract a library after the fact can be a more difficult process. Moreover, the cost of splitting code into a library is minimal.