Click to expand
- Uses
rustup
to install the latest version of Rust. - Rust files always end with the .rs extension.
- Uses
rustc
to compile rust files. - Functions start with a
fn
keyword.- Parameters go inside parenthesis,
()
. - The function body is wrapped in curly brakets,
{}
.
- Parameters go inside parenthesis,
- Rust uses four spaces rather than a tab.
- Using a
!
means calling a macro instead of a function.println!
is a macro that prints a string to the screen.
- Strings use double quotes,
"string"
. - Uses
;
to end lines
Click to expand
- Cargo is Rust`s build system and package manager.
- Uses
cargo
to make a new project.cargo build
to build a cargo project.--release
option does a compilation with some optimizations.
cargo run
to build and run.cargo check
to check a project without generating a binary.
Click to expand
prelude
library is automatically included to every rust program while compiling them.- To use other types or functions not in the prelude, they must be brought with a
use
statement.io
library comes from the standard librarystd
and does input/output functions.
- To create a variable, use a
let
statement. - In Rust, variables are immutable by default.
- Use
mut
before the variable name to make a variable mutable.
- Use
//
syntax starts a comment until the end of the line.String
type is provided by the standard library.- UTF-8 encoded bit of text.
::
syntax indicates that an associated function follows.- Calls
read_line
to get input from the user. &
indicates that this argument is a reference.- References are immutable by default.
- Uses
&mut
to make it mutable.
- When using the
.foo()
syntax, it is best to divide it. read_line
method returns a value with theio::Result
type.- The
Result
types are enumerations. (Ok
when successful /Err
when failed) - If the instance of
io::Result
is anErr
value,expect
will cause to crash the program and display the message that passed as an argument.
- The
{}
is a placeholder for printing values.- Before using external crates, they must be included into the
[dependencies]
section in.toml
file.- Cargo understands Semantic Versioning.
- e.g) `^0.5.5^ means any version that has a public API compatible with version 0.5.5.
- With
cargo doc --open
, documentation is built and provided by all of dependencies within it. std::cmp::Ordering
type is another enum. (Less, Greter, and Equal)- The
cmp
method compares two values.
- The
- A
match
expression is made up of arms.- Each arm consists of a pattern, the
match
looks through each arm's pattern in turn.
- Each arm consists of a pattern, the
- Rust has a strong, static type system, but it also has type inference.
- Rust allows to shadow the previous value with a new one.
- The
parse
method on strings parses a string into some kind of number. - The
loop
keyword creates an infinite loop. - Adds
break
to quit the loop.
Click to expand
- Rust has a set of keywords that are reserved for use by the language only.
- Link: Reference
- Constants are not variables.
- It is not allowed to use
mut
with constants. - When a constant is declared using the
const
keyword, its type must be annotated. - Constants may be set only to a constant expression, not the result of a function call or whatever.
- It is not allowed to use
- Rust's naming convention for constants is to use all uppercase with underscores between words.
- Underscores can be inserted in numeric literals to improve readability.
- e.g)
const MAX_POINTS: u32 = 100_000;
- e.g)
- Using shadowing, new variable with the same name can be declared in the different type.
- Data types are divided into two subsets which are scalar and compound.
- Rust is a statically typed language.
- A scalar type represents a single value.
- Integers, floating-point numbers, booleans, and characters.
- The integer type should be annotated like
[SIGN][BITS]
- e.g)
u32
for unsigned 32-bit long integer,i128
for signed 128-bit long integer. - The default integer is
i32
.
- e.g)
- Integer literals can be written in any of the forms shown below.
Number literals | Example |
---|---|
Binary | 0b1111_0000 |
Octal | 0o77 |
Decimal | 98_222 |
Hex | 0xff |
Byte(u8 only) |
b'A' |
- All number literals except the byte allow a type suffix(
57u8
) and_
as a visual seperator. - When compiling in debug mode, Rust checks for integer overflow that cause the program to panic at runtime.
- Panic means that a program exits with an error.
- When compiling in release mode with the
--release
flag, Rust just performs two's complement wrapping.- With the library
Wrapping
, it becomes available to wrap integers explicitly.
- With the library
- Rust's floating-point types are
f32
andf64
.- The default type is
f64
.
- The default type is
- Rust supports the basic mathematical operations.
+
for addition,-
for subtraction,*
for product,/
for division, and%
for remainder.- Link: All operators
- A boolean type in Rust has two possible values:
true
andfalse
.- Booleans are one byte in size.
- Booleans are specified using
bool
. - e.g)
let f: bool = false;
- A character type in Rust is used to represent letters.
- Characters are four bytes in size and represents a Unicode Scalar Value.
- Characters are specified using
char
. - Character literals are specified with single quotes, as opposed to string literals, which use double quotes.
- e.g)
let c: char = 'z';
- Compound types can group multiple values into one type.
- Rust has two primitive compound types: tuples and arrays.
- Tuples have a fixed length.
- Once decleared, they cannot grow or shrink in size.
- The tuple is specified with a comma-separated list of values inside parentheses.
- e.g)
let tup: (i32, f64, u8) = (500, 6.4, 1);
- e.g)
- Rust supports pattern matching to destructure a tuple value.
- e.g)
let (x, y, z) = tup;
- e.g)
- Rust also supports direct access to a tuple element by using a period,
.
.- e.g)
let six_point_four = tup.1;
- e.g)
- Arrays also have a fixed length.
- Every element of an array must have the same type.
- The array is annotated with values of the same type inside square brackets.
- e.g)
let a: [i32; 5]
- e.g)
- Array data are allocated on the stack.
- An element of an array can be directly accessed with an index inside square brackets.
- e.g)
a[0]
- e.g)
- Rust panics at index out of bounds in runtime.
Click to expand
- Function definitions start with the
fn
keyword and have a set of parentheses after the function name. - Rust code uses snake case as the conventional style for function and variable names.
- Rust doesn't care where functions are defined.
- Functions can also be defined to have parameters, which are special variables that are part of a function's signature.
- In function signatures, The type of each parameter must be declared.
- Statements are instructions that perform some action and do not return a value.
- e.g)
let y = 6;
, function definitions
- e.g)
- Expressions evaluate to a resulting value.
- Expressions do not include ending semicolons.
- e.g) math operations, calling a function/macro,
{}
- The return value of a function must be declared with its type after
->
. - It can be either using the
return
keyword or the final expression in the block to pass the return value.
Click to expand
- An
if
expression allows to branch the code depending on conditions.if
expressions start with the keywordif
, which is followed by a condition.
- Conditions of
if
expressions must be provided as Boolean type. - Rust only executes the block for the first true condition.
- It is more powerful to use
match
than using too manyelse if
expressions. - Because
if
is an expression, it can be placed on the right side of alet
statement.- e.g)
let number = if condition { ... }
- In this case, types of last expressions in
if
,else if
, andelse
blocks must be equal.
- e.g)
- Rust has three kinds of loops:
loop
,while
, andfor
. - The
loop
keyword makes an infinite loop.- To break out of a loop, the
break
keyword must be placed. - The loop can also return values using the
break
expression.
- To break out of a loop, the
- The
while
loop has a condition, and it checks the condition by each loops until it became false. - Using
for
loop, it is able to loop through each element of a collection without consideration to the bound.- It is more safe to use
for
than to usewhile
while looping through a collection.
- It is more safe to use
Range
is a type provided by the standard library.- It generates all numbers in sequence starting from one number and ending before another number.
- e.g) (1..4)
Click to expand
- All data stored on the stack must have a known, fixed size.
- Data with an unknown size at compile time or a size that might change must be stored on the heap.
- An enough spot on the memory is allocated on the heap.
- Ownership rules:
- Each value in Rust has a variable that's called its owner.
- There can only be one owner at a time.
- When the owner goes out of scope, the value will be dropped.
- A scope is the range within a program for which an item is valid.
- The string type is stored on the heap.
- String literals are stored on the stack.
- e.g)
let mut s = String::from("hello");
- The double colon,
::
, is an operator that allows to namespace methods under the type. - That kind of string can be mutated.
- e.g)
s.push_str(", world!");
- e.g)
- The
String
type need to be allocated an amount of memory on the heap.- The memory must be requested from the OS at runtime.
- This can be done by calling
String::from
.
- This can be done by calling
- This memory must be returned to the OS after use.
- The memory is automatically returned once the variable goes out of scope.
- The memory must be requested from the OS at runtime.
- When a
String
variable goes out of scope, Rust calls adrop
function. - When assigning the stack data of a variable to another variable, Rust makes a copy of this value.
- In case of the
String
type, Rust copies only pointers rather than values.- A
String
is made up of three parts, pointer, length, and capacity.
- A
- Rust does a shallow copy and also invalidates the first variable to avoid a double free error.
- In other words, Rust moves the first variable to the second.
- To copy the
String
deeply, use a common method calledclone
.- e.g)
let _s = s.clone();
- e.g)
- It's possible to return multiple values using a tuple.
- e.g)
(s, length)
- e.g)
Click to expand
- To prevent the ownership of a variable to be moved into a different scope, use a reference.
- The ampersand,
&
, allows to refer to some value without taking ownership of it. - To dereference it, use the dereference operator,
*
.
- The ampersand,
- Although the reference goes out of its scope, the variable is not dropped.
- Setting references as function parameters is called borrowing.
- The value of a reference can be modified only when the reference is set to be mutable.
- There can be only one mutable reference of the variable at a time.
- Rust can prevent data races in this fashion.
- A data race happens when three behaviors occur:
- Two or more pointers access the same data at the same time.
- At least one of the pointers is being used to write to the data.
- There's no mechanism being used to synchronize access to the data.
- It is not possible to have a mutable reference while having an immutable one.
- Multiple immutable references can exist simultaneously.
- A reference's scope starts from where it is introduced and continues through the last time that it is used.
- A dangling pointer is that references a location in memory that may have been given to someone else, by freeing some memory while preserving a pointer to that memory.
- In Rust, the compiler guarantees that references will never be dangling references.
- To do so, Rust introduced a new feature, lifetime.
- Slices let you reference a contiguous sequence of elements in a collection rather than the whole collection.
- The
enumerate
method returns a tuple that has an index and a reference of an item. - A string slice is a reference to part of a
String
.- e.g)
let hello = &s[0..5];
- e.g)
- String literals are also slices pointing to that specific point of the binary.
- e.g)
&str
- e.g)
- Other slices work same as string slices.
Click to expand
- To define a struct, write the keyword
struct
and name the entire struct.- Inside curly brackets, names and types of data being grouped together should be described.
- To use a struct after defining it, create an instance of that struct by specifying concrete values for each of the fields.
- Pairs of the field and the value will be assigned in this form
key: value
.
- Pairs of the field and the value will be assigned in this form
- To get a specific value from a struct, use dot notation.
- e.g)
user1.email = String::from("[email protected]");
- c.f) Note that the entire instance must be mutable for values to be changed.
- e.g)
- Rust doesn't allow to mark only certain fields as mutable.
- If the parameter names and the struct field names are same, the field names can be omitted.
- Using struct update syntax, it is easily done to create a new instance of a struct that uses most of an old instance's values.
- The syntax
..
specifies that the remaining fields not explicitly set have the same value as the given instance.
- The syntax
- Tuple structs have the added meaning the struct name provides but don't have names associated with their fields.
- e.g)
struct Color(u8, u8, u8);
- e.g)
- To print out debugging information, the annotation
#[derive(Debug)]
should be added just before the struct definition.- The placeholder should be also changed into
{:?}
for single line print or{:#?}
for multiple line print.
- The placeholder should be also changed into
- Link: Derivable Traits
Click to expand
- Methods are defined within the context of a struct(or an enum or a trait object).
- Their first parameter is always
self
, which represents the instance of the struct the method is being called on.
- Their first parameter is always
- To define the method within the context of a struct, write an
impl
block and implement it in this block.- Use method syntax to call a method: add a dot followed by the method name, parentheses, and any arguments.
- e.g)
rect1.area()
- To have a method that changes the instance, use
&mut self
as the first parameter, otherwise&self
. - To have a method that takes ownership of the instance, use
self
as the first parameter.- c.f) But this is very rare case.
- Associated functions don't take
self
as a parameter, for they don't have an instance of the struct to work with.- e.g)
String::from()
- e.g)
Click to expand
- To define an enumeration, use
enum
keyword and write the variants of the enum inside curly brakets.- e.g)
enum IpAddrKind { V4, V6 }
- e.g)
- The variants of the enum are namespaced under its identifier so they should be separated by using a double colon.
- e.g)
let four = IpAddrKind::V4;
- e.g)
- The variants of the enum can have associated values.
- e.g)
enum IpAddr { V4(String), V6(String) }
- e.g)
- Each variant can also have different types and amounts of associated data.
- e.g)
enum IpAddr {V4(u8, u8, u8, u8), V6(String)}
- e.g)
- This IpAddr enum is already in use.
- Link: Enum IpAddr
- It is able to define methods on enums using
impl
like on structs. - Rust doesn't have the null feature.
- But it does have an enum that can encode the concept of a value being present or absent with the
Option<T>
enum. <T>
is a generic type parameter.- e.g)
enum Option<T> { Some(T), None, }
- But it does have an enum that can encode the concept of a value being present or absent with the
- Link: Enum Option
Click to expand
- The
match
operator allows to compare a value against a series of patterns and then execute code based on which pattern matches.- The expression after the
match
keyword ca be any type.
- The expression after the
- An arm has two parts: a pattern and some code.
- Patterns can be made up of literal values, variable names, wildcards, and so on.
- When the
match
expression executes, it compares the resulting value against the pattern of each arm, in order. - The code associated with each arm is an expression and the resulting value of it gets returned for the entire
match
expression. - Matches in Rust are exhaustive.
- Every possibility must be covered in order for the code to be valid.
- The
_
placeholder can cover default arms. - The
()
is just the unit value. - Link:
unit
Type
Click to expand
- The
if let
syntax combinesif
andlet
into a less verbose way to handle values that match just one pattern while ignoring the rest. - The
if let
andelse
is the same as thematch
block with one arm for the first pattern and another for the rest.- It means that the block of code that goes with the
else
is exactly same as the block of code that would go with the_
case in thematch
expression.
- It means that the block of code that goes with the
Click to expand
- A crate is a binary or library.
- The crate root is a source file that the Rust compiler starts from and makes up the root module of the crate.
- A package is one or more crates that provide a set of functionality.
- A package contains a Cargo.toml file that describes how to build those crates.
- Rules for what a package can contain:
- A package must contain zero or one library crates, and no more.
- A package can contain as many binary crates as possible, but at least one crate(either library or binary).
- Cargo follows a convention that src/main.rs is the crate root of a binary crate with the same name as the package.
- If the package directory contains src/lib.rs, Cargo recognizes src/lib.rs as its crate root.
- A package can have multiple binary crates by placing binaries in the src/ directory: each file will be a separate binary crate.
Click to expand
- Module helps to organize the code and controls the privacy of items.
- Public items can be used by outside code.
- Private items are not available for outside use.
- A module can be defined by starting with the
mod
keyword, and then the name of the module must be specified. - A module tree shows how modules are related.
- To use an item in a module tree, its path should be written.
- A path can take two forms:
- An absolute path starts from a crate root by using a crate name or a literal crate.
- A relative path starts from the current module and uses
self
,super
, or an identifier in the current module.
- Both are followed by one or more identifiers separated by double colons,
::
.
- All items(functions, methods, structs, enums, modules, and constants) in Rust are private by default.
- Items in a parent module can't use the private items inside child modules.
- Items in child modules can use the items in their ancestor modules.
- Private items in child modules can be exposed to their ancestor modules by using the
pub
keyword. - Using super, items in child modules can use the items in their parents.
- Structs and enums can also be public.
- Struct's fields will still be private.
- All of enum's variants become public.
Click to expand
- Data which collections point to is stored on the heap.
- A vector, string, hash map are commonly used collections in Rust programs.
- To create a new, empty vector, use
Vec::new
function.- Vectors must have data with the same type.
- e.g) `let v: Vec = Vec::new();
- e.g) `let v = vec![1, 2, 3];
- To reference an element in a vector, there are two ways:
- Using square brackets:
&v[2]
- Using a method:
v.get(2)
- When index out of bounds error occured, the program panics in 1. case but not in 2. case.
- Using square brackets:
- With an enum, the vector can store multiple types.
- Rust has only one string type in the core language, which is the string slice
str
. - Both
String
and the string slice are UTF-8 encoded. - Many of the same operations available with
Vec<T>
are available withString
as well.- e.g)
let mut s = String::new();
- e.g)
- Or can create a
String
from a string literal.- e.g)
let s = "initial contents".to_string();
- e.g)
let s = String::from("initial contents");
- e.g)
- To update a string, there are two ways:
- Using a method,
push_str(&str)
,push(&char)
- Using a
+
operator:fn add(self, s; &str) -> String {...}
add
takes ownership ofself
.
- Using a method,
- Rust compiler can coerce the
&String
into a&str
. - Rust doesn't support indexing.
- A
String
is a wrapper over aVec<u8>
. - But each unicode scalar value takes multiple bytes..
- So, directly indexing to a
String
element returns an unexpected value.
- A
- Slicing
String
returns&str
. - Hash maps are homogeneous.
- All of the keys must have the same type, and all of the values must have the same type.
- To construct a hash map:
- Use
insert
method:scores.insert(String::from("Blue"), 10));
- Use iterator and
collect
method:teams.into_iter().zip(vec_str, vec_score);
- Use
- Only one value can be associated with a key.
- Link
std::collections
Click to expand
- Rust has the type
Result<T, E>
for recoverable errors - Rust also has the
panic!
macro that stops execution when the program encounters an unrecoverable error. - The call to
panic!
causes the error message that shows the place where the panic occurred.- With the
RUST_BACKTRACE
environment variable, the error can be backtraced.
- With the
- With using
Result<T, E>
, recoverable errors are easily interpreted in runtime.- Different errors can also be treated differently by using
match {}
.
- Different errors can also be treated differently by using
unwrap()
method returns the value inside theOk
.expect()
method is similar tounwrap()
but it can convey the error message that is provided bypanic!
.- Instead of handling the error within the function, the erorr can be propagated to the calling code.
- Using the
?
operator, the implementation of propagating error can be much easier.
- Using the