Codecademy Logo

Basic Syntax

Macros

Macros are procedures that expand and generate raw source code before the rustc compiler begins its compilation step. In this example, the #[derive()] macro will generate all the source code necessary for Wow to be able to print debug out.

// Attributes are macros.
#[derive(Debug)]
struct Wow;
let wow = Wow;
// Calls ending with `!` are macros
println!("{wow:?} that is convenient!");

println!()

print!() and println!() internally call the format!() macro while also printing the formatted String to stdout. Printing to stderr is accomplished with eprint!() and eprintln!().

let jungle_bird = "Macaw";
let sound = "caws";
print!("The {jungle_bird}"); // Does not print a newline
println!(" {sound}."); // Prints a newline

Variable Declarations

To assign variables in Rust we utilize the let keyword with the = operator. We can assign variables to any expression taking the following form:

let variable = "this is a &str";
// This is a closure
let double = |d| d * 2;
// This is the outcome of calling the closure
let var = double(10);
// This will re-assign the value of var
let doubled_var = var;

Type Signatures

We can manually annotate types by providing a type signature. Type signatures are declared with : Type placed after the variable name and before the assigning =.

let small_integer: u16 = 28;
fn double(num: u128) -> u128 {
num * 2
}
let unsigned_int: u8 = 28;

Shadowing

We can assign a new value to the same variable name within the same scope. This is called shadowing and does not alter the original assignment.

let favorite = "orange";
println!("{favorite}");
let favorite = "cerulean";
println!("{favorite}");
let favorite = "yellow";
println!("{favorite}");

Dereferences

When we need to access the underlying data a reference points to directly, we can dereference with the * prefix.

let mut year = 3020;
let y = &mut year;
*y + 10;
println!("The year is {year}.");

Slices

A reference to a range of elements from a collection is called a slice. To take a slice, we use index expressions on a referenced collection.

let s = String::from("hello world");
let hello = &s[0..5];
let world = &s[6..11];
println!("{hello}{world}");

References

Every time we declare a value with let, we are creating data that is stored in memory. We can then create a reference to that data by prefixing our expression with the & prefix.

// In this example, funny_number is a reference to pi
let pi = 3.14159265359;
let funny_number = π
println!("{funny_number}");
// We can also create references to references
let lightspeed = 299792458;
let fast = &lightspeed;
let still_fast = &&lightspeed;
let speed_of_light = &still_fast; // This is equivalent to &&&lightspeed

Attributes

Attributes in Rust are macros that allow us to do special things with the language such as set compilation options, conditionally compile pieces of code, ignore lints, and denote tests and benchmarks. Attributes can be declared inside the scope of the item they are being applied to, known as inner attributes, or before the item they are being applied to, known as outer attributes.

Attribute Patterns

Here are some common input patterns you will find in attribute macros.

// Named.
#[no_std]
// Named with value.
#[must_use = "This function should be used."]
// Named with list of identifiers or paths.
#[forbid(unsafe, warnings)]
// Named with list of key values.
#[cfg_attr(target_os = "linux", path = "os/linux.rs")]

Defining a Module

We can define a module using the mod keyword. A module has its own distinct scope and visibility.

// Here we have defined a module named cake with an is_favorite() function inside it
mod cake {
pub fn is_favorite(name: &str) -> bool {
name == "Coconut"
}
}

Path Syntax

Once we have declared a module, its content can be accessed by utilizing path syntax. A path is created by chaining any number of nested modules together with the :: operator.

Mutability vs Immutability

Mutability is the capability of a variable’s value to be altered in memory. Immutability means that once a variable is declared with let, its value cannot change. In Rust, all variables are immutable by default.

mut

To alter a value, we must explicitly declare it as mutable with the mut keyword. Once we have declared a variable as mutable, we can reassign the value with the = operator.

// Immutable
let three = 3;
// Mutable
let mut any_number = 20;
// Reassign explicitly
any_number = 22;
// Increment and decrement
any_number += 1;
any_number -= 6;

Constants

Constants always require a type declaration and only types with a known size at compile time can be declared as a constant.

const ANIMAL: &str = "penguin";
// String does not have a known size, so it cannot be used as a constant
// The below code will not compile.
const OOPS: String = String::from("sorry");

Blocks

In Rust, code can be separated into blocks. A block of code is a collection of statements and an optional expression contained within {}.

// Statement block
{
let number_1 = 11;
let number_2 = 31;
let sum = number_1 + number_2;
println!("{sum}");
}
// Expression block
{
let number_1 = 11;
let number_2 = 31;
number_1 + number_2
}

Scope

Scope is the concept of whether or not a particular item exists in memory and is accessible at a certain location in our codebase. In Rust, the scope of any particular item is limited to the block it is contained in.

Visibility

We can make an item accessible outside of its normal scope by denoting it as public with the pub keyword. All items in Rust are private by default. Private items can only be accessed within their declared module and any children modules.

Statements

In Rust, A Statement is a segment of code that does not return any value. Statements are easy to identify as they end with a semicolon (;) to denote that nothing is returned.

// This is a statement, but we cannot access its value
"purple";

Expressions

An Expression is a segment of code that returns a value. Since Rust is an ‘expression-oriented’ language, all code blocks will implicitly return their value unless we utilize a semicolon to terminate the expression.

// This is an expression, it will return the &str "green"
"green"

Patterns

Rust’s syntax also allows Patterns, which are special syntax rules that can be used in certain situations. Patterns help to make the language more readable and allow us to do things that are otherwise not easily accomplished. A comprehensive list of patterns can be found in the Rust Reference.

Learn More on Codecademy