Hello, World!
We have already seen how to write “Hello, World” in Rust. In this section, we will demystify all the parts of the Rust syntax relevant to the “Hello, World” code in the previous section.
The structure of Rust projects
The important parts of a Rust project are seen when we use Cargo to initialize a new project. Our hello
the project has the following files:
.
├── Cargo.toml
├──.gitignore
└── src
└── main.rs
Let’s understand what they stand for.
Cargo.toml:
stores metadata for the package. The contents are grouped into two sectionspackage
anddependencies
. Thedependencies
section contains records of the names and versions of external crates used in the project..gitignore
: stores files that Git should not track. Git is Rust's official version control software, and thecargo new
command initializes Git in the directory. When Rust code is built or compiled, atarget/
sub-directory is generated containing the build files. Adding it to the.gitignore
file is good practice to avoid pushing large unnecessary files to GitHub.src/main.rs
: Thesrc/
subdirectory is where all Rust code is normally written. Themain.rs
file is the entry point for all binary crates.
Note: With Cargo, you can create two types of projects (preferably called packages or crates): binary packages/crates and library packages/crates. Let’s see how these are done. Run:
cargo new libpkg --lib
You should see a new libpkg
project containing a lib.rs
file inside the src/
directory instead of a main.rs
file. You’ll also notice an auto-generated test code inside the lib.rs
file. Library crates do not have/need a main
function and do not allow the cargo run
command. You can only use the tests to check your code by running cargo test
. You can trick Cargo by creating a main.rs
file inside the src/
directory, but if you publish the project as a package, remember to delete it.
Understanding basic syntax and structure
We have been introduced to the main
function from the “Hello World” code above. Most programming languages like Go and C/C++ also have the main
function as the entry-point function. But the difference is that Rust’s main
function can behave like every other function by accepting and returning values. Weird, right?
Let’s go further with understanding Rust’s syntax for now.
Comments for documentation are made with the double forward slash: //
. Try this out in the main.rs
file:
fn main() {
// greet me on the terminal
println!("Hello World!");
}
The fn
keyword is the Rust way of specifying that a code block is a function. All expressions and separate statements in Rust code must end with a semicolon, just like in C/C++. If you remove or skip it, the compiler panics.
Remove the semicolon after the closing brackets to print out the greeting and running cargo run
.
Surprise! It still works. Why? The Rust compiler can infer the meaning of your code even if you omit the semicolon at the end of a statement. Smart, right?
Let’s update the main.rs
file to this:
fn main() {
let x = 5 // there is a missing semicolon here.
println!("The value of x is: {}", x) // there is also a missing semicolon here that can be omitted.
}
The output should be:
❯ cargo run
Compiling hello v0.1.0 (/.../workspace/hello)
error: expected `;`, found `println`
--> src/main.rs:2:14
|
2 | let x = 5
| ^ help: add `;` here
3 | println!("The value of x is: {}", x)
| ------- unexpected token
error: could not compile `hello` (bin "hello") due to previous error
The compiler tells you exactly what went wrong: expected
; . It further gives you a helping hand: help: add ;
here.
Add the semicolon at the end of line 2, and run the program. This works. But it isn’t ideal. Add another semicolon at the end of line 3 too. There you go; a properly written Rust code.
The let
keyword is a statement and the println!
keyword (called a macro in Rust) is an expression. We will learn more about the use of the let
keyword in the next section.
If you come from a C/C++ background, you can confirm that the main
function in Rust receives arguments and return statements by updating the main.rs
file and running it:
fn main() -> () {
let x = 5;
println!("The value of x is: {}", x);
}
The ->
symbol is how you specify return values. This is a more robust way to write the main
function in Rust.