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());
}