- io::stdin().read_line(&xx) appends new value on the target which is “xx” in this case.
- A tuple can have values with different types in it while an array must have the same type.
- A tuple with explictly type annotations example:
let x: (i32, f64, u8) = (500, 6.4, 1);
- An array in Rust have a fixed length while a Vector can grow or shrink its size.
- An array with explictly type and elements’ number annotations example:
let a: [i32; 5] = [1, 2, 3, 4, 5];
- If [6, 6, 6, 6, 6] is the desirable array, we can declare it in this way:
let a = [6; 5];
- A good way to create a new instance of a struct that uses most of an old instance’s values but changes some is using update syntax:
let user2 = User { email: String::from("user2@example.com"), username: String::from("user2name"), ..user1 };
- An example of using derived traits of custom structs:
#[derive(Debug)] println!("{:?}",user1); println!("{:#?}",user1);
- If a struct tuple is defined, when a function want to call an instance of this struct, other struct tuple (even with the same form) cannot be used:
struct Color (u32,u32,u32); struct Point (u32,u32,u32);
In this case, if a function want to call an instance of “Color”, we cannot put an instance of “Point” in it even they have the same form.
- An example of a struct and its method implementation:
struct Rectangle { width: u32, height: u32, } impl Rectangle { fn area(&self) -> u32 { self.width * self.height } //method with multiple parameters fn can_hold(&self, other:&Rectangle) -> bool { self.height > other.height && self.width > other.width } //associated function fn square(size:u32) -> Rectangle { Rectangle {width:size, height:size} } }
When we want to use them:
rect1.can_hold(&rect2) let sq = Rectangle::square(20);
- Option
and T are different types so that we cannot add an i8 and an Option . - If we use None rather than Some, we need to tell Rust what type of Option
we have: let absent_number: Option<i32> = None;
- Using match in order to check elements in enum or check Option
: match seven { Some(7) => println!("{:?}", seven), _ => (), }
The
_
pattern will match any value. The()
is just the unit value, so nothing will happen in the _ case. However, the example above can be written inif let
which is simpler to read:if let Some(7) = seven { println!("{:?}",seven); }
Also,
else
can be used withif let
:if let Some(7) = seven { println!("{:?}",seven); } else { println!("This is a else statement."); }
- Absolute path starts from a crate root by using a crate name or a literal
crate
:crate::front_house::hosting::add_to_waitlist();
Relative path starts from the current module and uses
self
,super
, or an identifier in the current module:front_house::hosting::add_to_waitlist(); self::front_house::hosting::add_to_waitlist();
btw when we use
use
keyword for this one, we need to useself
(but this usage might not be necessary in the future):use self::front_of_house::hosting;
- Use nested paths to bring the same items into scope in one line:
use std::{cmp::Ordering, io};
When there are two
use
statements where one is a subpath of the other, instead of using:use std::io; use std::io::Write;
we can use:
use std::io::{self, Write};
- When using
pub
to a struct, we need to also usepub
to fields we want to make it public:pub struct Breakfast { pub toast : String, //fruit is still private as its default fruit : String, }
but for an enum we only need to use
pub
to the enum itself:pub enum Appetizer { //both Soup and Salad are now public Soup, Salad, }
- Usually we
use
the full path for structs and enums:use std::collections::HashMap; fn main() { let mut map = HashMap::new(); map.insert(1, 2); }
when multiple of them have the same name we want to use their parent path in order to avoid conflicts:
use std::fmt; use std::io; fn function1() -> fmt::Result {} fn function2() -> io::Result<()> {}
but we can still use the full path if we use
as
keyword to give them aliases:use std::fmt::Result; use std::io::Result as IoResult; fn function1() -> Result {} fn function2() -> IoResult<()> {}
and usually when we want to use functions from outside, we
use
their parent path. - We can separate modules into different files and still
use
the same path when we use them. But we need to declare them first in their parent path level by level until it comes to lib.rs. In this example ,the files structure is like this:
|-src
| -front_of_house
| -hosting.rs
| -lib.rs
| -front_of_house.rs
lib.rs:mod front_of_house; pub use crate::front_of_house::hosting; pub fn eat_at_restaurant() { hosting::add_to_waitlist(); }
front_of_house.rs:
pub mod hosting;
hosting.rs:
pub fn add_to_waitlist() {}
- Store elements with different types using
enum
:enum SpreadsheetCell { Int(i32), Float(f64), Text(String), } let row = vec![ SpreadsheetCell::Int(1), SpreadsheetCell::Text(String::from("test")), SpreadsheetCell::Float(3.14), ];
- Cast a type to a different type:
let num1 : u32 = 1; let num : f32 = (num1) as f32;
- Instead of just using
match
in error handling:fn read_username_from_file() -> Result<String, io::Error> { let f = File::open("hello.txt"); let mut f = match f { Ok(file) => file, Err(e) => return Err(e), }; let mut s = String::new(); match f.read_to_string(&mut s) { Ok(_) => Ok(s), Err(e) => Err(e), } }
We can use
?
:fn read_username_from_file() -> Result<String, io::Error> { let mut f = File::open("hello.txt")?; let mut s = String::new(); f.read_to_string(&mut s)?; Ok(s) }
Or the shorter one:
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) }
BTW in the case above there is a even shorter way to do the same thing:
fn read_username_from_file() -> Result<String, io::Error> { fs::read_to_string("hello.txt") }
- When using
trait
as a parameter in a function:pub fn notify(item1: impl Summary, item2: impl Summary) {}
in this case is similar to:
pub fn notify<T: Summary>(item1: T, item2: T) {
However, this way will force both parameters to have the same type whih is T. If we want to allow
item1
anditem2
to have different types, it’s more appropriate to useimpl Trait
syntax. - We can use
+
for multiple trait bounds:pub fn notify(item: impl Summary + Display) {}
which in this case is the same as:
pub fn notify<T: Summary + Display>(item: T) {}
- Instead of using:
fn some_function<T: Display + Clone, U: Clone + Debug>(t: T, u: U) -> i32 {}
We can use a
where
clause:fn some_function<T, U>(t: T, u: U) -> i32 where T: Display + Clone, U: Clone + Debug {}
- We can make functions to return types which implement a specific trait:
fn returns_summarizable() -> impl Summary {}
However, we can only use impl Trait if we’re returning a single type. We cannot do this:
fn returns_summarizable() -> impl Summary { if condition { StructA{...} } else { StructB{...} } }
In this case the program will not compile even if both structs have the trait
Summary
.