Basis
Ref: Rust语言圣经
1. Hello, World!
println!
is a macro defined instd::fmt
.
fn greet_world() {
let southern_germany = "Grüß Gott!";
let chinese = "世界,你好";
let english = "World, hello";
let regions = [southern_germany, chinese, english];
for region in regions.iter() {
println!("{}", ®ion);
}
}
fn main() {
greet_world();
}
2. Variables
2.1 bindings
-
declare variable bindings and initialize them
-
immutable by default; declare
mut
for mutable onesdiffers from
const
: can be initialized later; type annotation is not necessary -
shadowing: re-declare bindings
fn main() {
let (a, mut b): (bool,bool) = (true, false);
// a = true,不可变; b = false,可变
println!("a = {:?}, b = {:?}", a, b);
b = true;
assert_eq!(a, b);
}
fn main() {
let mut x = 5_i32;
let mut x: i32 = x + 1;
let y;
x *= 2;
y = x.pow(2);
println!("x is {}, y is {}", x, y);
const Z_NUM: i32 = 1000;
println!("z^2 is {}", Z_NUM.pow(2));
}
2.2 Types
2.2.1 Tips
-
NO implicit conversion!
i32
for integers by default -
overflow/nan checking:
// pub fn overflowing_add_signed(self, rhs: i32) -> (u32, bool) assert_eq!((u32::MAX - 2).overflowing_add_signed(4), (1, true)); let x = (-42.0_f32).sqrt(); if x.is_nan() { println!("Error!") }
-
type conversion:
let b: u16 = 100; (b as i32)
-
Comparing by traits :
std::cmp::PartialEq
; operators can be used to trigger them.eq
assumes: symmetry, transitivity and reflexivity#![allow(unused)] fn main() { enum BookFormat { Paperback, Hardback, Ebook, } struct Book { isbn: i32, format: BookFormat, } impl PartialEq for Book { fn eq(&self, other: &Self) -> bool { self.isbn == other.isbn } } let b1 = Book { isbn: 3, format: BookFormat::Paperback }; let b2 = Book { isbn: 3, format: BookFormat::Ebook }; let b3 = Book { isbn: 10, format: BookFormat::Paperback }; assert!(b1 == b2); assert!(b1 != b3); }
-
range:
1..5
,1..=5
for i in 1..=5 { println!("{}",i); }
-
use
num
lib:# in Cargo.toml [dependencies] num = "0.4.0" num-bigint = "0.4"
use num::{complex::Complex, traits::Pow}; use num_bigint::{BigInt, ToBigInt}; fn main() { let a = Complex { re: 2.1, im: -1.2 }; let b = Complex::new(11.1, 22.2); let result = a + b; println!("{} + {}i", result.re, result.im); // explicit type annotation needed for calling impls let x = 0x7fffffff_i32.to_bigint().unwrap(); let y = 0x7fffffff_i32.to_bigint().unwrap(); let z: BigInt = x + y + 2; println!("{}", z.pow(4_u32)); }
-
Unicode supported
let x: char = '我'; println!("{}", std::mem::size_of_val(x)); // 4
-
unit type
()
as a placeholder -
&str
is immutable; useString
for mutable strings:fn main() { let mut s = String::from("hello"); s.push_str(", world!"); println!("{}", s); // hello, world! }
-
enum
can have data:enum Message { Quit, Move { x: i32, y: i32 }, Write(String), ChangeColor(i32, i32, i32), }
-
None
inOption<T>
andmatch
enum Option<T> { Some(T), None, }
fn plus_one(x: Option<i32>) -> Option<i32> { match x { None => None, Some(i) => Some(i + 1), // i takes the value inside Some! } }
-
array
let a: [i32; 5] = [1, 2, 3, 4, 5]; let b = [3; 5]; // [3,3,3,3,3]
2.2.2 Vector
let v: Vec<i32> = Vec::with_capacity(10);
// get returns Option<&T>
match v.get(2) {
Some(third) => println!("The 3rd one is {}", third),
None => println!("404 Not Found"),
}
2.2.3 HashMap
insert
may transfer the ownership if thecopy
trait is not implemented
use std::collections::HashMap;
let mut map: HashMap<&str, i32> = HashMap::new();
map.insert("key01", 1);
let number: Option<&i32> = map.get("1");
for (key, value) in &map {
println!("{}: {}", key, value);
}
let number: &mut i32 = map.entry("100").or_insert(100); // insert if not existed
*number = 56; // "100": 56
build HashMap from Vector by collect
:
let vec = vec![
("1", 1),
("2", 2),
];
let map: HashMap<_, _> = vec.into_iter().collect();
// println!("vec: {:?}", vec); // illegal: vec is borrowed by into_iter
println!("map: {:?}", map);
3. Statement
Statements and expressions are not the same!
- statements have no value
- expressions have values; they cannot contain
;
4. Function
-
must annotate types for each argument
-
"no return value" is equivalent to returning
()
-
functions that NEVER return "return" a
!
fn dead_end() -> ! { panic!("你已经到了穷途末路,崩溃吧!"); }
5. Ownership
5.1 Basis
- every value has only one owner
- a value will be freed when its owner is not in opening scopes
- C++ behavior
std::move
by default! including assign, pass args or return values
Transferring ownership is analog to std::move
(e.g. String
is stored in heap
; it doesn't has copy
trait!):
fn main() {
let s1 = String::from("hello");
let s2 = s1; // s1 is invalid after this
println!("{}, world!", s1); // illegal!
let x = String::from("abc");
let y = x.clone(); // deep copy
println!("{}, {}", x, y); // legal
}
5.2 Borrowing
-
Only one mutable reference or any number of immutable references can be valid at the same time.
-
References are valid until they are not be used.
The compiler will compute the smallest range for it.
DIFFERENT from variable bindings!
-
Use mutable ref. referring to a mutable ref. to change the value of the referred one:
let mut a = String::from("hello"); let mut b = String::from("world"); let mut c = &mut a; // make a mutable by ref *c = b.clone(); c = &mut b; // thanks to c's mutability; make b mutable by ref *c = "hello".to_string(); println!("a: {}", a); // world println!("b: {}", b); // hello
fn main() {
let mut s = String::from("hello");
change(&mut s);
println!("{}", cal_len(&s));
}
fn change(some_string: &mut String) {
// deref traits enables direct using of a reference when calling a method of the object
some_string.push_str(", world");
}
fn cal_len(s: &String) -> usize {
s.len()
}
Non-Lexical Lifetimes (NLL)
fn main() {
let mut s = String::from("hello");
let r1 = &s;
let r2 = &s;
println!("{} and {}", r1, r2);
// r1, r2 are invalid at this point
let r3 = &mut s;
println!("{}", r3);
// r3 is not invalid at this point
}
6. Flow Control
6.1 for
使用方法 | 等价使用方式 | 所有权 |
---|---|---|
for item in collection |
for item in IntoIterator::into_iter(collection) |
转移所有权至 for (后续无法使用) |
for item in &collection |
for item in collection.iter() |
不可变借用 |
for item in &mut collection |
for item in collection.iter_mut() |
可变借用(元素可以被修改) |
Iterate index and value simultaneously:
let a = [ 1, 2, 3 ];
for (i, v) in a.iter().enumerate() {
continue;
}
for item in &collection
is more efficient than visit elements in a collection by index, because the later one always needs bounds checking.
6.2 loop
Returning from loop:
fn main() {
let mut counter = 0;
let result = loop {
counter += 1;
if counter == 10 {
break counter * 2; // return by break
}
};
assert_eq!(result, 20);
}
7. Patterns and Matching
7.1 match arms
match some_u8_value {
1 | 2 => println!("one or two"),
3..5 => println!("three to four"),
_ => (),
}
Shadowing inside match
:
fn main() {
let age = Some(30);
println!("在匹配前,age是{:?}",age);
if let Some(age) = age {
// here age is i32 but not Some(i32)
println!("匹配出来的age是{}",age);
}
println!("在匹配后,age是{:?}",age);
}
7.2 if let
Actually, let
is a keyword for "pattern binding".
if
makes it possible that except the written case, other cases can be ignored.
Only match one case:
if let Some(3) = v {
println!("three");
}
let mut count = 0;
if let Coin::Quarter(state) = coin {
println!("State quarter from {:?}!", state);
} else {
count += 1;
}
if let
can be used together with else if
:
if let Some(color) = favorite_color {
println!("Using your favorite color, {}, as the background", color);
} else if is_tuesday {
println!("Tuesday is green day!");
} 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");
}
7.3 matches! (macro)
matches!
returns true
or false
:
let bar = Some(4);
assert!(matches!(bar, Some(x) if x > 2));
7.4 while let
let mut stack = Vec::new();
stack.push(1);
stack.push(2);
stack.push(3);
// pop returns Option<T>
// loop break when it returns a None, which causes fail matching
while let Some(top) = stack.pop() {
println!("{}", top);
}
7.5 deconstruction and matching
struct Point {
x: i32,
y: i32,
}
fn main() {
let p = Point { x: 0, y: 7 };
let Point { x: a, y: b } = p;
assert_eq!(0, a);
assert_eq!(7, b);
// less verbose
let Point { x, y } = p;
assert_eq!(0, x);
assert_eq!(7, y);
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),
}
}
Use ..
to skip some values:
let numbers = (2, 4, 8, 16, 32);
match numbers {
(first, .., last) => {
println!("Some numbers: {}, {}", first, last);
},
}
7.6 match guard
if
condition after match arms:
let x = 4;
let y = false;
match x {
4 | 5 | 6 if y => println!("yes"),
_ => println!("no"), // the matched one due to y is false
}
7.7 @ binding
var @ pattern
: bind the value matching the pattern to var
for later use.
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")
}
// parenthese outside the pattern are required!
hello @ (Message::Hello { id: 10..=12 } | Message::Hello { id: 13..=15 }) => {
println!("Found an id in another range when hello is {:?}", hello)
}
Message::Hello { id } => {
println!("Found some other id: {}", id)
}
}
8. Method
pub struct Rectangle {
width: u32,
height: u32,
}
impl Rectangle {
fn new(w: u32, h: u32) -> Rectangle {
Rectangle { width: w, height: h }
}
// getter
pub fn width(&self) -> u32 {
return self.width;
}
fn can_hold(&self, other: &Rectangle) -> bool {
self.width > other.width && self.height > other.height
}
}
9. Generics
e.g.
enum Result<T, E> {
Ok(T),
Err(E),
}
Restricting type parameter T
to what can be added by +
is needed:
fn add<T: std::ops::Add<Output = T>>(a:T, b:T) -> T {
a + b
}
e.g.
struct Point<T, U> {
x: T,
y: U,
}
impl<T, U> Point<T, U> {
fn mixup<V, W>(self, other: Point<V, W>) -> Point<T, W> {
Point {
x: self.x,
y: other.y,
}
}
}
"Changeable reference":
fn largest<T: PartialOrd>(list: &[T]) -> &T {
let mut largest: &T = &list[0];
for item: &T in list.iter() {
if item > largest {
largest = item;
}
}
largest
}
10. Trait
- a collection of functions
- trait object as a type:
Box<dyn MyStruct>
or&dyn MyStruct
trait Draw {
fn draw(&self) -> String;
}
struct Screen {
// trait object
components: Vec< Box<dyn Draw> >,
}
impl Draw for u8 {
fn draw(&self) -> String {
format!("u8: {}", *self)
}
}
impl Draw for f64 {
fn draw(&self) -> String {
format!("f64: {}", *self)
}
}
impl Screen {
fn run(&self) {
for comp in &self.components {
println!("{}", comp.draw());
}
}
}
fn main() {
let a = 4_u8;
let b = 6.54_f64;
let screen = Screen {
components: vec![
Box::new(a),
Box::new(b),
]
};
screen.run();
}
11. Error handling
panic!("crash and burn");
// unwarp: Ok -> get the value; Err -> panic
use std::net::IpAddr;
let home: IpAddr = "127.0.0.1".parse().unwrap();
// expect: output something when panic
use std::fs::File;
let f = File::open("hello.txt").expect("Failed to open hello.txt");
Use ?
to spread errors:
// Result<String, Box<dyn std::error::Error>>
fn read_username_from_file() -> Result<String, io::Error> {
let mut s = String::new();
File::open("hello.txt")?.read_to_string(&mut s)?; // chain calling
Ok(s)
}
Use ?
to spread None
:
fn last_char_of_first_line(text: &str) -> Option<char> {
text.lines().next()?.chars().last()
}