This repo contains my notes on Rust programming language.
let x = 5;
mut after let:
let mut x = 5;
let x: i32 = 5;
i32 here is data type for 32-bit integer values.const PI:f32 = 3.14;
mut with them.PI:f32).let x = 5;
let x = 2.0 * 5 as f32;
x to create another immutable variable.mut to let statement, as that won't allow you to assign data of different data type. So, the following would cause an error:let mut x = 5;
x = 2.0 * 5 as f32; // error: expected integer, found `f32`
| 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 | size |
| Number literals | Example |
|---|---|
| Decimal | 98_222 |
| Hex | 0xff |
| Octal | 0o77 |
| Binary | 0b1111_0000 |
| Byte (u8 only) | b'A' |
57u8, and _ as a visual separator, such as 1_000i32f32 and f64f64. E.g. in let x = 2.0, x's data type is f64.true or false.bool data type.charchar literals are specified using single quoteslet heart_eyed_cat = '😻';let digit_2 = '\u{0032}';let tup: (i32, f64, u8) = (500, 6.4, 1);let (x, y, z) = tup;
println!("The value of y is: {}", y);
let (x, y, z) = tup; is called destructuring, because it breakes tuple into multiple partslet five_hundred = x.0;let a = [1, 2, 3, 4, 5];let a: [i32; 5] = [1, 2, 3, 4, 5];
i32, 5 means array elements are of type i32 and array length is 5let a = [3; 5];let first = a[0];main function (stored in main.rs file) is the entry point of Rust programs.fn keyword defines new functions
fn main() {
print!("Inside main()");
da_func();
}
fn da_func() {
print!("Inside da_func()");
}
Output:
Inside main()
Inside da_func()
fn da_func(x:i32, y:i32) { ...}. Note that data types for function parameters (like x and y here) is required.fn main() {
fn factorial(num: i32) -> i32 {
if num <= 1 {
1
}
else {
factorial(num - 1) + factorial(num - 2)
}
}
print!("Factorial of 5: {}", factorial(5));
}
factorial function above).= is considered to be an expression.
let x = 6, 6 is an expression.let x = 6 is a statement which do not return any value. So, you can't you something like let y = (let x = 6).let x = some_func();)let x = 5;
let y = {
let x = 3;
x + 1
};
print!("x = {}, y = {}", x, y);
Output:
x = 5, y = 4
Notice the following:
x + 1). Any statement which does not end with ; is considered as return expression.x inside the block and assigned it 3. This value will shadow the value 5 throughout the block. Once the block is over, x's value becomes 5.fn func() -> i32 {...}fn five() -> i32 {
5
}
This functions returns the value 5.fn type.fn type is called a function pointer.fn add_one(x: i32) -> i32 {
x + 1
}
// `do_twice` function accepts a function pointer
// `fn(i32) -> i32` as first parameter.
// This parameter will accept any function which has
// `i32` as a single parameter and the return type.
fn do_twice(f: fn(i32) -> i32, arg: i32) -> i32 {
f(arg) + f(arg)
}
fn main() {
// `add_one` function is passed as argument to `do_twice`
let answer = do_twice(add_one, 5);
println!("The answer is: {}", answer);
}
Output:
The answer is: 12
//
// flag for whether to perform search op or not
let search_flag = false;
let search_flag = false; // comment
if expression can also return values:
let x = if number > 8 {
"greater than 8"
}
else if some_val == 8 {
"number is 8"
}
else {
"less than 8"
}
let mut counter = 0;
let x = loop {
counter += 1;
if counter == 10 {
break 3
}
}
Here, once counter reaches 10, loop breaks and returns a value of 3 and gets stored in variable x.break expression, loop will go on forever.for .. in statement:
let a = [1, 2, 3, 4, 5];
for elem in a.iter() {
print!("elem = {}", elem);
}
for a in (1..4).rev() {
print!("{} ",a);
}
Output:
3 2 1
Here, 1..4 creates an array with values between 1 and 3 (excluding 4), and calling rev function reverses the order. Then use for loop to iterate through the array.
1..4 are end-exclusive. Here, 4 is ignored, and the for loop runs on values 1, 2 and 3.{..} inside of which the variable is defined.
{ // s is not valid here, it’s not yet declared
let s = "hello"; // s is valid from this point forward
// do stuff with s
} // this scope is now over, and s is no longer valid
str and are immutable.String are mutable.str to String via: let string = String::from("hello");string.push_str(" world");str):
str types.String::from requests the required memory{
let s = String::from("hello"); // s is valid from this point forward
// do stuff with s
} // this scope is now over, and s is no
// longer valid
drop function.drop function is like Resource Acquisition Is Initialization (RAII) in C++, which is a pattern of deallocating resources at the end of an item’s lifetimelet x = 5;
let y = x;
let s1 = String::from("hello");
let s2 = s1;
s1 to s2. This would make s1 invalid. If you try to access s1 later:let s1 = String::from("hello");
let s2 = s1;
println!("{}, world!", s1);
you would see the following error:
error[E0382]: borrow of moved value: `s1`
--> src/main.rs:5:28
|
2 | let s1 = String::from("hello");
| -- move occurs because `s1` has type `String`, which does not implement the `Copy` trait
3 | let s2 = s1;
| -- value moved here
4 |
5 | println!("{}, world!", s1);
| ^^ value borrowed here after move
String does not implement Copy trait (trait is like interface in Java and C++).
Copy, Rust would copy the data and you would see two different copies of the same data.s1 later in the above code.String implements Drop trait, s1 gets automatically invalidated when it goes out of scope.Copy, Clone and Drop traitsDrop trait, then the resource (data stored in a variable) is dropped or deleted once the associated variable goes out of scope.Copy trait,then the resource gets copied to the other variable.
Copy for a type:#[derive(Copy)]
struct Person {
//...
}
then all the fields of the type must implement the Copy trait or the code won't compile.
Copy trait acts as a marker for compiler that says: "you can duplicate myself with a simple bytes copy".Copy trait:
u32boolf32charCopy.
Copy, but (i32, String) do not.Copy trait to a type, then you can't add Drop trait, and vice-versa.
Drop trait could get dropped in the same scope. This won't allow you to use the resource in later part of the scope, even though you might expect otherwise.fn drop_copy_type<T>(T x)
where
T: Copy + Drop,
{
// The inner file descriptor is closed there:
std::mem::drop(x);
}
fn main() {
let mut file = File::open("foo.txt").unwrap();
drop_copy_type(file);
let mut contents = String::new();
// Oops, this is unsafe!
// We try to read an already closed file descriptor:
file.read_to_string(&mut contents).unwrap();
}
Clone trait, then you can call explicitly s1.clone() method to clone data stored in s1 variable. In this case, you can also implement Drop trait to the same type.
Clone and Drop can co-exist.fn main() {
let s = String::from("hello"); // s comes into scope
takes_ownership(s); // s's value moves into the function...
// ... and so is no longer valid here
let x = 5; // x comes into scope
makes_copy(x); // x would move into the function,
// but i32 is Copy, so it's okay to still use x afterward
} // Here, x goes out of scope, then s.
// But because s's value was moved, nothing special happens.
fn takes_ownership(some_string: String) {
// some_string comes into scope
println!("{}", some_string);
} // Here, some_string goes out of scope and `drop` is called.
// The backing memory is freed.
fn makes_copy(some_integer: i32) {
// some_integer comes into scope
println!("{}", some_integer);
} // Here, some_integer goes out of scope.
// Nothing special happens.
fn main() {
let s1 = gives_ownership(); // gives_ownership moves its return value into s1
let s2 = String::from("hello"); // s2 comes into scope
let s3 = takes_and_gives_back(s2);
// s2 is moved into takes_and_gives_back, which also
// moves its return value into s3
} // Here, s3 goes out of scope and is dropped.
// s2 goes out of scope but was moved, so nothing happens.
// s1 goes out of scope and is dropped.
fn gives_ownership() -> String {
// gives_ownership will move its return value into the
// function that calls it
// some_string comes into scope
let some_string = String::from("hello");
some_string // some_string is returned and
// moves out to the calling function
}
// takes_and_gives_back will take a String and return one
fn takes_and_gives_back(a_string: String) -> String {
// a_string comes into scope
a_string // a_string is returned and moves out to the
// calling function
}
drop unless the data has been moved to be owned by another variable.fn main() {
let str = String::from("hello");
let (len, str) = length(str);
print!("len of {} is {}", str, len);
}
fn length(s: String) -> (i32, String) {
(s.len(), s)
}
reference of the variable to the function:
fn main() {
let s1 = String::from("hello");
let len = calculate_length(&s1);
println!("The length of '{}' is {}.", s1, len);
}
fn calculate_length(s: &String) -> usize {
s.len()
}
&s1 syntax lets us create a reference that refers to the value of s1 but does not own it.
& to indicate that the type of the parameter s is a reference.
// s is a reference to a String
fn calculate_length(s: &String) -> usize {
s.len()
} // Here, s goes out of scope. But because it does not have ownership of what it refers to, nothing happens.
fn change(some_string: &String) {
some_string.push_str(", world");
}
Error:
error[E0596]: cannot borrow `*some_string` as mutable, as it is behind a `&` reference
|
7 | fn change(some_string: &String) {
| ------- help: consider changing this to be a mutable reference: `&mut String`
8 | some_string.push_str(", world");
| ^^^^^^^^^^^ `some_string` is a `&` reference, so the data it refers to cannot be borrowed as mutable
mut after &:
fn change(some_string: &mut String) {
some_string.push_str(", world");
}
let mut s = String::from("hello");
let r1 = &mut s;
let r2 = &mut s;
println!("{}, {}", r1, r2);
Error:
error[E0499]: cannot borrow `s` as mutable more than once at a time
|
4 | let r1 = &mut s;
| ------ first mutable borrow occurs here
5 | let r2 = &mut s;
| ^^^^^^ second mutable borrow occurs here
6 |
7 | println!("{}, {}", r1, r2);
| -- first borrow later used here
let mut s = String::from("hello");
{
let r1 = &mut s;
} // r1 goes out of scope here, so we can make a new reference with no problems.
let r2 = &mut s;
let mut s = String::from("hello");
let r1 = &s; // no problem
let r2 = &s; // no problem
let r3 = &mut s; // BIG PROBLEM
// immutable reference r1 and r2 used after creating
// mutable reference r3
println!("{}, {}, and {}", r1, r2, r3);
Error:
error[E0502]: cannot borrow `s` as mutable because it is also borrowed as immutable
--> src/main.rs:6:14
|
4 | let r1 = &s; // no problem
| -- immutable borrow occurs here
5 | let r2 = &s; // no problem
6 | let r3 = &mut s; // BIG PROBLEM
| ^^^^^^ mutable borrow occurs here
7 |
8 | println!("{}, {}, and {}", r1, r2, r3);
| -- immutable borrow later used here
let mut s = String::from("hello");
let r1 = &s; // no problem
let r2 = &s; // no problem
println!("{} and {}", r1, r2);
// r1 and r2 are no longer used after this point
let r3 = &mut s; // no problem
println!("{}", r3);
fn main() {
let reference_to_nothing = dangle();
}
fn dangle() -> &String {
let s = String::from("hello");
&s
}
Error thrown by compiler:
|
5 | fn dangle() -> &String {
| ^ expected named lifetime parameter
|
= help: this function's return type contains a borrowed value, but there is no value for it to be borrowed from
help: consider using the `'static` lifetime
this function's return type contains a borrowed value, but there is no value for it to be borrowed fromdangle function with comments to explain the above error message:fn dangle() -> &String { // dangle returns a reference to a String
let s = String::from("hello"); // s is a new String
&s // we return a reference to the String, s
} // Here, s goes out of scope, and is dropped.
// Its memory goes away. Danger!
String is dropped at the end of the function, &s would create a reference to invalid memory location.
let s = String::from("hello world");
let hello = &s[0..5];
let world = &s[6..11];
[starting_index..ending_index]:
starting_index is inclusiveending_index is exclusive.&s[0..5] above will result in "hello" (index 0 to index 4, 5 is excluded)let slice = &s[..2]; // 0 to 2
let slice = &s[2..]; // 2 to len - 1
let slice = &s[..]; // 0 to len - 1
&s[2..4] slice is stored as reference to index 2 of String s and number of elements (2);fn first_word(s: &String) -> &str {
let bytes = s.as_bytes();
// iter() returns each element.
// enumerate() transforms each element into a tuple of
// (index, reference of element)
for (i, &item) in bytes.iter().enumerate() {
// b' ' converts char ' ' into its byte equivalent
if item == b' ' {
return &s[0..i];
}
}
&s[..]
}
fn main() {
let mut s = String::from("hello world");
let word = first_word(&s);
println!("the first word is: {}", word);
}
main function, if you were to clear s you would run into error.
fn main() {
let mut s = String::from("hello world");
let word = first_word(&s);
s.clear(); // error!
println!("the first word is: {}", word);
}
Error:
error[E0502]: cannot borrow `s` as mutable because it is also borrowed as immutable
--> src/main.rs:18:5
|
16 | let word = first_word(&s);
| -- immutable borrow occurs here
17 |
18 | s.clear(); // error!
| ^^^^^^^^^ mutable borrow occurs here
19 |
20 | println!("the first word is: {}", word);
| ---- immutable borrow later used here
word contains immutable reference to the string and while its still in use, we call s.clear() which does mutable borrow.first_word returned the end index of the first word, then word would still contain the index even after s becomes empty, and that would create another bug (since we are trying to get 0 to non-zero index string out of an empty string)first_word's signature could be written as fn first_word(s: &str) -> &str. This way, first_word function can accept both &String and &str.s as arguments to first_word:
fn main() {
// String type
let my_string = String::from("hello world");
// first_word works on slices of `String`s
let word = first_word(&my_string[..]);
// str type
let my_string_literal = "hello world";
// first_word works on slices of string literals
let word = first_word(&my_string_literal[..]);
// Because string literals *are* string slices already,
// this works too, without the slice syntax!
let word = first_word(my_string_literal);
}
let a = [1, 2, 3, 4, 5];
let slice = &a[1..3];
assert_eq!(slice, &[2, 3]);
slice has the type &[i32].struct User {
username: String,
email: String,
sign_in_count: u64,
active: bool,
}
active property? Yeah, that's allowed in Rust. In Java enums, that extra comma would have thrown a compilation error.User:
let user1 = User {
email: String::from("someone@example.com"),
username: String::from("someusername123"),
active: true,
sign_in_count: 1,
};
User instance.mut:
let mut user1 = User {
email: String::from("someone@example.com"),
username: String::from("someusername123"),
active: true,
sign_in_count: 1,
};
user1.email = String::from("anotheremail@example.com");
new that would return instance of the struct.struct Rectangle {
width: u32,
height: u32,
}
fn main() {
let rect1 = Rectangle {
width: 30,
height: 50,
};
println!(
"The area of the rectangle is {} square pixels.",
area(&rect1)
);
}
fn area(rectangle: &Rectangle) -> u32 {
rectangle.width * rectangle.height
}
fn build_user(email: String, username: String) -> User {
User {
email,
username,
active: true,
sign_in_count: 1,
}
}
let user2 = User {
email: String::from("another@example.com"),
username: String::from("anotherusername567"),
..user1
};
user2 would be used from user1.struct Color(i32, i32, i32);
struct Point(i32, i32, i32);
let black = Color(0, 0, 0);
let origin = Point(0, 0, 0);
struct Dimension(u32, u32);
fn main() {
let rect1 = Dimension(30, 50);
println!(
"The area of the rectangle is {} square pixels.",
area(rect1)
);
}
fn area(dimensions: Dimension) -> u32 {
dimensions.0 * dimensions.1
}
struct Rectangle {
width: u32,
height: u32,
}
fn main() {
let rect1 = Rectangle {
width: 30,
height: 50,
};
println!("rect1 is {}", rect1);
}
the program throws an error:
error[E0277]: `Rectangle` doesn't implement `std::fmt::Display`
Display trait because they can be shown in just one way. Structs do not.Debug on a struct as follows:
#[derive(Debug)]
struct Rectangle {
width: u32,
height: u32,
}
fn main() {
let rect1 = Rectangle {
width: 30,
height: 50,
};
println!("rect1 is {:?}", rect1);
}
Output:
rect1 is Rectangle { width: 30, height: 50 }
{:#?} instead in println! macro.
rect1 is Rectangle {
width: 30,
height: 50,
}
self (take ownership) or&self (immutable borrow) or&mut self (mutable borrow).#[derive(Debug)]
struct Rectangle {
width: u32,
height: u32,
}
impl Rectangle {
fn area(&self) -> u32 {
self.width * self.height
}
}
fn main() {
let rect1 = Rectangle {
width: 30,
height: 50,
};
println!(
"The area of the rectangle is {} square pixels.",
rect1.area()
);
}
impl (implementation) blocks for a given struct.impl Rectangle {
fn can_hold(&self, other: &Rectangle) -> bool {
self.width > other.width && self.height > other.height
}
}
fn main() {
let rect1 = Rectangle {
width: 30,
height: 50,
};
let rect2 = Rectangle {
width: 10,
height: 40,
};
println!("Can rect1 hold rect2? {}", rect1.can_hold(&rect2));
}
impl blocks:
impl Rectangle {
fn area(&self) -> u32 {
self.width * self.height
}
}
impl Rectangle {
fn can_hold(&self, other: &Rectangle) -> bool {
self.width > other.width && self.height > other.height
}
}
object.something(), Rust automatically adds in &, &mut, or * so object matches the signature of the method.p1.distance(&p2);
(&p1).distance(&p2);
self.&self), mutating (&mut self), or consuming (self).self or its variants as first parameters.impl Rectangle {
fn square(size: u32) -> Rectangle {
Rectangle {
width: size,
height: size,
}
}
}
fn main() {
let square = Rectangle::square(3);
}
:: syntax with the struct name.type c = a | b in languages like OCaml allows us to identify instance of a or b as instance of c.enum Message {
Quit,
Move { x: i32, y: i32 },
Write(String),
ChangeColor(i32, i32, i32),
}
impl Message {
fn call(&self) {
//..
}
}
fn main() {
let m = Message::Write(String::from("hello"));
m.call();
}
Message is a sum type of the following types: Quit, Move, Write, ChangeColor.
impl blocks (same as in structs):: operator (since enum types are like associated functions in structs; enum Message serves as namespace for these four enum types).Option EnumOption enum:
enum Option<T> {
Some(T),
None,
}
Option enum is available in Rust programs by default (included in the prelude), so don't have to import it.Option enum:
let some_number = Some(5);
let some_string = Some("a string");
let absent_number: Option<i32> = None;
None to a variable, you need to specify the variable's data type. Rust couldn't have inferred the type T in Option enum otherwise.Option is better than null:
let x: i8 = 5;
let y: Option<i8> = Some(5);
let sum = x + y;
x and y are of two different types (x is i8 and y is Option<i8>)public static void main(String[] args) {
Integer x = 0;
Integer y = null;
Integer z = x + y;
}
match control flowmatch operator.
enum Coin {
Penny,
Nickel,
Dime,
Quarter,
}
fn value_in_cents(coin: Coin) -> u8 {
match coin {
Coin::Penny => 1,
Coin::Nickel => {
println!("coin type is Nickel");
5
},
Coin::Dime => 10,
Coin::Quarter => 25,
}
}
Penny, value_in_cents will return 1.match block like Coin::Penny => 1 is called a match arm.
Coin::Penny is called patternCoin::Nickel above.Option matching example:
fn plus_one(x: Option<i32>) -> Option<i32> {
match x {
None => None,
Some(i) => Some(i + 1),
}
}
let five = Some(5);
let six = plus_one(five);
let none = plus_one(None);
fn plus_one(x: Option<i32>) -> Option<i32> {
match x {
Some(i) => Some(i + 1),
}
}
Error
error[E0004]: non-exhaustive patterns: `None` not covered
--> src/main.rs:3:15
|
3 | match x {
| ^ pattern `None` not covered
|
= help: ensure that all possible cases are being handled, possibly by adding wildcards or more match arms
= note: the matched value is of type `Option<i32>`
_:
let num_to_str = match input_num {
1 => "one",
3 => "three",
5 => "five",
7 => "seven",
_ => "default",
};
input_num values not equal to either 1, 3, 5, or 7, the match expression would return "default" and gets stored in num_to_str variable.let x = Some(5);
let y = 10;
match x {
Some(50) => println!("Got 50"),
Some(y) => println!("Matched, y = {:?}", y),
_ => println!("Default case, x = {:?}", x),
}
println!("at the end: x = {:?}, y = {:?}", x, y);
Output:
Matched, y = 5
at the end: x = Some(5), y = 10
y gets shadowed inside match arm Some(y) and y value becomes 5.x value was None, the output would have been:
Default case, x = None
at the end: x = None, y = 10
matchlet x = 1;
match x {
1 | 2 => println!("one or two"),
3 => println!("three"),
_ => println!("anything"),
}
Output:
one or two
..=let x = 5;
match x {
1..=5 => println!("one through five"),
_ => println!("something else"),
}
If x is 1, 2, 3, 4, or 5, the first arm will match.char range:
let x = 'c';
match x {
'a'..='j' => println!("early ASCII letter"),
'k'..='z' => println!("late ASCII letter"),
_ => println!("something else"),
}
Output:
early ASCII letter
struct Point {
x: i32,
y: i32,
}
fn main() {
let p = Point { x: 0, y: 7 };
let Point { x, y } = p;
assert_eq!(0, x);
assert_eq!(7, y);
}
fn main() {
let p = Point { x: 0, y: 7 };
let Point { x: a, y: b } = p;
assert_eq!(0, a);
assert_eq!(7, b);
}
Here, two new variables a and b are created that match the values x and y.match:
fn main() {
let p = Point { x: 0, y: 7 };
match p {
Point { x, y: 0 } => println!("On the x axis at {}", x),
Point { x: 0, y } => println!("On the y axis at {}", y),
Point { x, y } => println!("On neither axis: ({}, {})", x, y),
}
}
Output:
On the y axis at 7
enum Message {
Quit,
Move { x: i32, y: i32 },
Write(String),
ChangeColor(i32, i32, i32),
}
fn main() {
let msg = Message::ChangeColor(0, 160, 255);
match msg {
Message::Quit => {
println!("The Quit variant has no data to destructure.")
}
Message::Move { x, y } => {
println!(
"Move in the x direction {} and in the y direction {}",
x, y
);
}
Message::Write(text) => println!("Text message: {}", text),
Message::ChangeColor(r, g, b) => println!(
"Change the color to red {}, green {}, and blue {}",
r, g, b
),
}
}
Output:
Change the color to red 0, green 160, and blue 255
let ((feet, inches), Point { x, y }) = ((3, 10), Point { x: 3, y: -10 });
This declares and assigns the following variables: feet = 3, inches = 10, x = 3, y = -10._fn foo(_: i32, y: i32) {
println!("This code only uses the y parameter: {}", y);
}
fn main() {
foo(3, 4);
}
Output:
This code only uses the y parameter: 4
_let numbers = (2, 4, 8, 16, 32);
match numbers {
(first, _, third, _, fifth) => {
println!("Some numbers: {}, {}, {}", first, third, fifth)
}
}
Output:
Some numbers: 2, 8, 32
_fn main() {
let _x = 5;
let y = 10;
}
y, but we don't get any warning for _x._x and _; let _x = value still binds value to the variable _x. For example:
let s = Some(String::from("Hello!"));
if let Some(_s) = s {
println!("found a string");
}
println!("{:?}", s);
would result in compile-time error as value String::from("Hello!") would move to _s, thereby making s variable after if let expression invalid.
Some(_) instead, the code will compile just fine...struct Point {
x: i32,
y: i32,
z: i32,
}
let origin = Point { x: 3, y: 0, z: 0 };
match origin {
Point { x, .. } => println!("x is {}", x),
}
Output:
x is 3
fn main() {
let numbers = (2, 4, 8, 16, 32);
match numbers {
(first, .., last) => {
println!("first: {}, last: {}", first, last);
}
}
}
Output:
first: 2, last: 32
.. must be unambiguous. The following code will result in compile error:
fn main() {
let numbers = (2, 4, 8, 16, 32);
match numbers {
(.., second, ..) => {
println!("Some numbers: {}", second)
},
}
}
Error:
error: `..` can only be used once per tuple pattern
--> src/main.rs:5:22
|
5 | (.., second, ..) => {
| -- ^^ can only be used once per tuple pattern
| |
| previously used here
if condition specified after the pattern in a match arm that must also match, along with the pattern matching, for that arm to be chosen.let num = Some(4);
match num {
Some(x) if x < 5 => println!("less than five: {}", x),
Some(x) => println!("{}", x),
None => (),
}
Output:
less than five: 4
fn main() {
let x = Some(5);
let y = 5;
match x {
Some(50) => println!("Got 50"),
Some(n) if n == y => println!("Matched, n = {}", n),
_ => println!("Default case, x = {:?}", x),
}
println!("at the end: x = {:?}, y = {}", x, y);
}
Output:
Matched, n = 5
at the end: x = Some(5), y = 5
Some(n) if n == y match arm doesn't introduce a new variable y inside match scope, and thus can use outer y as match guard.let x = 4;
let y = false;
match x {
4 | 5 | 6 if y => println!("yes"),
_ => println!("no"),
}
4 | 5 | 6, followed by the match guard if y.@ bindings@ bindigs:
enum Message {
Hello { id: i32 },
}
let msg = Message::Hello { id: 5 };
match msg {
Message::Hello {
id: id_variable @ 3..=7,
} => println!("Found an id in range: {}", id_variable),
Message::Hello { id: 10..=12 } => {
println!("Found an id in another range")
}
Message::Hello { id } => println!("Found some other id: {}", id),
}
5 inside id variable when it checks whether the value of id field after destructuring msg falls in 3..=7 range.@ binding: id: id_variable @ 3..=7id_variable @ tells Rust to create a new variable named id_variable storing the matched value (here 5).#[derive(Debug)]
enum UsState {
Alabama,
Alaska,
// --snip--
}
enum Coin {
Penny,
Nickel,
Dime,
Quarter(UsState),
}
fn value_in_cents(coin: Coin) -> u8 {
match coin {
Coin::Penny => 1,
Coin::Nickel => 5,
Coin::Dime => 10,
Coin::Quarter(state) => {
println!("State quarter from {:?}!", state);
25
}
}
}
value_in_cents(Coin::Quarter(UsState::Alaska)), coin would be Coin::Quarter(UsState::Alaska).Coin::Quarter(state).state will be the value UsState::Alaska.if let syntaxif let syntax.let num_to_str = match input_num {
1 => "one",
3 => "three",
5 => "five",
7 => "seven",
_ => "default",
};
7 and ignore the rest, you could use if let:
let num_to_str = if let 7 = input_num {
"seven"
}
else {
"default"
}
fn func(favorite_color: Option<&str>, is_tuesday: bool,
age: Result<u8, _>) {
if let Some(color) = favorite_color {
println!("Using your favorite color, {}, as the background", color);
} else if let Ok(age) = age {
if age > 30 {
println!("Using purple as the background color");
} else {
println!("Using orange as the background color");
}
} else {
println!("Using blue as the background color");
}
}
favorite_color is Some("red"), then the output would be Using your favorite color, red, as the background.favorite_color is None and age is Ok(34), then the output would be Using orange as the background colorUsing blue as the background color.while let expressionlet mut stack = Vec::new();
stack.push(1);
stack.push(2);
stack.push(3);
while let Some(top) = stack.pop() {
println!("{}", top);
}
Output:
3
2
1
stack.pop() returns Some(val).stack.pop() returns None.for looplet v = vec!['a', 'b', 'c'];
for (index, value) in v.iter().enumerate() {
println!("{} is at index {}", value, index);
}
Output:
a is at index 0
b is at index 1
c is at index 2
let statementslet (x, y, z) = (1, 2, 3);
This would assign x = 1, y = 2, z = 3.let (x, y) = (1, 2, 3);
Output:
error[E0308]: mismatched types
fn print_coordinates(&(x, y): &(i32, i32)) {
println!("Current location: ({}, {})", x, y);
}
fn main() {
let point = (3, 5);
print_coordinates(&point);
}
Output:
Current location: (3, 5)
Patterns come in two forms: refutable and irrefutable.
Patterns that will match for any possible value passed are irrefutable.
x in statement let x = 5, because x matches anything and therefore cannot fail to match.Patterns that can fail to match for some possible value are refutable.
Some(x) in if let Some(x) = a_value expression, because if the value of a_value is None, then the Some(x) pattern won't match.Function parameters, let statements, and for loops can only accept irrefutable patterns, because the program cannot do anything meaningful when values don’t match.
let Some(x) = some_option_value;
as Some(x) = .. is a refutable pattern and let requires irrefutable patterns. So, let requires that None = pattern to also be covered.match with let for above:
let x = match {
Some(val) => val,
None => DEFAULT_VALUE
};
The if let and while let expressions accept refutable and irrefutable patterns, but the compiler warns against irrefutable patterns.
if let Some(x) = some_option_value {
println!("{}", x);
}
if let x = 5 {
println!("{}", x);
};
but compiler would give you a warning:
warning: irrefutable `if let` pattern
because using irrefutable pattern with if let is useless.Pattern refutability explains why match arms must use refutable patterns, except for the last arm, which should match any remaining values with an irrefutable pattern.
cargo new command:
$ cargo new my-project
Created binary (application) `my-project` package
$ ls my-project
Cargo.toml
src
$ ls my-project/src
main.rs
Cargo.toml is created, giving us a package.src/main.rs is mentioned in the contents of Cargo.toml file.
src/main.rs is the crate root of a binary crate with the same name as the package.src/lib.rs, that becomes the crate root of a library crate with the same name as the package.rustc to build the library or binary.src/main.rs and src/lib.rs, it has two crates: a library and a binary, both with the same name as the package.src/bin directory: each file will be a separate binary crate.struct Rng and then we import a crate rand which also has a struct named RngRng that we defined.Rng trait from the rand crate as rand::Rng.restaurant by running cargo new --lib restaurant, and then add the following code to src/lib.rs file:
mod front_of_house {
mod hosting {
fn add_to_waitlist() {}
fn seat_at_table() {}
}
mod serving {
fn take_order() {}
fn serve_order() {}
fn take_payment() {}
}
}
mod keyword followed by the name of the module (e.g. mod front_of_house)snake_case)src/main.rs and src/lib.rs are called crate roots because the contents of either of these two files form a module named crate at the root of the crate’s module structure, known as the module tree.
restaurant library:
crate
└── front_of_house
├── hosting
│ ├── add_to_waitlist
│ └── seat_at_table
└── serving
├── take_order
├── serve_order
└── take_payment
front_of_house is parent of hosting, and hosting is child of front_of_house.hosting and serving are siblings.crate.self, super, or an identifier in the current module.::).mod front_of_house {
mod hosting {
fn add_to_waitlist() {}
}
}
pub fn eat_at_restaurant() {
// Absolute path
crate::front_of_house::hosting::add_to_waitlist();
// Relative path
front_of_house::hosting::add_to_waitlist();
}
error[E0603]: module `hosting` is private
--> src/lib.rs:9:28
|
9 | crate::front_of_house::hosting::add_to_waitlist();
| ^^^^^^^ private module
|
note: the module `hosting` is defined here
--> src/lib.rs:2:5
|
2 | mod hosting {
| ^^^^^^^^^^^
pub keyword. Example:
mod front_of_house {
pub mod hosting {
fn add_to_waitlist() {}
}
}
pub fn eat_at_restaurant() {
crate::front_of_house::hosting::add_to_waitlist();
}
add_to_waitlist is not declared as pub.
fn add_to_waitlist() to pub fn add_to_waitlist() will compile the code.super keyword
cd .. in bash to go up to parent directory.fn serve_order() {}
mod back_of_house {
fn fix_incorrect_order() {
cook_order();
super::serve_order();
}
fn cook_order() {}
}
super will take compiler to search for serve_order function in parent module of back_of_house. In this case, it does find the function and hence code compiles.mod company {
pub struct Employee {
name: String
}
impl Employee {
pub fn new(name: String) -> Employee {
Employee {
name
}
}
pub fn get_name(&self) -> &String {
&self.name
}
}
}
fn main() {
let employee = company::Employee::new(String::from("Jethalal"));
// cannot create instance of Employee here as name is private. So below code will result in error
/*
let employee = company::Employee {
name: String::from("Jethalal")
};
*/
// trying to set employee name would throw error, as name is not visible
// employee.name = "Ramesh";
println!("name: {}", employee.get_name());
}
mod back_of_house {
pub enum Appetizer {
Soup,
Salad,
}
}
pub fn eat_at_restaurant() {
let order1 = back_of_house::Appetizer::Soup;
let order2 = back_of_house::Appetizer::Salad;
}
use keyworduse keyword. Example:
mod front_of_house {
pub mod hosting {
pub fn add_to_waitlist() {}
}
}
use crate::front_of_house::hosting;
pub fn eat_at_restaurant() {
hosting::add_to_waitlist();
hosting::add_to_waitlist();
hosting::add_to_waitlist();
}
use crate::front_of_house::hosting in the crate root, hosting is now a valid name in that scope, just as though the hosting module had been defined in the crate root.use also check privacy, like any other paths.mod front_of_house {
pub mod hosting {
pub fn add_to_waitlist() {}
}
}
use self::front_of_house::hosting;
pub fn eat_at_restaurant() {
hosting::add_to_waitlist();
hosting::add_to_waitlist();
hosting::add_to_waitlist();
}
use self::front_of_house::hosting::add_to_waitlist and directly use add_to_waitlist function:
use crate::front_of_house::hosting::add_to_waitlist;
pub fn eat_at_restaurant() {
add_to_waitlist();
}
add_to_wishlist function is located (same module or different).
use self::front_of_house::hosting) and then call the function (hosting::add_to_waitlist()).use std::collections::HashMap;
fn main() {
let mut map = HashMap::new();
map.insert(1, 2);
}
use std::fmt;
use std::io;
fn function1() -> fmt::Result {
// --snip--
}
fn function2() -> io::Result<()> {
// --snip--
}
Result struct from two different modules by bringing parent modules std::fmt and std::io into scope.Result via complete path like use std::fmt::Result, then Rust wouldn't know which Result struct to use.as keyword:
use std::fmt::Result;
use std::io::Result as IoResult;
fn function1() -> Result {
// --snip--
}
fn function2() -> IoResult<()> {
// --snip--
}
Cargo.toml:
[dependencies]
algorithms = "0.1.1"
you tell Cargo to download the algorithm package and its dependencies and make this external package available to our project.use the algorithm crate in any Rust project file as follows:
use algorithms;
fn main() {
let factorial_of_5 = algorithms::factorial(5);
}
std) is also a crate that’s external to our package.
std.use to bring items from there into our package’s scope:
use std::collections::HashMap;
std, the name of the standard library crate.// --snip--
use std::cmp::Ordering;
use std::io;
// --snip--
as
// --snip--
use std::{cmp::Ordering, io};
// --snip--
use statements share a subpath like below:
use std::io;
use std::io::Write;
we can write it as:
use std::io::{self, Write};
*, the glob operator:
use std::collections::*;
std::collections into the current scope.tests module.mod front_of_house {
pub mod hosting {
pub fn add_to_waitlist() {}
}
}
use self::front_of_house::hosting;
pub fn eat_at_restaurant() {
hosting::add_to_waitlist();
hosting::add_to_waitlist();
hosting::add_to_waitlist();
}
and you want to split it into different files for each module.<module-name>.rs or create a directory called <module-name> and then store it in <module-name>/mod.rs.src/lib.rs:
mod front_of_house;
use crate::front_of_house::hosting;
pub fn eat_at_restaurant() {
hosting::add_to_waitlist();
hosting::add_to_waitlist();
hosting::add_to_waitlist();
}
mod followed by <module-name> and a semicolon ; (e.g. mod front_of_house;) tells Rust to load that module file into scope.use statement with the refactoring of code into multiple files.src/front_of_house/mod.rs
pub mod hosting;
src/front_of_house/hosting.rs
pub fn add_to_waitlist() {}
Vec<T>, allows you to store more than one value in a single data structure that puts all the values next to each other in memory.// immutable vector of integers
let v: Vec<i32> = Vec::new();
// immutable vector of integers 1,2 and 3
let v = vec![1,2,3];
let mut v = Vec::new();
v.push(5);
v.push(6);
v.push(7);
v.push(8);
mut while declaring the vector.{
let v = vec![1, 2, 3, 4];
// do stuff with v
} // <- v goes out of scope and is freed here
let v = vec![1, 2, 3, 4, 5];
let third: &i32 = &v[2];
println!("The third element is {}", third);
match v.get(2) {
Some(third) => println!("The third element is {}", third),
None => println!("There is no third element."),
}
&v[100] in above example would result in panic and thus crashing the program.v.get(100) would return None and need to handle it accordingly.let mut v = vec![1, 2, 3, 4, 5];
let first = &v[0];
v.push(6);
println!("The first element is: {}", first);
Error:
error[E0502]: cannot borrow `v` as mutable because it is also borrowed as immutable
--> src/main.rs:6:5
|
4 | let first = &v[0];
| - immutable borrow occurs here
5 |
6 | v.push(6);
| ^^^^^^^^^ mutable borrow occurs here
7 |
8 | println!("The first element is: {}", first);
| ----- immutable borrow later used here
push method can result in copying elements to new memory space, and thus first will point to invalid memory location.let v = vec![100, 32, 57];
for i in &v {
println!("{}", i);
}
let mut v = vec![100, 32, 57];
for i in &mut v {
*i += 50;
}
*) to get to the value in i before we can use the += operator.enum SpreadsheetCell {
Int(i32),
Float(f64),
Text(String),
}
let row = vec![
SpreadsheetCell::Int(3),
SpreadsheetCell::Text(String::from("blue")),
SpreadsheetCell::Float(10.12),
];
str that is usually seen in its borrowed form &str.String type, which is provided by Rust’s standard library rather than coded into the core language, is a growable, mutable, owned, UTF-8 encoded string type.
String type and &str string slice type.String:
let mut s = String::new();
str literals to String:
let s = "initial contents".to_string();
// or
let s = String::from("initial contents");
let hello = String::from("नमस्ते");
let mut s1 = String::from("foo");
let s2 = "bar";
s1.push_str(s2);
println!("s2 is {}", s2);
push_str function takes a string slice, the calling function doesn't lose ownership of s2 and can thus print its value.push function:
let mut s = String::from("lo");
s.push('l');
s will contain lol+ operatorlet s1 = String::from("Hello, ");
let s2 = String::from("world!");
let s3 = s1 + &s2; // note s1 has been moved here and can no longer be used
+ operator uses the add method:
fn add(self, s: &str) -> String {
s3, s2 remains valid as s2 was borrowed. However, s1 becomes invalid since ownership was moved to add function.&s2 is of type &String, but add accepts &str as second parameter. This works because Rust uses a deref coercion which turns &s2 into &s2[..].let s3 = s1 + &s2; isn't making new strings, but rather:
format! macrolet s1 = String::from("tic");
let s2 = String::from("tac");
let s3 = String::from("toe");
let s = format!("{}-{}-{}", s1, s2, s3);
macro works in the same way asprintln!`, but instead of printing the output to the screen, it returns a String with the contents.format! is much easier to read and doesn’t take ownership of any of its parameters.let s1 = String::from("hello");
let h = s1[0];
Error:
error[E0277]: the type `String` cannot be indexed by `{integer}`
--> src/main.rs:3:13
|
3 | let h = s1[0];
| ^^^^^ `String` cannot be indexed by `{integer}`
|
= help: the trait `Index<{integer}>` is not implemented for `String`
Strings are implemented in Rust.Strings
String is a wrapper over a Vec<u8>let hello = String::from("Hola");
hello.len() will be 4, which means the vector storing the string "Hola" is 4 bytes long.
let hello = String::from("Здравствуйте");
vector is 24 bytes long, not 12. This is because Cyrillic letters take 2 bytes of storage.&hello[0] will return first byte value stored, not the character З (which is capital Cyrillic letter Ze).&hello[0].नमस्ते as bytes:
[224, 164, 168, 224, 164, 174, 224, 164, 184, 224, 165, 141, 224, 164, 164, 224, 165, 135]
नमस्ते as Unicode scalar values:
['न', 'म', 'स', '्', 'त', 'े']
नमस्ते as grapheme clusters:
["न", "म", "स्", "ते"]
let hello = "Здравствуйте";
let s = &hello[0..1];
Error:
thread 'main' panicked at 'byte index 1 is not a char boundary; it is inside 'З' (bytes 0..2) of `Здравствуйте`', src/main.rs:4:14
&hello[0..3] works OK as Cyrillic characters are stored as 2 bytes.Stringsfor c in "नमस्ते".chars() {
println!("{}", c);
}
Output:
न
म
स
्
त
े
for b in "नमस्ते".bytes() {
println!("{}", b);
}
Output:
224
164
// --snip--
165
135
HashMap<K, V> stores a mapping of keys of type K to values of type V.use std::collections::HashMap;
let mut scores = HashMap::new();
scores.insert(String::from("Blue"), 10);
scores.insert(String::from("Yellow"), 50);
zip and collect functions:
use std::collections::HashMap;
let teams = vec![String::from("Blue"), String::from("Yellow")];
let initial_scores = vec![10, 50];
let mut scores: HashMap<_, _> =
teams.into_iter().zip(initial_scores.into_iter()).collect();
HashMap<_, _> is needed here because it’s possible to collect into many different data structures and Rust doesn’t know which you want unless you specify.
String and the value type will be i32zip method is used to create a vector of tuples where team is paired with initial score (e.g. “Blue” is paired with 10).collect method to turns vector of tuples into a hash map.Copy trait, like i32, the values are copied into the hash map.String, the values will be moved and the hash map will be the owner of those values.use std::collections::HashMap;
let field_name = String::from("Favorite color");
let field_value = String::from("Blue");
let mut map = HashMap::new();
map.insert(field_name, field_value);
// field_name and field_value are invalid at this point, try using them and
// see what compiler error you get!
use std::collections::HashMap;
let mut scores = HashMap::new();
scores.insert(String::from("Blue"), 10);
scores.insert(String::from("Yellow"), 50);
let team_name = String::from("Blue");
let score = scores.get(&team_name);
score will have the value that’s associated with the Blue team, and the result will be Some(&10).Some because get returns an Option<&V>; if there’s no value for that key in the hash map, get will return None.use std::collections::HashMap;
let mut scores = HashMap::new();
scores.insert(String::from("Blue"), 10);
scores.insert(String::from("Yellow"), 50);
for (key, value) in &scores {
println!("{}: {}", key, value);
}
Output:
Yellow: 50
Blue: 10
use std::collections::HashMap;
let mut scores = HashMap::new();
scores.insert(String::from("Blue"), 10);
scores.insert(String::from("Blue"), 25);
println!("{:?}", scores);
Output:
{"Blue": 25}
entry followed by or_insert methods:
use std::collections::HashMap;
let mut scores = HashMap::new();
scores.insert(String::from("Blue"), 10);
scores.entry(String::from("Yellow")).or_insert(50);
scores.entry(String::from("Blue")).or_insert(50);
println!("{:?}", scores);
entry method is an enum called Entry that represents a value that might or might not exist.or_insert method on Entry:
Entry key, or else{"Yellow": 50, "Blue": 10}
use std::collections::HashMap;
let text = "hello world wonderful world";
let mut map = HashMap::new();
for word in text.split_whitespace() {
let count = map.entry(word).or_insert(0);
*count += 1;
}
println!("{:?}", map);
Output:
{"world": 2, "hello": 1, "wonderful": 1}
or_insert method returns mutable reference to value for the key.*) (e.g. *count += 1 above)HashMap uses a hashing function called SipHash that can provide resistance to Denial of Service (DoS) attacks involving hash tables.BuildHasher trait.panic! macro signals that your program is in a state it can’t handle and lets you tell the process to stop instead of trying to proceed with invalid or incorrect values.Result enum uses Rust’s type system to indicate that operations might fail in a way that your code could recover from.fn main() {
panic!("crash and burn");
}
Error:
thread 'main' panicked at 'crash and burn', src/main.rs:2:5
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
'crash and burn') and the place in our source code where the panic occurred: src/main.rs:2:5 indicates that it’s the second line, fifth character of our src/main.rs file.panic! backtracefn main() {
let v = vec![1, 2, 3];
v[99];
}
thread 'main' panicked at 'index out of bounds: the len is 3 but the index is 99', src/main.rs:4:5
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
RUST_BACKTRACE environment variable to get a backtrace of exactly what happened to cause the error.RUST_BACKTRACE=1 in cargo run command:
$ RUST_BACKTRACE=1 cargo run
thread 'main' panicked at 'index out of bounds: the len is 3 but the index is 99', src/main.rs:4:5
stack backtrace:
0: rust_begin_unwind
at /rustc/7eac88abb2e57e752f3302f02be5f3ce3d7adfb4/library/std/src/panicking.rs:483
1: core::panicking::panic_fmt
at /rustc/7eac88abb2e57e752f3302f02be5f3ce3d7adfb4/library/core/src/panicking.rs:85
2: core::panicking::panic_bounds_check
at /rustc/7eac88abb2e57e752f3302f02be5f3ce3d7adfb4/library/core/src/panicking.rs:62
3: <usize as core::slice::index::SliceIndex<[T]>>::index
at /rustc/7eac88abb2e57e752f3302f02be5f3ce3d7adfb4/library/core/src/slice/index.rs:255
4: core::slice::index::<impl core::ops::index::Index<I> for [T]>::index
at /rustc/7eac88abb2e57e752f3302f02be5f3ce3d7adfb4/library/core/src/slice/index.rs:15
5: <alloc::vec::Vec<T> as core::ops::index::Index<I>>::index
at /rustc/7eac88abb2e57e752f3302f02be5f3ce3d7adfb4/library/alloc/src/vec.rs:1982
6: panic::main
at ./src/main.rs:4
7: core::ops::function::FnOnce::call_once
at /rustc/7eac88abb2e57e752f3302f02be5f3ce3d7adfb4/library/core/src/ops/function.rs:227
# rest of the backtrace
at ./src/main.rs:4) are called debug symbols.cargo build --release or cargo run --release.panic = 'abort' to the appropriate [profile] sections in your Cargo.toml file.[profile.release]
panic = 'abort'
ResultResult enum:
enum Result<T, E> {
Ok(T),
Err(E),
}
T represents the type of the value that will be returned in a success case within the Ok variant, andE represents the type of the error that will be returned in a failure case within the Err variant.Result enum:
use std::fs::File;
fn main() {
let f = File::open("hello.txt");
let f = match f {
Ok(file) => file,
Err(error) => panic!("Problem opening the file: {:?}", error),
};
}
Result don't need to be imported via use as its brought into scope via prelude.Ok, return the inner file value out of the Ok variant, and we then assign that file handle value to the variable f.Err value from File::open. In this example, we’ve chosen to call the panic! macro.
hello.txt file does not exist.use std::fs::File;
use std::io::ErrorKind;
fn main() {
match File::open("hello.txt") {
Ok(file) => file,
Err(error) => match error.kind() {
ErrorKind::NotFound => match File::create("hello.txt") {
Ok(fc) => fc,
Err(e) => panic!("Problem creating the file: {:?}", e),
},
// capture all other errors in `other_error` variable
other_error => panic!("Problem opening the file: {:?}", other_error)
},
};
}
File::open returns inside the Err variant is io::Error, which is a struct provided by the standard library.io::ErrorKind value.io::ErrorKind is provided by the standard library and has variants representing the different kinds of errors that might result from an io operation.ErrorKind::NotFound, which indicates the file we’re trying to open doesn’t exist yet.File::open("hello.txt"), but we also have an inner match on error.kind().unwrap_or_else of Result enum to open or create file:
use std::fs::File;
use std::io::ErrorKind;
fn main() {
File::open("hello.txt").unwrap_or_else(|error| match error.kind() {
ErrorKind::NotFound => File::create("hello.txt")
.unwrap_or_else(|error| panic!("Problem creating the file: {:?}", error),
other_error => panic!("Problem opening the file: {:?}", other_error)
}
);
}
use std::fs::File;
fn main() {
let f = File::open("hello.txt").unwrap();
}
hello.txt is present, then File object is assigned to f.
thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: Error {
repr: Os { code: 2, message: "No such file or directory" } }',
src/libcore/result.rs:906:4
expect method:
use std::fs::File;
fn main() {
let f = File::open("hello.txt").expect("Failed to open hello.txt");
}
Output:
thread 'main' panicked at 'Failed to open hello.txt: Error { repr: Os { code:
2, message: "No such file or directory" } }', src/libcore/result.rs:906:4
unwrap or expect, because that would make the program crash. Instead, use unwrap_or_else method to handle errors in code itself.? operatoruse std::fs::File;
use std::io;
use std::io::Read;
fn read_username_from_file() -> Result<String, io::Error> {
let mut f = match File::open("hello.txt") {
Ok(file) => file,
Err(e) => Err(e),
};
let mut s = String::new();
match f.read_to_string(&mut s) {
Ok(_) => Ok(s),
Err(e) => Err(e),
}
}
? operator:
use std::fs::File;
use std::io;
use std::io::Read;
fn read_username_from_file() -> Result<String, io::Error> {
let mut s = String::new();
File::open("hello.txt")?.read_to_string(&mut s)?;
Ok(s)
}
? at the end of the File::open call will return the value inside an Ok.? operator will return early out of the whole function and give any Err value to the calling code.? at the end of the read_to_string call.? operator in main function as follows:
use std::error::Error;
use std::fs::File;
fn main() -> Result<(), Box<dyn Error>> {
let f = File::open("hello.txt")?;
Ok(())
}
Box<dyn Error> type is called a trait object.
fn largest<T: std::cmp::PartialOrd + Copy>(list: &[T]) -> T {
let mut largest = list[0];
for &item in list {
if item > largest {
largest = item;
}
}
largest
}
fn main() {
let number_list = vec![34, 50, 25, 100, 65];
let result = largest(&number_list);
println!("The largest number is {}", result);
let char_list = vec!['y', 'm', 'a', 'q'];
let result = largest(&char_list);
println!("The largest char is {}", result);
}
T: std::cmp::PartialOrd + Copy with just T, you would get the following error:
error[E0369]: binary operation `>` cannot be applied to type `T`
--> src/main.rs:5:17
|
5 | if item > largest {
| ---- ^ ------- T
| |
| T
|
help: consider restricting type parameter `T`
|
1 | fn largest<T: std::cmp::PartialOrd>(list: &[T]) -> T {
| ^^^^^^^^^^^^^^^^^^^^^^
> needs the generic type T to implement std::cmp::PartialOrd trait.let mut largest = list[0] requires T to implement Copy trait so that list[0] value gets copied over to largest or else move will be attempted.
list is a reference to an array.i32 and char implement the std::cmp::PartialOrd and Copy trait.struct Point<T> {
x: T,
y: T,
}
impl<T> Point<T> {
fn x(&self) -> &T {
&self.x
}
}
fn main() {
let p = Point { x: 5, y: 10 };
println!("p.x = {}", p.x());
}
fn main() {
let p = Point { x: 5, y: 10.5 };
println!("p.x = {}", p.x());
}
you would get the following error:
error[E0308]: mismatched types
--> src/main.rs:7:38
|
7 | let p = Point { x: 5, y: 4.0 };
| ^^^ expected integer, found floating-point number
Point's x and y fields, use different generic types:
struct Point<T, U> {
x: T,
y: U,
}
This struct would support something like
let p = Point {
x: 5,
y: 4.0
};
enum Option<T> {
Some(T),
None,
}
enum Result<T, E> {
Ok(T),
Err(E),
}
let integer = Some(5);
let float = Some(5.0);
Option<T> instances and identifies two kinds of Option<T>: one is i32 and the other is f64.Option<T> into Option_i32 and Option_f64, thereby replacing the generic definition with the specific ones.Option<T> code:
enum Option_i32 {
Some(i32),
None,
}
enum Option_f64 {
Some(f64),
None,
}
fn main() {
let integer = Option_i32::Some(5);
let float = Option_f64::Some(5.0);
}
pub trait Summary {
fn summarize(&self) -> String;
}
Any type implementing the trait Summary would enable a client to call summarize method on that type's instance.pub struct NewsArticle {
pub headline: String,
pub location: String,
pub author: String,
pub content: String,
}
impl Summary for NewsArticle {
fn summarize(&self) -> String {
format!("{}, by {} ({})", self.headline, self.author, self.location)
}
}
pub struct Tweet {
pub username: String,
pub content: String,
pub reply: bool,
pub retweet: bool,
}
impl Summary for Tweet {
fn summarize(&self) -> String {
format!("{}: {}", self.username, self.content)
}
}
impl <type_name> for <trait_name> { .. } (e.g. impl Summary for Tweet { .. })Summary for Tweet type:
let tweet = Tweet {
username: String::from("megan_sparkle"),
content: String::from(
"I love England!",
),
reply: false,
retweet: false,
};
println!("1 new tweet: {}", tweet.summarize());
Output:
1 new tweet: megan_sparkle: I love England!
Summary trait was defined in another module called aggregate, then you would need to bring the trait into scope via use aggregate::Summary;Summary trait need to be local or Tweet struct need to be local.Display trait on Vec<T> within our crate, because Display and Vec<T> are defined in the standard library and aren’t local to our crate.pub trait Summary {
fn summarize(&self) -> String {
String::from("(Read more...)")
}
}
// to use default implementation, use empty block
impl Summary for NewsArticle {}
fn main () {
let article = NewsArticle {
headline: String::from("Headline"),
location: String::from("USA"),
author: String::from("Ice"),
content: String::from("Some content"),
};
println!("New article available! {}", article.summarize());
}
Summary trait for NewsArticle type without overriding summarize method, the output would be: New article available! (Read more...)
summarize method to return Read more... string.pub trait Summary {
fn summarize_author(&self) -> String;
fn summarize(&self) -> String {
format!("(Read more from {}...)", self.summarize_author())
}
}
impl Summary for Tweet {
fn summarize_author(&self) -> String {
format!("@{}", self.username)
}
}
fn main() {
let tweet = Tweet {
username: String::from("horse_ebooks"),
content: String::from(
"of course, as you probably already know, people",
),
reply: false,
retweet: false,
};
println!("1 new tweet: {}", tweet.summarize());
}
Output:
1 new tweet: (Read more from @horse_ebooks...)
summarize method of Summary trait calls summarize_author method of the same trait.summarize_author method doesn't have an implementation, structs implementing the trait need to provide an implementation for it (as is done in impl Summary for Tweet block).pub fn notify<T: Summary>(item: &T) {
println!("Breaking news! {}", item.summarize());
}
Summary trait can be passed as an argument to the notify function.&impl:
pub fn notify(item: &impl Summary) {
println!("Breaking news! {}", item.summarize());
}
+ syntax:pub fn notify<T: Summary + Display>(item: &T) {
summarize and use {} to format item.where clausefn some_function<T: Display + Clone, U: Clone + Debug>(t: &T, u: &U) -> i32 {
you can make it more clearer by using where clause:
fn some_function<T, U>(t: &T, u: &U) -> i32
where T: Display + Clone,
U: Clone + Debug
{
fn returns_summarizable() -> impl Summary {
Tweet {
username: String::from("horse_ebooks"),
content: String::from(
"of course, as you probably already know, people",
),
reply: false,
retweet: false,
}
}
This is useful in case of Closures and iterators.
impl <Trait> syntax lets you concisely specify that a function returns some type that implements the Iterator trait without needing to write out a very long type.This doesn't work if your function returns multiple types implementing the same trait. So, following code won't compile:
fn returns_summarizable(switch: bool) -> impl Summary {
// both NewsArticle and Tweet implements Summary
if switch {
NewsArticle {
//..
}
} else {
Tweet {
//..
}
}
}
struct Pair<T> {
x: T,
y: T,
}
impl<T: Display + PartialOrd> Pair<T> {
fn cmp_display(&self) {
if self.x >= self.y {
println!("The largest member is x = {}", self.x);
} else {
println!("The largest member is y = {}", self.y);
}
}
}
cmp_display method is implemented only for those generic types which implements both Display and PartialOrd trait.impl<T: Display> ToString for T {
// --snip--
}
ToString trait is implemented for all types which implement Display trait.i32 implements Display, we can do this:
3.to_string()type SomeType;type SomeType = i32;Iterator trait:
pub trait Iterator {
type Item;
fn next(&mut self) -> Option<Self::Item>;
}
Item is a placeholder typenext method’s definition shows that it will return values of type Option<Self::Item>.Iterator trait will specify the concrete type for Item and the next method will return an Option containing a value of that concrete type.Iterator trait implementor:
impl Iterator for Counter {
type Item = u32;
fn next(&mut self) -> Option<Self::Item> {
// --snip--
}
}
<PlaceholderGenericType=ConcreteType> when declaring the generic type.Add trait in std::ops:
trait Add<Rhs=Self> {
type Output;
fn add(self, rhs: Rhs) -> Self::Output;
}
type Output; is associated type, referenced by Self::Output.Rhs=Self syntax is called default type parameters.Rhs generic type parameter (short for “right hand side”) defines the type of the rhs parameter in the add method.Rhs when we implement the Add trait, the type of Rhs will default to Self, which will be the type we’re implementing Add on.
Add for Point struct as
impl Add for Point {
//..
}
where we don't provide the value of Rhs type, then
Rhs will equate to Self, andSelf in this case will equate to Point.+) in particular situations.Point struct defined as:
struct Point {
x: i32,
y: i32,
}
and you want to perform addition of two Point instances using + operator:
assert_eq!(
Point { x: 1, y: 0 } + Point { x: 2, y: 3 },
Point { x: 3, y: 3 }
);
+ operator for Point struct. This can be done by implementing Add trait (discussed above) for Point:
impl Add for Point {
type Output = Point;
fn add(self, other: Point) -> Point {
Point {
x: self.x + other.x,
y: self.y + other.y,
}
}
}
Rhs type's default is Self (equal to Point here) and associated type Output is set as Point in the trait implementation, the above code compiles OK.+ operator when RHS is of different type:
struct Millimeters(u32);
struct Meters(u32);
impl Add<Meters> for Millimeters {
type Output = Millimeters;
fn add(self, other: Meters) -> Millimeters {
Millimeters(self.0 + (other.0 * 1000))
}
}
assert_eq!(
Millimeters(1000) + Meters(1),
Millimeters(2000)
);
Game which implements traits GameStop and Amazon.
GameStop and Amazon traits have price method.Game struct also implements its own price method.trait GameStop {
pub price(&self) -> u32;
}
trait Amazon {
pub price(&self) -> u32;
}
struct Game;
impl Game {
pub price(&self) -> u32 { 100 }
}
impl GameStop for Game {
pub price(&self) -> u32 { 200 }
}
impl Amazon for Game {
pub price(&self) -> u32 { 150 }
}
game.price() method, where game is an instance of Game would return 100.
<TRAIT_NAME>::<METHOD_NAME>(..) syntax:
fn main() {
let game = Game{};
println!("Game price: {}", game.price());
println!("Game price: {}", Game::price(&game));
println!("GameStop price: {}", GameStop::price(&game));
println!("Amazon price: {}", Amazon::price(&game));
}
Output:
Game price: 100
Game price: 100
GameStop price: 200
Amazon price: 150
game.price() can also be written as Game::price(&game).self or its variants (like &self) as a first parameter.<STRUCT_NAME as TRAIT_NAME>::<FUNCTION_NAME>(...) syntax:
trait Premium {
pub price() -> u32;
}
struct Cabbage;
impl Cabbage {
pub price() -> u32 { 20 }
}
impl Premium for Cabbage {
pub price() -> u32 { 40 }
}
fn main() {
println!("Cabbage price: {}", Cabbage::price());
println!("Premium Cabbage price: {}", <Cabbage as Premium>::price());
}
Output:
Cabbage price: 20
Premium Cabbage price: 40
fn main() {
let x = 4;
let equal_to_x = |z| z == x;
let y = 4;
assert!(equal_to_x(y));
}
x is not one of the parameters of equal_to_x, the equal_to_x closure is allowed to use the x variable that’s defined in the same scope that equal_to_x is defined in.fn main() {
let x = 4;
fn equal_to_x(z: i32) -> bool {
z == x
}
let y = 4;
assert!(equal_to_x(y));
}
Error:
error[E0434]: can't capture dynamic environment in a fn item
--> src/main.rs:5:14
|
5 | z == x
| ^
|
= help: use the `|| { ... }` closure form instead
Fn traits as follows:
FnOnce consumes the variables it captures from its enclosing scope, known as the closure’s environment.
Once part of the name represents the fact that the closure can’t take ownership of the same variables more than once, so it can be called only once.FnMut can change the environment because it mutably borrows values.Fn borrows values from the environment immutably.FnOnce because they can all be called at least once.FnMutFn.
let x = 4;
let equal_to_x = |z| z == x;
x immutably, so equal_to_x has Fn trait.move keyword before the parameter list.
move closures may still implement Fn or FnMut, even though they capture variables by move.
move keyword).fn main() {
let x = vec![1, 2, 3];
let equal_to_x = move |z| z == x;
println!("can't use x here: {:?}", x);
let y = vec![1, 2, 3];
assert!(equal_to_x(y));
}
We get the following error:
error[E0382]: borrow of moved value: `x`
--> src/main.rs:6:40
|
2 | let x = vec![1, 2, 3];
| - move occurs because `x` has type `Vec<i32>`, which does not implement the `Copy` trait
3 |
4 | let equal_to_x = move |z| z == x;
| -------- - variable moved due to use in closure
| |
| value moved into closure here
5 |
6 | println!("can't use x here: {:?}", x);
| ^ value borrowed here after move
x value is moved into the closure when the closure is defined, because we added the move keyword.x, and main isn’t allowed to use x anymore in the println! statement.println! will fix this example.41 commits
This repo contains my notes on Rust programming language.
let x = 5;
mut after let:
let mut x = 5;
let x: i32 = 5;
i32 here is data type for 32-bit integer values.const PI:f32 = 3.14;
mut with them.PI:f32).let x = 5;
let x = 2.0 * 5 as f32;
x to create another immutable variable.mut to let statement, as that won't allow you to assign data of different data type. So, the following would cause an error:let mut x = 5;
x = 2.0 * 5 as f32; // error: expected integer, found `f32`
| 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 | size |
| Number literals | Example |
|---|---|
| Decimal | 98_222 |
| Hex | 0xff |
| Octal | 0o77 |
| Binary | 0b1111_0000 |
| Byte (u8 only) | b'A' |
57u8, and _ as a visual separator, such as 1_000i32f32 and f64f64. E.g. in let x = 2.0, x's data type is f64.true or false.bool data type.charchar literals are specified using single quoteslet heart_eyed_cat = '😻';let digit_2 = '\u{0032}';let tup: (i32, f64, u8) = (500, 6.4, 1);let (x, y, z) = tup;
println!("The value of y is: {}", y);
let (x, y, z) = tup; is called destructuring, because it breakes tuple into multiple partslet five_hundred = x.0;let a = [1, 2, 3, 4, 5];let a: [i32; 5] = [1, 2, 3, 4, 5];
i32, 5 means array elements are of type i32 and array length is 5let a = [3; 5];let first = a[0];main function (stored in main.rs file) is the entry point of Rust programs.fn keyword defines new functions
fn main() {
print!("Inside main()");
da_func();
}
fn da_func() {
print!("Inside da_func()");
}
Output:
Inside main()
Inside da_func()
fn da_func(x:i32, y:i32) { ...}. Note that data types for function parameters (like x and y here) is required.fn main() {
fn factorial(num: i32) -> i32 {
if num <= 1 {
1
}
else {
factorial(num - 1) + factorial(num - 2)
}
}
print!("Factorial of 5: {}", factorial(5));
}
factorial function above).= is considered to be an expression.
let x = 6, 6 is an expression.let x = 6 is a statement which do not return any value. So, you can't you something like let y = (let x = 6).let x = some_func();)let x = 5;
let y = {
let x = 3;
x + 1
};
print!("x = {}, y = {}", x, y);
Output:
x = 5, y = 4
Notice the following:
x + 1). Any statement which does not end with ; is considered as return expression.x inside the block and assigned it 3. This value will shadow the value 5 throughout the block. Once the block is over, x's value becomes 5.fn func() -> i32 {...}fn five() -> i32 {
5
}
This functions returns the value 5.fn type.fn type is called a function pointer.fn add_one(x: i32) -> i32 {
x + 1
}
// `do_twice` function accepts a function pointer
// `fn(i32) -> i32` as first parameter.
// This parameter will accept any function which has
// `i32` as a single parameter and the return type.
fn do_twice(f: fn(i32) -> i32, arg: i32) -> i32 {
f(arg) + f(arg)
}
fn main() {
// `add_one` function is passed as argument to `do_twice`
let answer = do_twice(add_one, 5);
println!("The answer is: {}", answer);
}
Output:
The answer is: 12
//
// flag for whether to perform search op or not
let search_flag = false;
let search_flag = false; // comment
if expression can also return values:
let x = if number > 8 {
"greater than 8"
}
else if some_val == 8 {
"number is 8"
}
else {
"less than 8"
}
let mut counter = 0;
let x = loop {
counter += 1;
if counter == 10 {
break 3
}
}
Here, once counter reaches 10, loop breaks and returns a value of 3 and gets stored in variable x.break expression, loop will go on forever.for .. in statement:
let a = [1, 2, 3, 4, 5];
for elem in a.iter() {
print!("elem = {}", elem);
}
for a in (1..4).rev() {
print!("{} ",a);
}
Output:
3 2 1
Here, 1..4 creates an array with values between 1 and 3 (excluding 4), and calling rev function reverses the order. Then use for loop to iterate through the array.
1..4 are end-exclusive. Here, 4 is ignored, and the for loop runs on values 1, 2 and 3.{..} inside of which the variable is defined.
{ // s is not valid here, it’s not yet declared
let s = "hello"; // s is valid from this point forward
// do stuff with s
} // this scope is now over, and s is no longer valid
str and are immutable.String are mutable.str to String via: let string = String::from("hello");string.push_str(" world");str):
str types.String::from requests the required memory{
let s = String::from("hello"); // s is valid from this point forward
// do stuff with s
} // this scope is now over, and s is no
// longer valid
drop function.drop function is like Resource Acquisition Is Initialization (RAII) in C++, which is a pattern of deallocating resources at the end of an item’s lifetimelet x = 5;
let y = x;
let s1 = String::from("hello");
let s2 = s1;
s1 to s2. This would make s1 invalid. If you try to access s1 later:let s1 = String::from("hello");
let s2 = s1;
println!("{}, world!", s1);
you would see the following error:
error[E0382]: borrow of moved value: `s1`
--> src/main.rs:5:28
|
2 | let s1 = String::from("hello");
| -- move occurs because `s1` has type `String`, which does not implement the `Copy` trait
3 | let s2 = s1;
| -- value moved here
4 |
5 | println!("{}, world!", s1);
| ^^ value borrowed here after move
String does not implement Copy trait (trait is like interface in Java and C++).
Copy, Rust would copy the data and you would see two different copies of the same data.s1 later in the above code.String implements Drop trait, s1 gets automatically invalidated when it goes out of scope.Copy, Clone and Drop traitsDrop trait, then the resource (data stored in a variable) is dropped or deleted once the associated variable goes out of scope.Copy trait,then the resource gets copied to the other variable.
Copy for a type:#[derive(Copy)]
struct Person {
//...
}
then all the fields of the type must implement the Copy trait or the code won't compile.
Copy trait acts as a marker for compiler that says: "you can duplicate myself with a simple bytes copy".Copy trait:
u32boolf32charCopy.
Copy, but (i32, String) do not.Copy trait to a type, then you can't add Drop trait, and vice-versa.
Drop trait could get dropped in the same scope. This won't allow you to use the resource in later part of the scope, even though you might expect otherwise.fn drop_copy_type<T>(T x)
where
T: Copy + Drop,
{
// The inner file descriptor is closed there:
std::mem::drop(x);
}
fn main() {
let mut file = File::open("foo.txt").unwrap();
drop_copy_type(file);
let mut contents = String::new();
// Oops, this is unsafe!
// We try to read an already closed file descriptor:
file.read_to_string(&mut contents).unwrap();
}
Clone trait, then you can call explicitly s1.clone() method to clone data stored in s1 variable. In this case, you can also implement Drop trait to the same type.
Clone and Drop can co-exist.fn main() {
let s = String::from("hello"); // s comes into scope
takes_ownership(s); // s's value moves into the function...
// ... and so is no longer valid here
let x = 5; // x comes into scope
makes_copy(x); // x would move into the function,
// but i32 is Copy, so it's okay to still use x afterward
} // Here, x goes out of scope, then s.
// But because s's value was moved, nothing special happens.
fn takes_ownership(some_string: String) {
// some_string comes into scope
println!("{}", some_string);
} // Here, some_string goes out of scope and `drop` is called.
// The backing memory is freed.
fn makes_copy(some_integer: i32) {
// some_integer comes into scope
println!("{}", some_integer);
} // Here, some_integer goes out of scope.
// Nothing special happens.
fn main() {
let s1 = gives_ownership(); // gives_ownership moves its return value into s1
let s2 = String::from("hello"); // s2 comes into scope
let s3 = takes_and_gives_back(s2);
// s2 is moved into takes_and_gives_back, which also
// moves its return value into s3
} // Here, s3 goes out of scope and is dropped.
// s2 goes out of scope but was moved, so nothing happens.
// s1 goes out of scope and is dropped.
fn gives_ownership() -> String {
// gives_ownership will move its return value into the
// function that calls it
// some_string comes into scope
let some_string = String::from("hello");
some_string // some_string is returned and
// moves out to the calling function
}
// takes_and_gives_back will take a String and return one
fn takes_and_gives_back(a_string: String) -> String {
// a_string comes into scope
a_string // a_string is returned and moves out to the
// calling function
}
drop unless the data has been moved to be owned by another variable.fn main() {
let str = String::from("hello");
let (len, str) = length(str);
print!("len of {} is {}", str, len);
}
fn length(s: String) -> (i32, String) {
(s.len(), s)
}
reference of the variable to the function:
fn main() {
let s1 = String::from("hello");
let len = calculate_length(&s1);
println!("The length of '{}' is {}.", s1, len);
}
fn calculate_length(s: &String) -> usize {
s.len()
}
&s1 syntax lets us create a reference that refers to the value of s1 but does not own it.
& to indicate that the type of the parameter s is a reference.
// s is a reference to a String
fn calculate_length(s: &String) -> usize {
s.len()
} // Here, s goes out of scope. But because it does not have ownership of what it refers to, nothing happens.
fn change(some_string: &String) {
some_string.push_str(", world");
}
Error:
error[E0596]: cannot borrow `*some_string` as mutable, as it is behind a `&` reference
|
7 | fn change(some_string: &String) {
| ------- help: consider changing this to be a mutable reference: `&mut String`
8 | some_string.push_str(", world");
| ^^^^^^^^^^^ `some_string` is a `&` reference, so the data it refers to cannot be borrowed as mutable
mut after &:
fn change(some_string: &mut String) {
some_string.push_str(", world");
}
let mut s = String::from("hello");
let r1 = &mut s;
let r2 = &mut s;
println!("{}, {}", r1, r2);
Error:
error[E0499]: cannot borrow `s` as mutable more than once at a time
|
4 | let r1 = &mut s;
| ------ first mutable borrow occurs here
5 | let r2 = &mut s;
| ^^^^^^ second mutable borrow occurs here
6 |
7 | println!("{}, {}", r1, r2);
| -- first borrow later used here
let mut s = String::from("hello");
{
let r1 = &mut s;
} // r1 goes out of scope here, so we can make a new reference with no problems.
let r2 = &mut s;
let mut s = String::from("hello");
let r1 = &s; // no problem
let r2 = &s; // no problem
let r3 = &mut s; // BIG PROBLEM
// immutable reference r1 and r2 used after creating
// mutable reference r3
println!("{}, {}, and {}", r1, r2, r3);
Error:
error[E0502]: cannot borrow `s` as mutable because it is also borrowed as immutable
--> src/main.rs:6:14
|
4 | let r1 = &s; // no problem
| -- immutable borrow occurs here
5 | let r2 = &s; // no problem
6 | let r3 = &mut s; // BIG PROBLEM
| ^^^^^^ mutable borrow occurs here
7 |
8 | println!("{}, {}, and {}", r1, r2, r3);
| -- immutable borrow later used here
let mut s = String::from("hello");
let r1 = &s; // no problem
let r2 = &s; // no problem
println!("{} and {}", r1, r2);
// r1 and r2 are no longer used after this point
let r3 = &mut s; // no problem
println!("{}", r3);
fn main() {
let reference_to_nothing = dangle();
}
fn dangle() -> &String {
let s = String::from("hello");
&s
}
Error thrown by compiler:
|
5 | fn dangle() -> &String {
| ^ expected named lifetime parameter
|
= help: this function's return type contains a borrowed value, but there is no value for it to be borrowed from
help: consider using the `'static` lifetime
this function's return type contains a borrowed value, but there is no value for it to be borrowed fromdangle function with comments to explain the above error message:fn dangle() -> &String { // dangle returns a reference to a String
let s = String::from("hello"); // s is a new String
&s // we return a reference to the String, s
} // Here, s goes out of scope, and is dropped.
// Its memory goes away. Danger!
String is dropped at the end of the function, &s would create a reference to invalid memory location.
let s = String::from("hello world");
let hello = &s[0..5];
let world = &s[6..11];
[starting_index..ending_index]:
starting_index is inclusiveending_index is exclusive.&s[0..5] above will result in "hello" (index 0 to index 4, 5 is excluded)let slice = &s[..2]; // 0 to 2
let slice = &s[2..]; // 2 to len - 1
let slice = &s[..]; // 0 to len - 1
&s[2..4] slice is stored as reference to index 2 of String s and number of elements (2);fn first_word(s: &String) -> &str {
let bytes = s.as_bytes();
// iter() returns each element.
// enumerate() transforms each element into a tuple of
// (index, reference of element)
for (i, &item) in bytes.iter().enumerate() {
// b' ' converts char ' ' into its byte equivalent
if item == b' ' {
return &s[0..i];
}
}
&s[..]
}
fn main() {
let mut s = String::from("hello world");
let word = first_word(&s);
println!("the first word is: {}", word);
}
main function, if you were to clear s you would run into error.
fn main() {
let mut s = String::from("hello world");
let word = first_word(&s);
s.clear(); // error!
println!("the first word is: {}", word);
}
Error:
error[E0502]: cannot borrow `s` as mutable because it is also borrowed as immutable
--> src/main.rs:18:5
|
16 | let word = first_word(&s);
| -- immutable borrow occurs here
17 |
18 | s.clear(); // error!
| ^^^^^^^^^ mutable borrow occurs here
19 |
20 | println!("the first word is: {}", word);
| ---- immutable borrow later used here
word contains immutable reference to the string and while its still in use, we call s.clear() which does mutable borrow.first_word returned the end index of the first word, then word would still contain the index even after s becomes empty, and that would create another bug (since we are trying to get 0 to non-zero index string out of an empty string)first_word's signature could be written as fn first_word(s: &str) -> &str. This way, first_word function can accept both &String and &str.s as arguments to first_word:
fn main() {
// String type
let my_string = String::from("hello world");
// first_word works on slices of `String`s
let word = first_word(&my_string[..]);
// str type
let my_string_literal = "hello world";
// first_word works on slices of string literals
let word = first_word(&my_string_literal[..]);
// Because string literals *are* string slices already,
// this works too, without the slice syntax!
let word = first_word(my_string_literal);
}
let a = [1, 2, 3, 4, 5];
let slice = &a[1..3];
assert_eq!(slice, &[2, 3]);
slice has the type &[i32].struct User {
username: String,
email: String,
sign_in_count: u64,
active: bool,
}
active property? Yeah, that's allowed in Rust. In Java enums, that extra comma would have thrown a compilation error.User:
let user1 = User {
email: String::from("someone@example.com"),
username: String::from("someusername123"),
active: true,
sign_in_count: 1,
};
User instance.mut:
let mut user1 = User {
email: String::from("someone@example.com"),
username: String::from("someusername123"),
active: true,
sign_in_count: 1,
};
user1.email = String::from("anotheremail@example.com");
new that would return instance of the struct.struct Rectangle {
width: u32,
height: u32,
}
fn main() {
let rect1 = Rectangle {
width: 30,
height: 50,
};
println!(
"The area of the rectangle is {} square pixels.",
area(&rect1)
);
}
fn area(rectangle: &Rectangle) -> u32 {
rectangle.width * rectangle.height
}
fn build_user(email: String, username: String) -> User {
User {
email,
username,
active: true,
sign_in_count: 1,
}
}
let user2 = User {
email: String::from("another@example.com"),
username: String::from("anotherusername567"),
..user1
};
user2 would be used from user1.struct Color(i32, i32, i32);
struct Point(i32, i32, i32);
let black = Color(0, 0, 0);
let origin = Point(0, 0, 0);
struct Dimension(u32, u32);
fn main() {
let rect1 = Dimension(30, 50);
println!(
"The area of the rectangle is {} square pixels.",
area(rect1)
);
}
fn area(dimensions: Dimension) -> u32 {
dimensions.0 * dimensions.1
}
struct Rectangle {
width: u32,
height: u32,
}
fn main() {
let rect1 = Rectangle {
width: 30,
height: 50,
};
println!("rect1 is {}", rect1);
}
the program throws an error:
error[E0277]: `Rectangle` doesn't implement `std::fmt::Display`
Display trait because they can be shown in just one way. Structs do not.Debug on a struct as follows:
#[derive(Debug)]
struct Rectangle {
width: u32,
height: u32,
}
fn main() {
let rect1 = Rectangle {
width: 30,
height: 50,
};
println!("rect1 is {:?}", rect1);
}
Output:
rect1 is Rectangle { width: 30, height: 50 }
{:#?} instead in println! macro.
rect1 is Rectangle {
width: 30,
height: 50,
}
self (take ownership) or&self (immutable borrow) or&mut self (mutable borrow).#[derive(Debug)]
struct Rectangle {
width: u32,
height: u32,
}
impl Rectangle {
fn area(&self) -> u32 {
self.width * self.height
}
}
fn main() {
let rect1 = Rectangle {
width: 30,
height: 50,
};
println!(
"The area of the rectangle is {} square pixels.",
rect1.area()
);
}
impl (implementation) blocks for a given struct.impl Rectangle {
fn can_hold(&self, other: &Rectangle) -> bool {
self.width > other.width && self.height > other.height
}
}
fn main() {
let rect1 = Rectangle {
width: 30,
height: 50,
};
let rect2 = Rectangle {
width: 10,
height: 40,
};
println!("Can rect1 hold rect2? {}", rect1.can_hold(&rect2));
}
impl blocks:
impl Rectangle {
fn area(&self) -> u32 {
self.width * self.height
}
}
impl Rectangle {
fn can_hold(&self, other: &Rectangle) -> bool {
self.width > other.width && self.height > other.height
}
}
object.something(), Rust automatically adds in &, &mut, or * so object matches the signature of the method.p1.distance(&p2);
(&p1).distance(&p2);
self.&self), mutating (&mut self), or consuming (self).self or its variants as first parameters.impl Rectangle {
fn square(size: u32) -> Rectangle {
Rectangle {
width: size,
height: size,
}
}
}
fn main() {
let square = Rectangle::square(3);
}
:: syntax with the struct name.type c = a | b in languages like OCaml allows us to identify instance of a or b as instance of c.enum Message {
Quit,
Move { x: i32, y: i32 },
Write(String),
ChangeColor(i32, i32, i32),
}
impl Message {
fn call(&self) {
//..
}
}
fn main() {
let m = Message::Write(String::from("hello"));
m.call();
}
Message is a sum type of the following types: Quit, Move, Write, ChangeColor.
impl blocks (same as in structs):: operator (since enum types are like associated functions in structs; enum Message serves as namespace for these four enum types).Option EnumOption enum:
enum Option<T> {
Some(T),
None,
}
Option enum is available in Rust programs by default (included in the prelude), so don't have to import it.Option enum:
let some_number = Some(5);
let some_string = Some("a string");
let absent_number: Option<i32> = None;
None to a variable, you need to specify the variable's data type. Rust couldn't have inferred the type T in Option enum otherwise.Option is better than null:
let x: i8 = 5;
let y: Option<i8> = Some(5);
let sum = x + y;
x and y are of two different types (x is i8 and y is Option<i8>)public static void main(String[] args) {
Integer x = 0;
Integer y = null;
Integer z = x + y;
}
match control flowmatch operator.
enum Coin {
Penny,
Nickel,
Dime,
Quarter,
}
fn value_in_cents(coin: Coin) -> u8 {
match coin {
Coin::Penny => 1,
Coin::Nickel => {
println!("coin type is Nickel");
5
},
Coin::Dime => 10,
Coin::Quarter => 25,
}
}
Penny, value_in_cents will return 1.match block like Coin::Penny => 1 is called a match arm.
Coin::Penny is called patternCoin::Nickel above.Option matching example:
fn plus_one(x: Option<i32>) -> Option<i32> {
match x {
None => None,
Some(i) => Some(i + 1),
}
}
let five = Some(5);
let six = plus_one(five);
let none = plus_one(None);
fn plus_one(x: Option<i32>) -> Option<i32> {
match x {
Some(i) => Some(i + 1),
}
}
Error
error[E0004]: non-exhaustive patterns: `None` not covered
--> src/main.rs:3:15
|
3 | match x {
| ^ pattern `None` not covered
|
= help: ensure that all possible cases are being handled, possibly by adding wildcards or more match arms
= note: the matched value is of type `Option<i32>`
_:
let num_to_str = match input_num {
1 => "one",
3 => "three",
5 => "five",
7 => "seven",
_ => "default",
};
input_num values not equal to either 1, 3, 5, or 7, the match expression would return "default" and gets stored in num_to_str variable.let x = Some(5);
let y = 10;
match x {
Some(50) => println!("Got 50"),
Some(y) => println!("Matched, y = {:?}", y),
_ => println!("Default case, x = {:?}", x),
}
println!("at the end: x = {:?}, y = {:?}", x, y);
Output:
Matched, y = 5
at the end: x = Some(5), y = 10
y gets shadowed inside match arm Some(y) and y value becomes 5.x value was None, the output would have been:
Default case, x = None
at the end: x = None, y = 10
matchlet x = 1;
match x {
1 | 2 => println!("one or two"),
3 => println!("three"),
_ => println!("anything"),
}
Output:
one or two
..=let x = 5;
match x {
1..=5 => println!("one through five"),
_ => println!("something else"),
}
If x is 1, 2, 3, 4, or 5, the first arm will match.char range:
let x = 'c';
match x {
'a'..='j' => println!("early ASCII letter"),
'k'..='z' => println!("late ASCII letter"),
_ => println!("something else"),
}
Output:
early ASCII letter
struct Point {
x: i32,
y: i32,
}
fn main() {
let p = Point { x: 0, y: 7 };
let Point { x, y } = p;
assert_eq!(0, x);
assert_eq!(7, y);
}
fn main() {
let p = Point { x: 0, y: 7 };
let Point { x: a, y: b } = p;
assert_eq!(0, a);
assert_eq!(7, b);
}
Here, two new variables a and b are created that match the values x and y.match:
fn main() {
let p = Point { x: 0, y: 7 };
match p {
Point { x, y: 0 } => println!("On the x axis at {}", x),
Point { x: 0, y } => println!("On the y axis at {}", y),
Point { x, y } => println!("On neither axis: ({}, {})", x, y),
}
}
Output:
On the y axis at 7
enum Message {
Quit,
Move { x: i32, y: i32 },
Write(String),
ChangeColor(i32, i32, i32),
}
fn main() {
let msg = Message::ChangeColor(0, 160, 255);
match msg {
Message::Quit => {
println!("The Quit variant has no data to destructure.")
}
Message::Move { x, y } => {
println!(
"Move in the x direction {} and in the y direction {}",
x, y
);
}
Message::Write(text) => println!("Text message: {}", text),
Message::ChangeColor(r, g, b) => println!(
"Change the color to red {}, green {}, and blue {}",
r, g, b
),
}
}
Output:
Change the color to red 0, green 160, and blue 255
let ((feet, inches), Point { x, y }) = ((3, 10), Point { x: 3, y: -10 });
This declares and assigns the following variables: feet = 3, inches = 10, x = 3, y = -10._fn foo(_: i32, y: i32) {
println!("This code only uses the y parameter: {}", y);
}
fn main() {
foo(3, 4);
}
Output:
This code only uses the y parameter: 4
_let numbers = (2, 4, 8, 16, 32);
match numbers {
(first, _, third, _, fifth) => {
println!("Some numbers: {}, {}, {}", first, third, fifth)
}
}
Output:
Some numbers: 2, 8, 32
_fn main() {
let _x = 5;
let y = 10;
}
y, but we don't get any warning for _x._x and _; let _x = value still binds value to the variable _x. For example:
let s = Some(String::from("Hello!"));
if let Some(_s) = s {
println!("found a string");
}
println!("{:?}", s);
would result in compile-time error as value String::from("Hello!") would move to _s, thereby making s variable after if let expression invalid.
Some(_) instead, the code will compile just fine...struct Point {
x: i32,
y: i32,
z: i32,
}
let origin = Point { x: 3, y: 0, z: 0 };
match origin {
Point { x, .. } => println!("x is {}", x),
}
Output:
x is 3
fn main() {
let numbers = (2, 4, 8, 16, 32);
match numbers {
(first, .., last) => {
println!("first: {}, last: {}", first, last);
}
}
}
Output:
first: 2, last: 32
.. must be unambiguous. The following code will result in compile error:
fn main() {
let numbers = (2, 4, 8, 16, 32);
match numbers {
(.., second, ..) => {
println!("Some numbers: {}", second)
},
}
}
Error:
error: `..` can only be used once per tuple pattern
--> src/main.rs:5:22
|
5 | (.., second, ..) => {
| -- ^^ can only be used once per tuple pattern
| |
| previously used here
if condition specified after the pattern in a match arm that must also match, along with the pattern matching, for that arm to be chosen.let num = Some(4);
match num {
Some(x) if x < 5 => println!("less than five: {}", x),
Some(x) => println!("{}", x),
None => (),
}
Output:
less than five: 4
fn main() {
let x = Some(5);
let y = 5;
match x {
Some(50) => println!("Got 50"),
Some(n) if n == y => println!("Matched, n = {}", n),
_ => println!("Default case, x = {:?}", x),
}
println!("at the end: x = {:?}, y = {}", x, y);
}
Output:
Matched, n = 5
at the end: x = Some(5), y = 5
Some(n) if n == y match arm doesn't introduce a new variable y inside match scope, and thus can use outer y as match guard.let x = 4;
let y = false;
match x {
4 | 5 | 6 if y => println!("yes"),
_ => println!("no"),
}
4 | 5 | 6, followed by the match guard if y.@ bindings@ bindigs:
enum Message {
Hello { id: i32 },
}
let msg = Message::Hello { id: 5 };
match msg {
Message::Hello {
id: id_variable @ 3..=7,
} => println!("Found an id in range: {}", id_variable),
Message::Hello { id: 10..=12 } => {
println!("Found an id in another range")
}
Message::Hello { id } => println!("Found some other id: {}", id),
}
5 inside id variable when it checks whether the value of id field after destructuring msg falls in 3..=7 range.@ binding: id: id_variable @ 3..=7id_variable @ tells Rust to create a new variable named id_variable storing the matched value (here 5).#[derive(Debug)]
enum UsState {
Alabama,
Alaska,
// --snip--
}
enum Coin {
Penny,
Nickel,
Dime,
Quarter(UsState),
}
fn value_in_cents(coin: Coin) -> u8 {
match coin {
Coin::Penny => 1,
Coin::Nickel => 5,
Coin::Dime => 10,
Coin::Quarter(state) => {
println!("State quarter from {:?}!", state);
25
}
}
}
value_in_cents(Coin::Quarter(UsState::Alaska)), coin would be Coin::Quarter(UsState::Alaska).Coin::Quarter(state).state will be the value UsState::Alaska.if let syntaxif let syntax.let num_to_str = match input_num {
1 => "one",
3 => "three",
5 => "five",
7 => "seven",
_ => "default",
};
7 and ignore the rest, you could use if let:
let num_to_str = if let 7 = input_num {
"seven"
}
else {
"default"
}
fn func(favorite_color: Option<&str>, is_tuesday: bool,
age: Result<u8, _>) {
if let Some(color) = favorite_color {
println!("Using your favorite color, {}, as the background", color);
} else if let Ok(age) = age {
if age > 30 {
println!("Using purple as the background color");
} else {
println!("Using orange as the background color");
}
} else {
println!("Using blue as the background color");
}
}
favorite_color is Some("red"), then the output would be Using your favorite color, red, as the background.favorite_color is None and age is Ok(34), then the output would be Using orange as the background colorUsing blue as the background color.while let expressionlet mut stack = Vec::new();
stack.push(1);
stack.push(2);
stack.push(3);
while let Some(top) = stack.pop() {
println!("{}", top);
}
Output:
3
2
1
stack.pop() returns Some(val).stack.pop() returns None.for looplet v = vec!['a', 'b', 'c'];
for (index, value) in v.iter().enumerate() {
println!("{} is at index {}", value, index);
}
Output:
a is at index 0
b is at index 1
c is at index 2
let statementslet (x, y, z) = (1, 2, 3);
This would assign x = 1, y = 2, z = 3.let (x, y) = (1, 2, 3);
Output:
error[E0308]: mismatched types
fn print_coordinates(&(x, y): &(i32, i32)) {
println!("Current location: ({}, {})", x, y);
}
fn main() {
let point = (3, 5);
print_coordinates(&point);
}
Output:
Current location: (3, 5)
Patterns come in two forms: refutable and irrefutable.
Patterns that will match for any possible value passed are irrefutable.
x in statement let x = 5, because x matches anything and therefore cannot fail to match.Patterns that can fail to match for some possible value are refutable.
Some(x) in if let Some(x) = a_value expression, because if the value of a_value is None, then the Some(x) pattern won't match.Function parameters, let statements, and for loops can only accept irrefutable patterns, because the program cannot do anything meaningful when values don’t match.
let Some(x) = some_option_value;
as Some(x) = .. is a refutable pattern and let requires irrefutable patterns. So, let requires that None = pattern to also be covered.match with let for above:
let x = match {
Some(val) => val,
None => DEFAULT_VALUE
};
The if let and while let expressions accept refutable and irrefutable patterns, but the compiler warns against irrefutable patterns.
if let Some(x) = some_option_value {
println!("{}", x);
}
if let x = 5 {
println!("{}", x);
};
but compiler would give you a warning:
warning: irrefutable `if let` pattern
because using irrefutable pattern with if let is useless.Pattern refutability explains why match arms must use refutable patterns, except for the last arm, which should match any remaining values with an irrefutable pattern.
cargo new command:
$ cargo new my-project
Created binary (application) `my-project` package
$ ls my-project
Cargo.toml
src
$ ls my-project/src
main.rs
Cargo.toml is created, giving us a package.src/main.rs is mentioned in the contents of Cargo.toml file.
src/main.rs is the crate root of a binary crate with the same name as the package.src/lib.rs, that becomes the crate root of a library crate with the same name as the package.rustc to build the library or binary.src/main.rs and src/lib.rs, it has two crates: a library and a binary, both with the same name as the package.src/bin directory: each file will be a separate binary crate.struct Rng and then we import a crate rand which also has a struct named RngRng that we defined.Rng trait from the rand crate as rand::Rng.restaurant by running cargo new --lib restaurant, and then add the following code to src/lib.rs file:
mod front_of_house {
mod hosting {
fn add_to_waitlist() {}
fn seat_at_table() {}
}
mod serving {
fn take_order() {}
fn serve_order() {}
fn take_payment() {}
}
}
mod keyword followed by the name of the module (e.g. mod front_of_house)snake_case)src/main.rs and src/lib.rs are called crate roots because the contents of either of these two files form a module named crate at the root of the crate’s module structure, known as the module tree.
restaurant library:
crate
└── front_of_house
├── hosting
│ ├── add_to_waitlist
│ └── seat_at_table
└── serving
├── take_order
├── serve_order
└── take_payment
front_of_house is parent of hosting, and hosting is child of front_of_house.hosting and serving are siblings.crate.self, super, or an identifier in the current module.::).mod front_of_house {
mod hosting {
fn add_to_waitlist() {}
}
}
pub fn eat_at_restaurant() {
// Absolute path
crate::front_of_house::hosting::add_to_waitlist();
// Relative path
front_of_house::hosting::add_to_waitlist();
}
error[E0603]: module `hosting` is private
--> src/lib.rs:9:28
|
9 | crate::front_of_house::hosting::add_to_waitlist();
| ^^^^^^^ private module
|
note: the module `hosting` is defined here
--> src/lib.rs:2:5
|
2 | mod hosting {
| ^^^^^^^^^^^
pub keyword. Example:
mod front_of_house {
pub mod hosting {
fn add_to_waitlist() {}
}
}
pub fn eat_at_restaurant() {
crate::front_of_house::hosting::add_to_waitlist();
}
add_to_waitlist is not declared as pub.
fn add_to_waitlist() to pub fn add_to_waitlist() will compile the code.super keyword
cd .. in bash to go up to parent directory.fn serve_order() {}
mod back_of_house {
fn fix_incorrect_order() {
cook_order();
super::serve_order();
}
fn cook_order() {}
}
super will take compiler to search for serve_order function in parent module of back_of_house. In this case, it does find the function and hence code compiles.mod company {
pub struct Employee {
name: String
}
impl Employee {
pub fn new(name: String) -> Employee {
Employee {
name
}
}
pub fn get_name(&self) -> &String {
&self.name
}
}
}
fn main() {
let employee = company::Employee::new(String::from("Jethalal"));
// cannot create instance of Employee here as name is private. So below code will result in error
/*
let employee = company::Employee {
name: String::from("Jethalal")
};
*/
// trying to set employee name would throw error, as name is not visible
// employee.name = "Ramesh";
println!("name: {}", employee.get_name());
}
mod back_of_house {
pub enum Appetizer {
Soup,
Salad,
}
}
pub fn eat_at_restaurant() {
let order1 = back_of_house::Appetizer::Soup;
let order2 = back_of_house::Appetizer::Salad;
}
use keyworduse keyword. Example:
mod front_of_house {
pub mod hosting {
pub fn add_to_waitlist() {}
}
}
use crate::front_of_house::hosting;
pub fn eat_at_restaurant() {
hosting::add_to_waitlist();
hosting::add_to_waitlist();
hosting::add_to_waitlist();
}
use crate::front_of_house::hosting in the crate root, hosting is now a valid name in that scope, just as though the hosting module had been defined in the crate root.use also check privacy, like any other paths.mod front_of_house {
pub mod hosting {
pub fn add_to_waitlist() {}
}
}
use self::front_of_house::hosting;
pub fn eat_at_restaurant() {
hosting::add_to_waitlist();
hosting::add_to_waitlist();
hosting::add_to_waitlist();
}
use self::front_of_house::hosting::add_to_waitlist and directly use add_to_waitlist function:
use crate::front_of_house::hosting::add_to_waitlist;
pub fn eat_at_restaurant() {
add_to_waitlist();
}
add_to_wishlist function is located (same module or different).
use self::front_of_house::hosting) and then call the function (hosting::add_to_waitlist()).use std::collections::HashMap;
fn main() {
let mut map = HashMap::new();
map.insert(1, 2);
}
use std::fmt;
use std::io;
fn function1() -> fmt::Result {
// --snip--
}
fn function2() -> io::Result<()> {
// --snip--
}
Result struct from two different modules by bringing parent modules std::fmt and std::io into scope.Result via complete path like use std::fmt::Result, then Rust wouldn't know which Result struct to use.as keyword:
use std::fmt::Result;
use std::io::Result as IoResult;
fn function1() -> Result {
// --snip--
}
fn function2() -> IoResult<()> {
// --snip--
}
Cargo.toml:
[dependencies]
algorithms = "0.1.1"
you tell Cargo to download the algorithm package and its dependencies and make this external package available to our project.use the algorithm crate in any Rust project file as follows:
use algorithms;
fn main() {
let factorial_of_5 = algorithms::factorial(5);
}
std) is also a crate that’s external to our package.
std.use to bring items from there into our package’s scope:
use std::collections::HashMap;
std, the name of the standard library crate.// --snip--
use std::cmp::Ordering;
use std::io;
// --snip--
as
// --snip--
use std::{cmp::Ordering, io};
// --snip--
use statements share a subpath like below:
use std::io;
use std::io::Write;
we can write it as:
use std::io::{self, Write};
*, the glob operator:
use std::collections::*;
std::collections into the current scope.tests module.mod front_of_house {
pub mod hosting {
pub fn add_to_waitlist() {}
}
}
use self::front_of_house::hosting;
pub fn eat_at_restaurant() {
hosting::add_to_waitlist();
hosting::add_to_waitlist();
hosting::add_to_waitlist();
}
and you want to split it into different files for each module.<module-name>.rs or create a directory called <module-name> and then store it in <module-name>/mod.rs.src/lib.rs:
mod front_of_house;
use crate::front_of_house::hosting;
pub fn eat_at_restaurant() {
hosting::add_to_waitlist();
hosting::add_to_waitlist();
hosting::add_to_waitlist();
}
mod followed by <module-name> and a semicolon ; (e.g. mod front_of_house;) tells Rust to load that module file into scope.use statement with the refactoring of code into multiple files.src/front_of_house/mod.rs
pub mod hosting;
src/front_of_house/hosting.rs
pub fn add_to_waitlist() {}
Vec<T>, allows you to store more than one value in a single data structure that puts all the values next to each other in memory.// immutable vector of integers
let v: Vec<i32> = Vec::new();
// immutable vector of integers 1,2 and 3
let v = vec![1,2,3];
let mut v = Vec::new();
v.push(5);
v.push(6);
v.push(7);
v.push(8);
mut while declaring the vector.{
let v = vec![1, 2, 3, 4];
// do stuff with v
} // <- v goes out of scope and is freed here
let v = vec![1, 2, 3, 4, 5];
let third: &i32 = &v[2];
println!("The third element is {}", third);
match v.get(2) {
Some(third) => println!("The third element is {}", third),
None => println!("There is no third element."),
}
&v[100] in above example would result in panic and thus crashing the program.v.get(100) would return None and need to handle it accordingly.let mut v = vec![1, 2, 3, 4, 5];
let first = &v[0];
v.push(6);
println!("The first element is: {}", first);
Error:
error[E0502]: cannot borrow `v` as mutable because it is also borrowed as immutable
--> src/main.rs:6:5
|
4 | let first = &v[0];
| - immutable borrow occurs here
5 |
6 | v.push(6);
| ^^^^^^^^^ mutable borrow occurs here
7 |
8 | println!("The first element is: {}", first);
| ----- immutable borrow later used here
push method can result in copying elements to new memory space, and thus first will point to invalid memory location.let v = vec![100, 32, 57];
for i in &v {
println!("{}", i);
}
let mut v = vec![100, 32, 57];
for i in &mut v {
*i += 50;
}
*) to get to the value in i before we can use the += operator.enum SpreadsheetCell {
Int(i32),
Float(f64),
Text(String),
}
let row = vec![
SpreadsheetCell::Int(3),
SpreadsheetCell::Text(String::from("blue")),
SpreadsheetCell::Float(10.12),
];
str that is usually seen in its borrowed form &str.String type, which is provided by Rust’s standard library rather than coded into the core language, is a growable, mutable, owned, UTF-8 encoded string type.
String type and &str string slice type.String:
let mut s = String::new();
str literals to String:
let s = "initial contents".to_string();
// or
let s = String::from("initial contents");
let hello = String::from("नमस्ते");
let mut s1 = String::from("foo");
let s2 = "bar";
s1.push_str(s2);
println!("s2 is {}", s2);
push_str function takes a string slice, the calling function doesn't lose ownership of s2 and can thus print its value.push function:
let mut s = String::from("lo");
s.push('l');
s will contain lol+ operatorlet s1 = String::from("Hello, ");
let s2 = String::from("world!");
let s3 = s1 + &s2; // note s1 has been moved here and can no longer be used
+ operator uses the add method:
fn add(self, s: &str) -> String {
s3, s2 remains valid as s2 was borrowed. However, s1 becomes invalid since ownership was moved to add function.&s2 is of type &String, but add accepts &str as second parameter. This works because Rust uses a deref coercion which turns &s2 into &s2[..].let s3 = s1 + &s2; isn't making new strings, but rather:
format! macrolet s1 = String::from("tic");
let s2 = String::from("tac");
let s3 = String::from("toe");
let s = format!("{}-{}-{}", s1, s2, s3);
macro works in the same way asprintln!`, but instead of printing the output to the screen, it returns a String with the contents.format! is much easier to read and doesn’t take ownership of any of its parameters.let s1 = String::from("hello");
let h = s1[0];
Error:
error[E0277]: the type `String` cannot be indexed by `{integer}`
--> src/main.rs:3:13
|
3 | let h = s1[0];
| ^^^^^ `String` cannot be indexed by `{integer}`
|
= help: the trait `Index<{integer}>` is not implemented for `String`
Strings are implemented in Rust.Strings
String is a wrapper over a Vec<u8>let hello = String::from("Hola");
hello.len() will be 4, which means the vector storing the string "Hola" is 4 bytes long.
let hello = String::from("Здравствуйте");
vector is 24 bytes long, not 12. This is because Cyrillic letters take 2 bytes of storage.&hello[0] will return first byte value stored, not the character З (which is capital Cyrillic letter Ze).&hello[0].नमस्ते as bytes:
[224, 164, 168, 224, 164, 174, 224, 164, 184, 224, 165, 141, 224, 164, 164, 224, 165, 135]
नमस्ते as Unicode scalar values:
['न', 'म', 'स', '्', 'त', 'े']
नमस्ते as grapheme clusters:
["न", "म", "स्", "ते"]
let hello = "Здравствуйте";
let s = &hello[0..1];
Error:
thread 'main' panicked at 'byte index 1 is not a char boundary; it is inside 'З' (bytes 0..2) of `Здравствуйте`', src/main.rs:4:14
&hello[0..3] works OK as Cyrillic characters are stored as 2 bytes.Stringsfor c in "नमस्ते".chars() {
println!("{}", c);
}
Output:
न
म
स
्
त
े
for b in "नमस्ते".bytes() {
println!("{}", b);
}
Output:
224
164
// --snip--
165
135
HashMap<K, V> stores a mapping of keys of type K to values of type V.use std::collections::HashMap;
let mut scores = HashMap::new();
scores.insert(String::from("Blue"), 10);
scores.insert(String::from("Yellow"), 50);
zip and collect functions:
use std::collections::HashMap;
let teams = vec![String::from("Blue"), String::from("Yellow")];
let initial_scores = vec![10, 50];
let mut scores: HashMap<_, _> =
teams.into_iter().zip(initial_scores.into_iter()).collect();
HashMap<_, _> is needed here because it’s possible to collect into many different data structures and Rust doesn’t know which you want unless you specify.
String and the value type will be i32zip method is used to create a vector of tuples where team is paired with initial score (e.g. “Blue” is paired with 10).collect method to turns vector of tuples into a hash map.Copy trait, like i32, the values are copied into the hash map.String, the values will be moved and the hash map will be the owner of those values.use std::collections::HashMap;
let field_name = String::from("Favorite color");
let field_value = String::from("Blue");
let mut map = HashMap::new();
map.insert(field_name, field_value);
// field_name and field_value are invalid at this point, try using them and
// see what compiler error you get!
use std::collections::HashMap;
let mut scores = HashMap::new();
scores.insert(String::from("Blue"), 10);
scores.insert(String::from("Yellow"), 50);
let team_name = String::from("Blue");
let score = scores.get(&team_name);
score will have the value that’s associated with the Blue team, and the result will be Some(&10).Some because get returns an Option<&V>; if there’s no value for that key in the hash map, get will return None.use std::collections::HashMap;
let mut scores = HashMap::new();
scores.insert(String::from("Blue"), 10);
scores.insert(String::from("Yellow"), 50);
for (key, value) in &scores {
println!("{}: {}", key, value);
}
Output:
Yellow: 50
Blue: 10
use std::collections::HashMap;
let mut scores = HashMap::new();
scores.insert(String::from("Blue"), 10);
scores.insert(String::from("Blue"), 25);
println!("{:?}", scores);
Output:
{"Blue": 25}
entry followed by or_insert methods:
use std::collections::HashMap;
let mut scores = HashMap::new();
scores.insert(String::from("Blue"), 10);
scores.entry(String::from("Yellow")).or_insert(50);
scores.entry(String::from("Blue")).or_insert(50);
println!("{:?}", scores);
entry method is an enum called Entry that represents a value that might or might not exist.or_insert method on Entry:
Entry key, or else{"Yellow": 50, "Blue": 10}
use std::collections::HashMap;
let text = "hello world wonderful world";
let mut map = HashMap::new();
for word in text.split_whitespace() {
let count = map.entry(word).or_insert(0);
*count += 1;
}
println!("{:?}", map);
Output:
{"world": 2, "hello": 1, "wonderful": 1}
or_insert method returns mutable reference to value for the key.*) (e.g. *count += 1 above)HashMap uses a hashing function called SipHash that can provide resistance to Denial of Service (DoS) attacks involving hash tables.BuildHasher trait.panic! macro signals that your program is in a state it can’t handle and lets you tell the process to stop instead of trying to proceed with invalid or incorrect values.Result enum uses Rust’s type system to indicate that operations might fail in a way that your code could recover from.fn main() {
panic!("crash and burn");
}
Error:
thread 'main' panicked at 'crash and burn', src/main.rs:2:5
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
'crash and burn') and the place in our source code where the panic occurred: src/main.rs:2:5 indicates that it’s the second line, fifth character of our src/main.rs file.panic! backtracefn main() {
let v = vec![1, 2, 3];
v[99];
}
thread 'main' panicked at 'index out of bounds: the len is 3 but the index is 99', src/main.rs:4:5
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
RUST_BACKTRACE environment variable to get a backtrace of exactly what happened to cause the error.RUST_BACKTRACE=1 in cargo run command:
$ RUST_BACKTRACE=1 cargo run
thread 'main' panicked at 'index out of bounds: the len is 3 but the index is 99', src/main.rs:4:5
stack backtrace:
0: rust_begin_unwind
at /rustc/7eac88abb2e57e752f3302f02be5f3ce3d7adfb4/library/std/src/panicking.rs:483
1: core::panicking::panic_fmt
at /rustc/7eac88abb2e57e752f3302f02be5f3ce3d7adfb4/library/core/src/panicking.rs:85
2: core::panicking::panic_bounds_check
at /rustc/7eac88abb2e57e752f3302f02be5f3ce3d7adfb4/library/core/src/panicking.rs:62
3: <usize as core::slice::index::SliceIndex<[T]>>::index
at /rustc/7eac88abb2e57e752f3302f02be5f3ce3d7adfb4/library/core/src/slice/index.rs:255
4: core::slice::index::<impl core::ops::index::Index<I> for [T]>::index
at /rustc/7eac88abb2e57e752f3302f02be5f3ce3d7adfb4/library/core/src/slice/index.rs:15
5: <alloc::vec::Vec<T> as core::ops::index::Index<I>>::index
at /rustc/7eac88abb2e57e752f3302f02be5f3ce3d7adfb4/library/alloc/src/vec.rs:1982
6: panic::main
at ./src/main.rs:4
7: core::ops::function::FnOnce::call_once
at /rustc/7eac88abb2e57e752f3302f02be5f3ce3d7adfb4/library/core/src/ops/function.rs:227
# rest of the backtrace
at ./src/main.rs:4) are called debug symbols.cargo build --release or cargo run --release.panic = 'abort' to the appropriate [profile] sections in your Cargo.toml file.[profile.release]
panic = 'abort'
ResultResult enum:
enum Result<T, E> {
Ok(T),
Err(E),
}
T represents the type of the value that will be returned in a success case within the Ok variant, andE represents the type of the error that will be returned in a failure case within the Err variant.Result enum:
use std::fs::File;
fn main() {
let f = File::open("hello.txt");
let f = match f {
Ok(file) => file,
Err(error) => panic!("Problem opening the file: {:?}", error),
};
}
Result don't need to be imported via use as its brought into scope via prelude.Ok, return the inner file value out of the Ok variant, and we then assign that file handle value to the variable f.Err value from File::open. In this example, we’ve chosen to call the panic! macro.
hello.txt file does not exist.use std::fs::File;
use std::io::ErrorKind;
fn main() {
match File::open("hello.txt") {
Ok(file) => file,
Err(error) => match error.kind() {
ErrorKind::NotFound => match File::create("hello.txt") {
Ok(fc) => fc,
Err(e) => panic!("Problem creating the file: {:?}", e),
},
// capture all other errors in `other_error` variable
other_error => panic!("Problem opening the file: {:?}", other_error)
},
};
}
File::open returns inside the Err variant is io::Error, which is a struct provided by the standard library.io::ErrorKind value.io::ErrorKind is provided by the standard library and has variants representing the different kinds of errors that might result from an io operation.ErrorKind::NotFound, which indicates the file we’re trying to open doesn’t exist yet.File::open("hello.txt"), but we also have an inner match on error.kind().unwrap_or_else of Result enum to open or create file:
use std::fs::File;
use std::io::ErrorKind;
fn main() {
File::open("hello.txt").unwrap_or_else(|error| match error.kind() {
ErrorKind::NotFound => File::create("hello.txt")
.unwrap_or_else(|error| panic!("Problem creating the file: {:?}", error),
other_error => panic!("Problem opening the file: {:?}", other_error)
}
);
}
use std::fs::File;
fn main() {
let f = File::open("hello.txt").unwrap();
}
hello.txt is present, then File object is assigned to f.
thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: Error {
repr: Os { code: 2, message: "No such file or directory" } }',
src/libcore/result.rs:906:4
expect method:
use std::fs::File;
fn main() {
let f = File::open("hello.txt").expect("Failed to open hello.txt");
}
Output:
thread 'main' panicked at 'Failed to open hello.txt: Error { repr: Os { code:
2, message: "No such file or directory" } }', src/libcore/result.rs:906:4
unwrap or expect, because that would make the program crash. Instead, use unwrap_or_else method to handle errors in code itself.? operatoruse std::fs::File;
use std::io;
use std::io::Read;
fn read_username_from_file() -> Result<String, io::Error> {
let mut f = match File::open("hello.txt") {
Ok(file) => file,
Err(e) => Err(e),
};
let mut s = String::new();
match f.read_to_string(&mut s) {
Ok(_) => Ok(s),
Err(e) => Err(e),
}
}
? operator:
use std::fs::File;
use std::io;
use std::io::Read;
fn read_username_from_file() -> Result<String, io::Error> {
let mut s = String::new();
File::open("hello.txt")?.read_to_string(&mut s)?;
Ok(s)
}
? at the end of the File::open call will return the value inside an Ok.? operator will return early out of the whole function and give any Err value to the calling code.? at the end of the read_to_string call.? operator in main function as follows:
use std::error::Error;
use std::fs::File;
fn main() -> Result<(), Box<dyn Error>> {
let f = File::open("hello.txt")?;
Ok(())
}
Box<dyn Error> type is called a trait object.
fn largest<T: std::cmp::PartialOrd + Copy>(list: &[T]) -> T {
let mut largest = list[0];
for &item in list {
if item > largest {
largest = item;
}
}
largest
}
fn main() {
let number_list = vec![34, 50, 25, 100, 65];
let result = largest(&number_list);
println!("The largest number is {}", result);
let char_list = vec!['y', 'm', 'a', 'q'];
let result = largest(&char_list);
println!("The largest char is {}", result);
}
T: std::cmp::PartialOrd + Copy with just T, you would get the following error:
error[E0369]: binary operation `>` cannot be applied to type `T`
--> src/main.rs:5:17
|
5 | if item > largest {
| ---- ^ ------- T
| |
| T
|
help: consider restricting type parameter `T`
|
1 | fn largest<T: std::cmp::PartialOrd>(list: &[T]) -> T {
| ^^^^^^^^^^^^^^^^^^^^^^
> needs the generic type T to implement std::cmp::PartialOrd trait.let mut largest = list[0] requires T to implement Copy trait so that list[0] value gets copied over to largest or else move will be attempted.
list is a reference to an array.i32 and char implement the std::cmp::PartialOrd and Copy trait.struct Point<T> {
x: T,
y: T,
}
impl<T> Point<T> {
fn x(&self) -> &T {
&self.x
}
}
fn main() {
let p = Point { x: 5, y: 10 };
println!("p.x = {}", p.x());
}
fn main() {
let p = Point { x: 5, y: 10.5 };
println!("p.x = {}", p.x());
}
you would get the following error:
error[E0308]: mismatched types
--> src/main.rs:7:38
|
7 | let p = Point { x: 5, y: 4.0 };
| ^^^ expected integer, found floating-point number
Point's x and y fields, use different generic types:
struct Point<T, U> {
x: T,
y: U,
}
This struct would support something like
let p = Point {
x: 5,
y: 4.0
};
enum Option<T> {
Some(T),
None,
}
enum Result<T, E> {
Ok(T),
Err(E),
}
let integer = Some(5);
let float = Some(5.0);
Option<T> instances and identifies two kinds of Option<T>: one is i32 and the other is f64.Option<T> into Option_i32 and Option_f64, thereby replacing the generic definition with the specific ones.Option<T> code:
enum Option_i32 {
Some(i32),
None,
}
enum Option_f64 {
Some(f64),
None,
}
fn main() {
let integer = Option_i32::Some(5);
let float = Option_f64::Some(5.0);
}
pub trait Summary {
fn summarize(&self) -> String;
}
Any type implementing the trait Summary would enable a client to call summarize method on that type's instance.pub struct NewsArticle {
pub headline: String,
pub location: String,
pub author: String,
pub content: String,
}
impl Summary for NewsArticle {
fn summarize(&self) -> String {
format!("{}, by {} ({})", self.headline, self.author, self.location)
}
}
pub struct Tweet {
pub username: String,
pub content: String,
pub reply: bool,
pub retweet: bool,
}
impl Summary for Tweet {
fn summarize(&self) -> String {
format!("{}: {}", self.username, self.content)
}
}
impl <type_name> for <trait_name> { .. } (e.g. impl Summary for Tweet { .. })Summary for Tweet type:
let tweet = Tweet {
username: String::from("megan_sparkle"),
content: String::from(
"I love England!",
),
reply: false,
retweet: false,
};
println!("1 new tweet: {}", tweet.summarize());
Output:
1 new tweet: megan_sparkle: I love England!
Summary trait was defined in another module called aggregate, then you would need to bring the trait into scope via use aggregate::Summary;Summary trait need to be local or Tweet struct need to be local.Display trait on Vec<T> within our crate, because Display and Vec<T> are defined in the standard library and aren’t local to our crate.pub trait Summary {
fn summarize(&self) -> String {
String::from("(Read more...)")
}
}
// to use default implementation, use empty block
impl Summary for NewsArticle {}
fn main () {
let article = NewsArticle {
headline: String::from("Headline"),
location: String::from("USA"),
author: String::from("Ice"),
content: String::from("Some content"),
};
println!("New article available! {}", article.summarize());
}
Summary trait for NewsArticle type without overriding summarize method, the output would be: New article available! (Read more...)
summarize method to return Read more... string.pub trait Summary {
fn summarize_author(&self) -> String;
fn summarize(&self) -> String {
format!("(Read more from {}...)", self.summarize_author())
}
}
impl Summary for Tweet {
fn summarize_author(&self) -> String {
format!("@{}", self.username)
}
}
fn main() {
let tweet = Tweet {
username: String::from("horse_ebooks"),
content: String::from(
"of course, as you probably already know, people",
),
reply: false,
retweet: false,
};
println!("1 new tweet: {}", tweet.summarize());
}
Output:
1 new tweet: (Read more from @horse_ebooks...)
summarize method of Summary trait calls summarize_author method of the same trait.summarize_author method doesn't have an implementation, structs implementing the trait need to provide an implementation for it (as is done in impl Summary for Tweet block).pub fn notify<T: Summary>(item: &T) {
println!("Breaking news! {}", item.summarize());
}
Summary trait can be passed as an argument to the notify function.&impl:
pub fn notify(item: &impl Summary) {
println!("Breaking news! {}", item.summarize());
}
+ syntax:pub fn notify<T: Summary + Display>(item: &T) {
summarize and use {} to format item.where clausefn some_function<T: Display + Clone, U: Clone + Debug>(t: &T, u: &U) -> i32 {
you can make it more clearer by using where clause:
fn some_function<T, U>(t: &T, u: &U) -> i32
where T: Display + Clone,
U: Clone + Debug
{
fn returns_summarizable() -> impl Summary {
Tweet {
username: String::from("horse_ebooks"),
content: String::from(
"of course, as you probably already know, people",
),
reply: false,
retweet: false,
}
}
This is useful in case of Closures and iterators.
impl <Trait> syntax lets you concisely specify that a function returns some type that implements the Iterator trait without needing to write out a very long type.This doesn't work if your function returns multiple types implementing the same trait. So, following code won't compile:
fn returns_summarizable(switch: bool) -> impl Summary {
// both NewsArticle and Tweet implements Summary
if switch {
NewsArticle {
//..
}
} else {
Tweet {
//..
}
}
}
struct Pair<T> {
x: T,
y: T,
}
impl<T: Display + PartialOrd> Pair<T> {
fn cmp_display(&self) {
if self.x >= self.y {
println!("The largest member is x = {}", self.x);
} else {
println!("The largest member is y = {}", self.y);
}
}
}
cmp_display method is implemented only for those generic types which implements both Display and PartialOrd trait.impl<T: Display> ToString for T {
// --snip--
}
ToString trait is implemented for all types which implement Display trait.i32 implements Display, we can do this:
3.to_string()type SomeType;type SomeType = i32;Iterator trait:
pub trait Iterator {
type Item;
fn next(&mut self) -> Option<Self::Item>;
}
Item is a placeholder typenext method’s definition shows that it will return values of type Option<Self::Item>.Iterator trait will specify the concrete type for Item and the next method will return an Option containing a value of that concrete type.Iterator trait implementor:
impl Iterator for Counter {
type Item = u32;
fn next(&mut self) -> Option<Self::Item> {
// --snip--
}
}
<PlaceholderGenericType=ConcreteType> when declaring the generic type.Add trait in std::ops:
trait Add<Rhs=Self> {
type Output;
fn add(self, rhs: Rhs) -> Self::Output;
}
type Output; is associated type, referenced by Self::Output.Rhs=Self syntax is called default type parameters.Rhs generic type parameter (short for “right hand side”) defines the type of the rhs parameter in the add method.Rhs when we implement the Add trait, the type of Rhs will default to Self, which will be the type we’re implementing Add on.
Add for Point struct as
impl Add for Point {
//..
}
where we don't provide the value of Rhs type, then
Rhs will equate to Self, andSelf in this case will equate to Point.+) in particular situations.Point struct defined as:
struct Point {
x: i32,
y: i32,
}
and you want to perform addition of two Point instances using + operator:
assert_eq!(
Point { x: 1, y: 0 } + Point { x: 2, y: 3 },
Point { x: 3, y: 3 }
);
+ operator for Point struct. This can be done by implementing Add trait (discussed above) for Point:
impl Add for Point {
type Output = Point;
fn add(self, other: Point) -> Point {
Point {
x: self.x + other.x,
y: self.y + other.y,
}
}
}
Rhs type's default is Self (equal to Point here) and associated type Output is set as Point in the trait implementation, the above code compiles OK.+ operator when RHS is of different type:
struct Millimeters(u32);
struct Meters(u32);
impl Add<Meters> for Millimeters {
type Output = Millimeters;
fn add(self, other: Meters) -> Millimeters {
Millimeters(self.0 + (other.0 * 1000))
}
}
assert_eq!(
Millimeters(1000) + Meters(1),
Millimeters(2000)
);
Game which implements traits GameStop and Amazon.
GameStop and Amazon traits have price method.Game struct also implements its own price method.trait GameStop {
pub price(&self) -> u32;
}
trait Amazon {
pub price(&self) -> u32;
}
struct Game;
impl Game {
pub price(&self) -> u32 { 100 }
}
impl GameStop for Game {
pub price(&self) -> u32 { 200 }
}
impl Amazon for Game {
pub price(&self) -> u32 { 150 }
}
game.price() method, where game is an instance of Game would return 100.
<TRAIT_NAME>::<METHOD_NAME>(..) syntax:
fn main() {
let game = Game{};
println!("Game price: {}", game.price());
println!("Game price: {}", Game::price(&game));
println!("GameStop price: {}", GameStop::price(&game));
println!("Amazon price: {}", Amazon::price(&game));
}
Output:
Game price: 100
Game price: 100
GameStop price: 200
Amazon price: 150
game.price() can also be written as Game::price(&game).self or its variants (like &self) as a first parameter.<STRUCT_NAME as TRAIT_NAME>::<FUNCTION_NAME>(...) syntax:
trait Premium {
pub price() -> u32;
}
struct Cabbage;
impl Cabbage {
pub price() -> u32 { 20 }
}
impl Premium for Cabbage {
pub price() -> u32 { 40 }
}
fn main() {
println!("Cabbage price: {}", Cabbage::price());
println!("Premium Cabbage price: {}", <Cabbage as Premium>::price());
}
Output:
Cabbage price: 20
Premium Cabbage price: 40
fn main() {
let x = 4;
let equal_to_x = |z| z == x;
let y = 4;
assert!(equal_to_x(y));
}
x is not one of the parameters of equal_to_x, the equal_to_x closure is allowed to use the x variable that’s defined in the same scope that equal_to_x is defined in.fn main() {
let x = 4;
fn equal_to_x(z: i32) -> bool {
z == x
}
let y = 4;
assert!(equal_to_x(y));
}
Error:
error[E0434]: can't capture dynamic environment in a fn item
--> src/main.rs:5:14
|
5 | z == x
| ^
|
= help: use the `|| { ... }` closure form instead
Fn traits as follows:
FnOnce consumes the variables it captures from its enclosing scope, known as the closure’s environment.
Once part of the name represents the fact that the closure can’t take ownership of the same variables more than once, so it can be called only once.FnMut can change the environment because it mutably borrows values.Fn borrows values from the environment immutably.FnOnce because they can all be called at least once.FnMutFn.
let x = 4;
let equal_to_x = |z| z == x;
x immutably, so equal_to_x has Fn trait.move keyword before the parameter list.
move closures may still implement Fn or FnMut, even though they capture variables by move.
move keyword).fn main() {
let x = vec![1, 2, 3];
let equal_to_x = move |z| z == x;
println!("can't use x here: {:?}", x);
let y = vec![1, 2, 3];
assert!(equal_to_x(y));
}
We get the following error:
error[E0382]: borrow of moved value: `x`
--> src/main.rs:6:40
|
2 | let x = vec![1, 2, 3];
| - move occurs because `x` has type `Vec<i32>`, which does not implement the `Copy` trait
3 |
4 | let equal_to_x = move |z| z == x;
| -------- - variable moved due to use in closure
| |
| value moved into closure here
5 |
6 | println!("can't use x here: {:?}", x);
| ^ value borrowed here after move
x value is moved into the closure when the closure is defined, because we added the move keyword.x, and main isn’t allowed to use x anymore in the println! statement.println! will fix this example.41 commits