Rust

By default, Rust brings only a few types into the scope of every program in the prelude. If a type you want to use isn't in the prelude, you have to bring that type into scope explicitly with a use statement.

Scalar Types

Compound Types

Statements

Expressions

Types

Every value in Rust is of a certain data type, which tells Rust what kind of data is being specified so it knows how to work with that data.

Scalar Types

A scalar type represents a single value. Rust has four primary scalar types: integers, floating-point numbers, booleans, and characters.

Integer Types

An integer is a number without a fractional component. Signed and unsigned refer to whether it's possible for the number to be negative or positive. Rust's default integer representation is i32

Length Signed Unsigned
8-bit i8 u8
16-bit i16 u16
32-bit i32 u32
64-bit i64 u64
128-bit i128 u128
arch isize usize

Number literals Example
Decimal 98_222
Hex 0xff
Octal 0o77
Binary 0b1111_0000
Byte b'A'

Floating-Point Types

Rust also has two primitive types for floating-point numbers, which are numbers with decimal points. Rust's default floating-point representation is f64

Length Type
32-bit f32
64-bit f64
        
          fn main() {
              let x = 2.0; // f64
              let y: f32 = 3.0; // f32
          }
        
      

Numeric Operations

Rust supports the basic mathematical operations you'd expect for all of the number types: addition, subtraction, multiplication, division, and remainder.

        
          fn main() {
              // addition
              let sum = 5 + 10;
              // subtraction
              let difference = 95.5 - 4.3;
              // multiplication
              let product = 4 * 30;
              // division
              let quotient = 56.7 / 32.2;
              // remainder
              let remainder = 43 % 5;
          }
        
      

Boolean Type

A boolean type in Rust has two possible values: true and false. Booleans are one byte in size.

        
          fn main() {
              let t = true;
              let f: bool = false;
          }
        
      

Character Type

Rust's char type is the language's most primitive alphabetic type.

        
          fn main() {
              let c = 'z';
              let z = 'Z';
              let heart_eyed_cat = '😻';
          }
        
      

Rust's char type is four bytes in size and represents a Unicode Scalar Value, which means it can represent a lot more than just ASCII. Unicode Scalar Values range from U+0000 to U+D7FF and U+E000 to U+10FFFF inclusive.

Compound Types

Compound types can group multiple values into one type. Rust has two primitive compound types: tuples and arrays.

Tuple Type

A tuple is a general way of grouping together some number of other values with a variety of types into one compund type. Tuples have a fixed length: once declared, they cannot grow or shrink in size.

        
          fn main() {
              let tup: (i32, f64, u8) = (500, 6.4, 1);
              let (x, y, z) = tup;
              println!("The value of y is: {}", y);
              let five_hundred = x.0;
              let six_point_four = x.1;
              let one = x.2;
          }
        
      

Array Type

Another way to have a collection of multiple values is with an array. Unlike a tuple, every element of an array must have the same type. Arrays in rust are different from arrays in some other languages because arrays in Rust have a fixed length, like tuples.

        
          fn main() {
              let a = [1, 2, 3, 4, 5];
              let months = [
                "January", "February", "March", 
                "April", "May", "June", "July", 
                "August", "September", "October", 
                "November", "December"
              ];
              let a: [i32; 5] = [1, 2, 3, 4, 5];
              let a = [3; 5];
              let a = [3, 3, 3, 3, 3];
              let first = a[0];
          }
        
      

Arrays are useful when you want your data allocated on the stack rather than the heap or when you want to ensure you always have a fixed number of elements.

When you attempt to access an element using indexing, Rust will check that the index you've specified is less than the array length. If the index is greater than or equal to the length, Rust will panic. This check has to happen at runtime.

Functions

Functions are pervasive in Rust code. Rust code uses snake case as the conventional style for function and variable names. In snake case, all letters are lowercase and underscores separate words.

        
          fn main() {
              println!("Hello, world!");
              another_function();
          }

          fn another_function() {
              println!("Another function.");
          }
        
      

Function definitions in Rust start with fn and have a set of parentheses after the function name. The curly brackets tell the compiler where the function body begins and ends.

Functions can also be defined to have parameters, which are special variables that are part of a function's signature. When a function has parameters, you can provide it with concrete values for those parameters. Technically, the concrete values are called arguments, but in casual conversation, people tend to use the words parameter and argument interchangeably for either the variables in a function's definition or the concrete values passed in when you call a function.

        
          fn main() {
              another_function(5);
          }

          fn another_function(x: i32) {
              println!("The value of x is: {}", x);
          }
        
      

In function signatures, you must declare the type of each parameter. This is a deliberate decision in Rust's design: requiring type annotations in function definitions means the compiler almost never needs you to use them elsewhere in the code to figure out what you mean.

        
          fn main() {
              another_function(5, 6);
          }

          fn another_function(x: i32, y: i32) {
              println!("The value of x is: {}", x);
              println!("The value of y is: {}", y);
          }
        
      

Function parameters don't need to be the same type.

Statements and Expressions

Function bodies are made up of a series of statements optionally ending in an expression. Rust is an expression-based language. Statements are instructions that perform some action and do not return a value. Expressions evaluate to a resulting value.

Creating a variable and assigning a value to it with the let keyword is a statement.

        
          fn main() {
              let y = 6;
          }
        
      

Function definitions are also statements. Statements do no return values. Therefore, you can't assign a let statement to another variable, as the following code tries to do; you'll get an error:

        
          fn main() {
              let x = (let y = 6);
          }
        
      

Expressions evaluate to something and make up must of the rest of the code that you'll write in Rust. Expressions can be part of statements. The value 6 is an expression. Calling a function is an expression. Calling a macro is an expression. The block that we use to create new scopes, {}, is an expression, for example:

        
          fn main() {
              let x = 5;

              let y = {
                  let x = 3;
                  x + 1
              };
              println!("The value of y is: {}", y);
          }
        
      

Expressions do not include ending semicolons. If you add a semicolon to the end of an expression, you turn it into a statement, which will then not return a value.

Return Values

Functions can return values to the code that calls them. We don't name return values, but we do declare their type after an arrow (->). In rust, the return value of the function is synonymous with the value of the final expression in the block of the body of a function. You can return early from a function by using the return keyword and specifying a value, but most functions return the last expression implicitly.

        
          fn five() -> i32 {
              5
          }

          fn main() {
              let x = five();
              println!("The value of x is: {}", x);
          }
        
      

The following definition of plus_one says that it will return an i32, but statements don't evaluate to a value, which is expressed by (), an empty tuple. Therefore, nothing is returned, which contradicts the function definition and results in an error.

        
          fn main() {
              let x = plus_one(5);
              println!("The value of x is: {}", x);
          }

          fn plus_one(x: i32) -> i32 {
              x + 1;
          }
        
      

Comments

In Rust, comments must start with two slashes and continue until the end of the line. For comments that extend beyond a single line, you'll need to include // on each line. Comments are mostly used on a separate line above the code it's annotating.

Control Flow

The most common constructs that let you control the flow of execution of Rust code are if expressions and loops.

If Expressions

An if expression allows you to branch your code depending on conditions. Optionally we can also include an else expression.

        
          fn main() {
              let number = 3;

              if number < 5 {
                  println!("condition was true");
              } else {
                  println!("condition was false");
              }
          }
        
      

Rust will not automatically try to convert non-Boolean types to a Boolean. You must be explicit and always provide if with a Boolean as its condition.

You can have multiple conditions by combining if and else in an else if expression.

Rust only executes the block for the first true condition, and once it finds one, it doesn't even check the rest.

Because if is an expression, we can use it on the righ side of a let statement.

        
          fn main() {
              let condition = true;
              let number = if condition {
                  5
              } else {
                  6
              };
              println!("The value of number is: {}", number);
          }
        
      

Loops

A loop runs through the code inside the loop body to the end and then starts immediately back at the beginning. Rust has three kinds of loops: loop, while, and for.

The loop keyword tells Rust to execute a block of code over and over again forever or until you explicitly tell it to stop.

        
          fn main() {
              loop {
                  println!("again!");
              }
          }
        
      

You can place the break keyword within the loop to tell the program when to stop executing the loop.

        
          fn main() {
              let mut counter = 0;
              let result = loop {
                  counter += 1;
                  if counter == 10 {
                      break counter * 2;
                  }
              };
              println!("The result is {}", result);
          }
        
      
        
          fn main() {
              let mut number = 3;
              while number != 0 {
                  println!("{}!", number);
                  number = number - 1;
              }
              println!("LIFTOFF!!!");
          }
        
      

You can also iterate through a collection by using the for keyword.

        
          fn main() {
              let a = [10, 20, 30, 40, 50];
              for element in a.iter() {
                  println!("the value is: {}", element);
              }
          }
        
      
        
          fn main() {
              for number in (1..4).rev() {
                  println!("{}!", number);
              }
              println!("LIFTOFF!!!");
          }
        
      
println! Is a macro that prints a string to the screen.
let The let statement is used to create a variable.
// The // syntax starts a comment and continues until the end of the line.
:: The :: indicates that the right value is an associated function. Some languages call this a static method.