Rust體驗
Hello World
fn main() {
println!("Hello, world!");
}
概述
Rust是靜態強類型語言
變量
fn main() {
let x = 5; // 默認整數類型i32
println!("x: {}", x);
// x = 6; 沒有被關鍵字mut修飾的變量不可以修改值,即變量默認不可變
let x = x + 5; // x shadowed by x
println!("x: {}", x);
let mut y = 5;
println!("y: {}", y);
y = 6;
println!("y: {}", y);
const CONST:i32 = 6 + 8; // 常量可以使用編譯器能夠直接計算得出結果的表達式作為值,但不可使用運行時才可知的表達式作為值
println!("CONST: {}", CONST);
}
基本數據類型
fn main() {
// 整數類型包括12種:8-128bit的有符號(i)和無符號(u)類型、根據操作系統位數決定的isize、usize類型,默認i32
// 浮點數類型包括兩種:f32和f64類型,默認為f64類型
// 布爾類型包括兩個值:true、false,大小為一個字節
// 字符類型可以使用''包裹單字符(utf-8字符集內)
let x = 6; // i32
let y: u8 = 6;
let z = 3.6; // f64
let b = true;
let c = '??';
println!("x:{};y:{};z:{};b:{};c:{}", x, y, z, b, c)
}
// 注意,debug模式下會在編譯時檢查整數溢出,release模式下會忽略整數溢出
容器類型
元組
fn main() {
// 元組,一個擁有固定長度的可以包含多類型的容器
let tup = (1, 'a');
let (x, y) = tup;
println!("x:{}; y:{}", x, y);
println!("x:{}; y:{}", tup.0, tup.1);
}
數組
fn main() {
// 數組,一個擁有固定長度的相同類型元素的容器
let a:[i8; 6] = [1, 2, 3, 4, 5, 6];
let b:[i8; 6*1] = [6; 6*1];
println!("{}", a[0]);
println!("{}", b[0]);
// 要修改數組中的元素,需要將數組聲明為可變數組
let mut c:[i8; 2] = [6, 8];
c[1] = 10;
println!("{}", c[1])
}
切片
fn main() {
// 切片,對數組的部分引用(很像golang中的切片是對底層數組的view)
let arr:[u8;6] = [1, 2, 3, 4, 5, 6];
let s = &arr[0..3]; // &表引用,..類似于python中的:
println!("s[0]:{}, len(s):{}", s[0], s.len());
let s1 = &s[s.len()-2..];
println!("s[-2]:{}", s1[0]);
// 通過切片修改底層數組元素需要底層數組也是mutable
let mut arr2:[u8;6] = [1, 2, 3, 4, 5, 6];
let s2 = &mut arr2[0..3];
s2[0] = 0;
println!("arr2[0]: {}", arr2[0])
}
結構體
#[derive(Debug)] // 派生屬性
// 結構體
struct Cat {
age: u8,
color: String,
}
fn main() {
// 切片,對數組的部分引用(很像golang中的切片是對底層數組的view)
let cat = Cat { age: 3, color: "三花".to_string() };
println!("age: {}; color: {}", cat.age, cat.color);
println!("{:?}", cat)
}
枚舉
#[derive(Debug)] // 派生屬性
enum Log {
Project,
Task,
}
enum Task {
Read = 1,
Write = 2,
}
enum Cat {
CatWithNum(u8, String, u8),
CatWithoutNum(u8, String),
}
fn main() {
let cat: Cat = Cat::CatWithNum(3, "三花".to_string(), 1);
match cat{
Cat::CatWithNum(age, color, num) => {
println!("age:{}; color:{}; num:{}", age, color, num)
}
Cat::CatWithoutNum(age, color) => {
println!("age:{}; color:{}", age, color)
}
}
}
類型轉換
use std::mem;
// 派生屬性
fn main() {
// 類型轉換
let a: i8 = -1;
let b = a as u8;
println!("b:{}", b);
let c: u16 = 258;
let d = c as u8;
println!("d:{}", d); // 2
let e = c as u32;
println!("e:{}", e); // 258
// transmute,直接指示編譯器將內存中的某些數據以區別于原類型的另一種類型來處理
let a = [0u8, 1u8, 0u8, 0u8, ];
unsafe {
let b: u32 = mem::transmute(a);
println!("{}", b) // 256
}
}
流程控制
fn main() {
// 表達式,除聲明語句之外的其他絕大部分代碼在Rust中都被視為表達式,這也意味著這些代碼會有返回值
let a = if true {
'a'
} else {
'b'
};
println!("a: {}", a);
}
if-else
fn main() {
// if-else表達式,當if-else表達式返回值被接收時,才可以在if-else的分支代碼塊中返回值,且各分支中的返回值必須相同類型
let x = true;
if x {
println!("{}", 6);
} else {
println!("{}", 8);
}
let y = if x {
x
} else {
false
};
println!("{}", y);
}
loop
fn main() {
// loop循環
let mut sum = 0;
let mut counter = 1;
let res = loop {
sum += counter;
counter += 1;
if counter > 100 {break sum / counter}
};
println!("sum: {}", sum);
println!("res: {}", res);
}
while
fn main() {
// while循環
let mut sum = 0;
let mut counter = 1;
while counter < 101 {
sum += counter;
counter += 1;
}
println!("sum: {}", sum)
}
for range
fn main() {
// for range,相當類似python中的for range語法
for i in 0..3 { // 左閉右開區間
println!("{}", i)
}
for i in 0..=3 { // 閉區間
println!("{}", i)
}
let mut arr = [1, 2, 3];
for i in arr.iter() { // iter()會返回迭代器
println!("{}", i)
}
for i in arr.iter_mut() { // 可修改元素
*i *= 2 // *i = *i * 2
}
for i in arr.iter() {
println!("{}", i)
}
}
match
fn main() {
// match
let m = 'x';
match m {
'a' => {
println!("'a'")
},
_ => { // default
println!("'{}'", m)
}
}
}
if-let語法糖
enum Gender {
M(String),
F,
}
fn main() {
// if-let語法糖
let gender = Gender::M("George".to_string());
if let Gender::M(s) = gender {
println!("{}", s);
}
}
while-let語法糖
#[derive(Debug)]
enum Gender {
M,
F,
}
fn main() {
// while-let語法糖
let gender = Gender::M;
let mut counter = 1;
while let Gender::M = gender {
println!("{:?}", gender);
counter += 1;
if counter == 11 {break}
}
}
函數
普通函數和方法
#[derive(Debug)]
struct Cat {
age: u8,
color: String,
}
impl Cat {
fn new(age: u8, color: String) -> Cat {
Cat{age, color}
}
fn say_my_age(&self) {
println!("我現在{}歲了", self.age)
}
fn new_age(&mut self, age: u8) {
self.age = age;
self.say_my_age()
}
}
fn handle_fbn(i: u64) -> u64 {
if i < 2 {
i
} else {
handle_fbn(i - 1) + handle_fbn(i - 2)
}
}
fn main() {
// 函數
println!("{}", handle_fbn(3));
let cat = Cat::new(3, "三花".to_string());
println!("{:?}", cat);
let mut cat2 = Cat{ age: 3, color: "四花".to_string() };
cat2.new_age(4);
}
閉包
// 使用 |var| -> res {func body}定義閉包函數并可賦值給變量
use std::thread;
fn main() {
let n = 1;
thread::spawn(move || {
println!("{}", n);
}).join().unwrap();
}
高階函數
即參數或者返回值中包含函數的函數
fn opt(f: fn(i32, i32) -> i32, a: i32, b: i32) -> i32 {
f(a, b)
}
fn add(a: i32, b: i32) -> i32 {
a + b
}
fn sub(a: i32, b: i32) -> i32 {
a - b
}
fn main() {
println!("{}", opt(add, 1, 2));
println!("{}", opt(sub, 1, 2));
}
發散函數
一個以!標志其返回空類型的永遠不會返回的函數,可以用來綁定任意類型的接收者。
猜數游戲
use std::io;
use rand::Rng;
fn main() {
let answer: u32 = rand::thread_rng().gen_range(1..101);
println!("猜數游戲開始");
loop {
println!("請輸入你猜的數字:");
let mut in_str = String::new();
io::stdin().read_line(&mut in_str).unwrap();
let in_num: u32 = match in_str.trim().parse() {
Ok(num) => num,
Err(_err) => continue
};
println!("您輸入的數字是:{}", in_num);
if in_num > answer {
println!("答錯了,輸入的數字比正確答案大。");
} else if in_num < answer {
println!("答錯了,輸入的數字比正確答案小。");
} else {
println!("恭喜你,答對了。");
break;
}
}
}
代碼組織
package
使用cargo new來創建的用于管理一個或者多個crate的結構;
main.rs、lib.rs即與所屬package同名的二進制或者庫crate的入口;
crate
rust的最小編譯單元;
module
在crate中組織獨立代碼塊的結構;
pub
成員默認私有,pub用來定義可見性:
pub 模塊可見
pub(self) 當前直接所屬子模塊可見
pub(crate) 當前crate可見
fn add1(a: i32, b: i32) -> i32 {
a + b
}
mod mod1 {
pub fn add(a: i32, b: i32) -> i32 {
a + b
}
fn add1(a: i32, b: i32) -> i32 {
a + b
}
pub mod mod2 {
fn add1(a: i32, b: i32) -> i32 {
println!("mod1::mod2::add1");
a + b
}
pub fn add2(a: i32, b: i32) -> i32 {
super::add1(a, b)
}
pub fn add3(a: i32, b: i32) -> i32 {
println!("調用來自mod2的add1");
add1(a, b) // 等同于 self::add1(a, b)
}
}
}
fn main() {
println!("{}",add1(1, 2));
println!("{}", mod1::add(1, 2));
// println!("{}", mod1::add1(1, 2)) add1 is private.
println!("{}", mod1::mod2::add2(1, 2));
println!("{}", mod1::mod2::add3(1, 2));
}
mod mod1 {
pub struct Cat {
pub age: u8,
color: String,
}
impl Cat {
pub fn new(age: u8, color: &str) -> Cat {
Cat { age, color: String::from(color) }
}
fn set_age(&mut self, age: u8) {
self.age = age;
}
pub fn set_cat(&mut self, age: u8, color: &str) {
self.set_age(age);
self.color = String::from(color);
}
}
}
fn main() {
let mut cat = mod1::Cat::new(3, "三花");
cat.set_cat(5, "四花");
// cat.set_age(6); Method `set_age` is private.
// println!("{}", cat.color); field `color` of struct `Cat` is private.
}
模塊映射
類似于python中的模塊,你可以將一個.rs文件通過文件名的形式聲明引用:mod mod1;。
也可以將模塊映射到文件夾,即在某個文件夾下直接創建mod.rs(約定的文件名),可以通過文件夾名引用:mod mod2;。
引用modx.rs中的標識符時可以用:println!("{}", mod2::modx::something),需要在mod.rs中pub聲明modx
./src
-main.rs
-mod1.rs
mod2
-mod.rs
-modx.rs
泛型
泛型,個人理解,是對強類型語言、靜態語言的類型確定這種(在運行時的優點但)在代碼組織上的缺點的一種優化,完全明確而具體的類型可能會使代碼出現冗余,所以需要可以承載不同具體類型的共同特征的一種形式,來對不同類型的傳參或者屬性賦值進行代碼復用。
這里本人聯想到python或者其他語言中的鴨子類型,鴨子類型與泛型似乎有異同點:
相同:都是對代碼書寫、組織的靈活性的一種支持;都是對不同具體類型的共同特征的提取和發布
不同:鴨子類型強調行為的一致性或者說相同點,泛型并不單獨強調行為或者屬性,而只強調共同特征;鴨子類型不一定需要標注式的參數來實現,但泛型需要這種類型參數來實現(這種類型參數需要從函數、結構體、代碼塊的起始位置聲明向內部傳遞才可以使用)
fn gt<T: PartialOrd>(a: T, b: T) -> T {
if a > b { a } else { b }
}
struct Cat<T, U> {
// 僅為演示,但實際編碼中應該拆分結構體以確保每個結構體中的泛型參數少些
age: T,
color: U,
}
struct TwoNums<T> {
a: T,
b: T,
}
impl<T: Clone + PartialOrd> TwoNums<T> {
fn gt(&self) -> T {
if self.a > self.b { self.a.clone() } else { self.b.clone() }
}
}
// 為具體類型實現方法(奇奇怪怪的想法,使用泛型又要單獨實現具體類型的方法),估計是為了少數特殊需求
impl TwoNums<u8> {
fn sub(&self) -> (u8, bool) {
self.a.overflowing_sub(self.b)
}
}
fn main() {
println!("{}", gt(1, 2));
println!("{}", gt(1.1, 2.2));
let cat = Cat {
age: 3,
color: "三花".to_string(),
};
println!("{}, {}", cat.age, cat.color);
let tn = TwoNums { a: 1, b: 2 };
println!("{}", tn.gt());
// let tn1 = TwoNums { a: 255, b: 256 }; b out of u8.
let tn1 = TwoNums { a: 254, b: 255 };
println!("{}, {}", tn1.sub().0, tn1.sub().1)
}
traits
類似于go中的接口,用來定義共同的行為
use std::fmt::Formatter;
struct Cat<T, U> {
// 僅為演示,但實際編碼中應該拆分結構體以確保每個結構體中的泛型參數少些
age: T,
color: U,
}
impl<T: std::fmt::Display, U: std::fmt::Display> std::fmt::Display for Cat<T, U> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "貓的年齡:{},貓的毛色:{}", self.age, self.color)
}
}
// 要求參數已經實現的traits
fn print_cat(cat: impl std::fmt::Display) {
println!("{}", cat)
}
fn main() {
let cat = Cat { age: 3, color: "三花" };
println!("{}", cat);
print_cat(cat);
}
派生
編譯期的動作,不會有運行時開銷
#[derive(Debug, PartialEq, Default)] // `Cat` has derived impl for the traits `Debug`、`PartialEq`、`Default`.僅修飾struct Cat。
struct Cat {
age: u8,
color: String,
}
fn main() {
let cat = Cat { age: 3, color: "三花".to_string() };
println!("{}, {}", cat.age, cat.color);
println!("{:?}", cat);
let cat2 = Cat { age: 3, color: "三花".to_string() };
println!("{}", cat == cat2);
let cat3 = Cat::default();
println!("{:?}", cat3);
}
其他理論
所有權
用來兼顧GC方便和性能的概念。
作用域
即使用"{}"包裹的代碼塊
fn main() {
// 一個值只能由一個變量持有所有權
// 值離開變量所在的作用域會被銷毀
let s = String::from("cat");
let s1 = s; // "cat"所有權轉移至s2即s1喪失"cat"的所有權
// println!("{}", s); borrow of moved value: `s`.
println!("{}", s1);
let s2 = String::from("cat");
let s3 = &s2; // borrow from s2.
println!("{}", s2);
println!("{}", s3);
// 無法從不可變變量獲取可變引用
let _s4 = String::from("cat");
// let s5 = &mut s4; // cannot borrow `s4` as mutable, as it is not declared as mutable.
let mut s5 = String::from("cat");
let s6 = &mut s5;
s6.push_str("cat");
println!("{}", s6);
// 同一個變量同時只能被取得一個可變引用
let mut s7 = String::from("cat");
let s8 = &mut s7;
// let s9 = &mut s7; cannot borrow `s7` as mutable more than once at a time. 當有代碼調用s9時此處會報錯。
println!("{}", s8);
}
生命周期
struct Cat<'a> {
color: &'a str, //color: &str, will get missing lifetime specifier
}
fn main() {
let cat = Cat { color: "三花" };
println!("{}", cat.color)
}
錯誤處理
Rust的錯誤處理類似于Golang,Golang區分了錯誤和異常的含義,將錯誤定義為正常程序的一部分,是可以被預知或處理的,而將異常定義為程序邏輯自身的錯誤。Rust則更直接得將錯誤定義為可恢復的錯誤,將異常定義為不可恢復的錯誤。
不可恢復的錯誤
fn main() {
// 不可恢復錯誤
// panic!("GG"); // 1. panic.
// assert!(false); // 2. assertion failed.
// unimplemented!(); // 3. not implemented.
// unreachable!(); // 4. 運行到預計不應該運行的代碼
}
可恢復的錯誤
fn main() {
match std::fs::read("not_exist.txt") {
Ok(content) => println!("{}", std::str::from_utf8(&content).unwrap()),
Err(err) => println!("{}", err)
}
}
?表達式
fn make_err() -> Result<u8, String> {
Err("三花".to_string())
}
fn get_err() -> Result<u8, String> {
let _ = make_err()?;
Ok(1)
}
fn main() {
println!("{:?}", get_err())
}
內建資源
Box智能指針
fn main() {
let _n = Box::new(6);
// 與let _n = 6;并無不同
// Box返回指針這個特性決定了接收者長度一定,應該在需要長度一定的屬性或者元素但這些元素或者屬性本身的值長度不一定時使用
}
#[derive(Debug)]
enum List {
Cons(i32, Box<List>),
Nil,
}
use crate::List::{Cons, Nil};
fn main() {
let list = Cons(1, Box::new(Cons(2, Box::new(Cons(3, Box::new(Nil))))));
println!("{:?}", list)
}
Rc-帶引用計數的智能指針
rc智能指針可以通過安全的通過引用計數銷毀內存的方式實現一個值存在多個所有者。
enum List {
Cons(i32, Rc<List>),
Nil,
}
use crate::List::{Cons, Nil};
use std::rc::Rc;
fn main() {
let a = Rc::new(Cons(5, Rc::new(Cons(10, Rc::new(Nil)))));
println!("創建a后計數 {}", Rc::strong_count(&a)); // 1 Rc::strong_count(&a) == a.clone()
let _b = Cons(3, Rc::clone(&a));
println!("創建b后計數 {}", Rc::strong_count(&a)); // 2
{
let _c = Cons(4, Rc::clone(&a));
println!("創建c后計數 {}", Rc::strong_count(&a)); // 3
}
println!("跳出c代碼塊后計數 = {}", Rc::strong_count(&a)); // 2
}
Vector
fn main() {
let mut v: Vec<u8> = Vec::new();
for i in 0..6 {
v.push(i)
}
println!("{}", v.len());
for i in v.iter() {
println!("{}", i)
}
}
HashMap
use std::collections::HashMap;
fn main() {
let mut m:HashMap<&str, u8> = HashMap::new();
m.insert("cat1", 3);
m.insert("cat2", 4);
for (name, &age) in m.iter() { // 無序遍歷
println!("name: {}, age: {}", name, age)
}
}
Time
原生time比較簡單,具體的功能建議直接chrono。
use std::thread::sleep;
use std::time::{Duration, SystemTime};
fn main() {
let now = SystemTime::now();
println!("{:?}", now);
sleep(Duration::from_secs(3));
println!("{:?}", now.elapsed().unwrap());
}
浙公網安備 33010602011771號