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.
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.
A scalar type represents a single value. Rust has four primary scalar types: integers, floating-point numbers, booleans, and characters.
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' |
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
}
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;
}
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;
}
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 can group multiple values into one type. Rust has two primitive compound types: tuples and arrays.
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;
}
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 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.
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.
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;
}
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.
The most common constructs that let you control the flow of execution of Rust code are if
expressions and loops.
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);
}
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. |