安装和使用
安装(具体参见 官网)
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
更新
rustup update
cargo update
使用
# 编译
rustc main.rs
# 运行
./main
cargo:系统和包管理器
cargo --version
# 创建一个新的cargo项目
cargo new learn_rust_project
cargo init --lib
# 编译
cargo build
# 编译并运行代码
cargo run
# 编译并发布,
# 1)生成到 target/release 下,而不是 target/debug下
# 2)会做更深的优化
cargo build --release
# cargo 会优先搜索 cargo.lock 中的版本,以实现每次都用同一个版本的包。如果强制刷新 cargo.lock :
cargo update
# 下载所用的包的相关帮助,并打开
cargo doc --open
cargo clean
变量
// 可变变量
let mut x = 1;
// 不可变变量
let x = 1;
// 常量
const THREE_HOURS_IN_SECONDS: u32 = 60 * 60 * 3;
遮蔽
遮蔽
let x = 5;
// x = 5
let x = x + 1;
// x = 6
{
let x = x * 2;
// x = 12
println!("The value of x in the inner scope is: {}", x);
}
println!("The value of x is: {}", x);
// 内部遮蔽结束,这里 x = 6
遮蔽2
// 可以重新绑定
let mut x = "abcd";
let x = "aaa";
let mut x = x.len();
数据类型
- rust是静态类型,必须编译时知道所有变量的类型
整型
长度 | 有符号类型 | 无符号类型 |
---|---|---|
8 位 | i8 | u8 |
16 位 | i16 | u16 |
32 位 | i32 | u32 |
64 位 | i64 | u64 |
128 位 | i128 | u128 |
根据系统,32/64位 | isize | usize |
- 浮点型
f32
,f64
- bool类型
bool
- 元组
- 赋值:
let tup: (i32, f64, u8) = (500, 6.4, 1);
- 可以是不同类型
- 取值1:
let (x, y, z) = tup;
- 取值2:
let y = tup.1;
- 赋值:
- 数组
- 赋值
let a: [i32; 5] = [1, 2, 3, 4, 5];
- 赋值2:
let a = [3; 5];
这是3重复5次 - 赋值后长度固定,不可再改
- 内存整块分配到栈上,而不是堆上
- 取值:
a[0]
- 赋值
其它
// 允许任意添加下划线,以提高可读性
let num: i32 = 100_00_000_000;
let num: i32 = 0xff; // 16进制
let num: i32 = 0o77; // 八进制
let num: i32 = 0b111_000;// 二进制
数据类型的选择
- i32 大多数情况下最快
- f64 与 f32 运行速度差不多,因此默认类型推导是 f64
format
let x = 42; // 42 is '2A' in hex
// 转十六进制
assert_eq!(format!("{x:X}"), "2A");
assert_eq!(format!("{x:#X}"), "0x2A");
assert_eq!(format!("{:X}", -16), "FFFFFFF0");
// n进制:b二进制,xX十六进制,o八进制
let a = format!("{:o}", 0b111000);
println!("{}", a);
// 转科学计数法
assert_eq!(format!("{x:E}"), "4.2E1");
// 引用、Box 和其它指针类型的地址
format!("{:p}", &a)
x.to_string() // 转文本
// +代表显示符号,#代表显示进制头,0代表补0,12代表最小长度,b代表二进制
let a = format!("{num:+#012b}", num = 0b110);
println!("{}", a);
// 1位小数
let a = format!("{a:8.1}", a = 1.23);
println!("{}", a);
// https://blog.csdn.net/feiyanaffection/article/details/125575203
as 转化
// bool 类型可以转 int
assert_eq!(true as i32, 1); // true转换为1
// char 类型其实是一个 i32
assert_eq!('*' as i32, 42);
// ascii 值转 char
assert_eq!(65 as char, 'A');
assert_eq!(std::char::from_u32(65).unwrap(), 'A');
// 数值类型可以任意相互转化
基本运算
+-*/ // 加减乘除
a%b //取余
数字对应的方法
// 按位前后颠倒
let a = 0b01110000_i8.reverse_bits(); // 返回 0b00001110
// swap_bytes:按字节颠倒
// 二进制表示的1的个数
let a: u32 = 0b1110_i8.count_ones();
// 二进制表示的0个个数
let a: u32 = 0b1110_i8.count_zeros();
a&b // 按位与
a|b // 按位或
a^b // 按位异或
!a // 按位否
>> // 按位右移
<<
逻辑
// 比较
==, !=, <, >, <=, >=
// 逻辑运算
&&
||
! // 逻辑非
科学计算
// 常量
use std::f64::consts::{PI, E, LOG10_E};
let a: f64 = 9.0;
a.abs()
a.exp(); // e^a
a.exp2(); // 2^a
a.log(3.0); // 以3.0为底的对数
a.log2(); // 以2为底
a.log10(); // 以10为底
a.ln()
x.ln_1p() // ln(1+x)
a.powf(3.0); //a^3.0
a.powi(3); //a^3
a.floor(); // 向下取整
a.ceil(); // 向上取整
a.round(); //四舍五入
a.fract(); // 小数部分
a.trunc(); // 整数部分
a.clamp(3.0, 5.0); // 裁剪到3.0~5.0之间
// 大端表示小端表示
// a.to_le,a.to_be,to_be_bytes,to_ne_bytes
https://doc.rust-lang.org/std/primitive.i32.html
// 含头不含尾
0..6
(0..6).rev()
结构体 struct
struct User {
active: bool,
username: String,
email: String,
sign_in_count: u64,
}
fn main() {
// 不允许单独一个字段可变,只能整体全都可变
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");
}
简写1
let email = String::from("123@qq.com");
let username = Strin::from("name");
let mut user2 = User {
// 简写:email:email,
email,
username,
active: true,
sign_in_count: 2,
};
简写2:复制别的值进来
let user3 = User {
email: String::from("321@"),
..user1
};
// user1就不能用了,因为它的 username 已经移动过来了
// 不过 active 和 sign_in_count 还是可以用
没有字段名的结构体
struct Color(i32, i32, i32);
let black = Color(0, 0, 0);
// 可以声明字段共有
// struct Color(pub i32,pub i32,pub i32);
没有字段的结构体
struct AlwaysEqual;
fn main() {
let subject = AlwaysEqual;
}
注:字段默认是私有的。
结构体的方法
#[derive(Debug)]
struct Rectangle {
width: u32,
height: u32,
}
impl Rectangle {
// &self 是 self: &Self 的缩写
// 这里的 &self 指的是 rectangle: &Rectangle
// 加 & 是因为不想获取所有权,只希望读,而不希望写
fn area(&self) -> u32 {
self.width * self.height;
}
}
方法和字段可以同名
#[derive(Debug)]
struct Rectangle {
width: u32,
height: u32,
}
impl Rectangle {
fn width(&self)->bool{
self.width>0
}
}
impl 也可以不接一个方法,通常用来构建一个对象
impl Rectangle {
fn square(size: u32) -> Rectangle {
Rectangle {
width: size,
height: size,
}
}
}
// 通常用来做一个构造器
impl Rectangle{
pub fn new() -> Rectangle{
Rectangle{
width: 1,
height: 1
}
}
}
// 用 Rectangle::new() 可以调用它
// Self指得是自身类的别名
impl Rectangle{
pub fn new() -> Self{
Self{
width: 1,
height: 1
}
}
}
泛型
泛型用于 struct
// 1. 派生 trait 。这要求所有的字段都支持这些 trait 才行
#[derive(Copy, Clone, Debug, PartialEq)]
struct Point<T> {
x: T,
y: T,
}
// 2. 泛型用于方法
impl<T> Point<T> {
fn get_x(&self) -> &T {
&self.x
}
}
// 都合法
let p1 = Point { x: 5, y: 6 };
let p1 = Point { x: "mid", y: "mid" };
使用泛型时,如何做类型推断
pub struct MyQueue<T> {
data: Vec<T>,
}
impl<T> MyQueue<T> {
pub fn new() -> MyQueue<T> {
MyQueue {
data: Vec::new()
}
}
pub fn push(&mut self, t: T) {
self.data.push(t)
}
pub fn pop(&mut self) -> Option<T> {
self.data.pop()
}
}
fn main() {
// 可以自动推断类型(最常用)
let mut q1 = MyQueue::new();
q1.push(1.0);
// 也可以这样指定类型
let mut q2 = MyQueue::<i32>::new();
//也可以这样
let mut q3: MyQueue<i64> = MyQueue::new();
}
知识点
- 泛型没有额外的性能消耗。编译时会把所有用过的具体类型都填进去。
生命周期:https://blog.csdn.net/feiyanaffection/article/details/125574590
match 相关
enum:枚举
enum IpAddrKind {
// 可以是不同类型
V4(u8, u8, u8, u8),
V6(String),
}
let four = IpAddrKind::V4;
// IpAddrKind 可以像一个类型一样使用
let six: IpAddrKind = IpAddrKind::V6(String::from("::1"));
应用:
- 范型。例如在 vec中塞入int和string
- Option 类型。实际上是 enum
- Result 类型。实际上是 enum
match
enum Coin {
Penny,
Nickel,
Dime,
Quarter,
}
let coin = Coin::Penny;
let res: u8 = match coin {
Coin::Penny => {
println!("Lucky penny!");
1
}
Coin::Nickel => 5,
Coin::Dime => 10,
Coin::Quarter => 25,
};
match 中的 other
fn func1() {}
fn func2(num_spaces: u8) {}
let num = 9;
match num {
3 => func1(),
7 => (), // 表示什么也不做
other => func2(other)
}
如果变量 other 没用,可以用下划线代替
match num {
3 => func1(),
7 => func2(),
_ => func1()
}
知识点
- pub 枚举的所有构造式和字段都会是 pub 的
- match 可以是 元组、结构体。
-
可以多匹配 ` 9 10 => { println!(“big”) }`
https://blog.csdn.net/feiyanaffection/article/details/125574617?spm=1001.2014.3001.5502
泛型枚举 Option 和 Result
常见的泛型枚举:
enum Option<T> {
None,
Some(T)
}
enum Result<T, E> {
Ok(T),
Err(E)
}
如何使用 Option
// Option:
let opt = Some(5); // or None
// 1. 用 match
match opt {
None => { println!("值为 None"); }
Some(num) => { println!("值为 {}", num); }
}
// 1.1 if let
// 1.2 let a = match{}
// 2: unwrap
// 是 None 时设定为某个值
let a: i32 = opt.unwrap_or(7);
// 是 None 时调用一个函数,返回的是 Option
let a: Option<i32> = opt.or_else(|| Some(6));
// 是 None 时返回默认值
opt.unwrap_or_default()
// 2.1 ok_or: 转 Result
// ok_or:Option 转 Result
let a: Result<i32, &str> = opt.ok_or("hello");
// 然后用链式很方便
let a: i32 = opt.ok_or(anyhow!("hello"))?;
// ok_or_else(err_fn),类似于 ok_or 但是传入的是一个函数
// 3. 逻辑运算
// 如果 opt1 为 Some,返回 opt1;如果opt1 为 None,返回 opt2
opt1.or(opt2)
// 如果两个都是 Some/None,返回 None;如果一个 Some,一个是 None,返回 Some
opt1.xor(opt2)
// 4. 其他消费方式
if some_option.is_some(){
...
}
if some_option.is_none(){
...
}
// 慎用
opt.unwrap() // 为 None 时 Panic
opt.unwrap_unchecked() // 必须用 unsafe 圈起来,程序员自己保证其非 None
// 5. 关于 Result 错误的链式传递,参见下面的章节“错误”
Result
// Result:
let mut res: Result<i32, String> = Ok(5);
// 1.1 match
res = Err("错误信息".to_string());
match res {
Err(e) => { println!("出错:{}", e); }
Ok(num) => { println!("值为:{}", num); }
}
// 1.2 可以使用 let x = ... 来接受代码块中的信息
let x = match res {
Ok(x) => {
println!("Ok :x = {}", x);
x
}
Err(e) => {
println!("error:{}", e);
0
}
};
// 1.3 if let 语句
// 1.3.1 if let Err(e) 也可以
// 1.3.2 这个 x 的作用范围只在代码块里面
if let Ok(x) = res {
println!("Ok x = {}", x);
} else { println!("error") }
// 一个返回 Result的函数
fn func1(x: i32) -> Result<i32, String> {
if x < 0 {
Err(format!("错误信息: {} <0 ", x))
} else {
Ok(x)
}
}
let res = func1(-6);
match res {
Ok(T) => { println!("正确的值 {:?}", T) }
Err(e) => { println!("错误的值,e:String = {:?}", e) }
}
如何解Result
// 1. 使用问号 ?,允许链式传递错误,简洁
fn my_main() -> Result<String, io::Error> {
let mut s = String::new();
File::open("a.txt")?.read_to_string(&mut s)?;
Ok(s)
}
// 1.1 如果涉及到多个 Error 类型,可以把它们都转为 Result<T, String> 类型,做到统一。
fn my_main() -> Result<(), String> {
let bytes = fs::read("不存在的文件").map_err(|e| e.to_string());
let y = func1(-5)?;
Ok(())
}
// 1.2 当然也可以不使用 String,而是自定义 struct Error
// (代码略)
// 2. unwrap_or_else: 允许更自定义的处理
// 2.1 给出一个默认值,如果错误就使用默认值
let bytes = fs::read("不存在的文件").unwrap_or_else(|e| vec![]);
// 2.2 或者 panic
let bytes = fs::read("不存在的文件").unwrap_or_else(|e| panic!("出错啦! {:?}", e));
// 2.3 或者按照 error 类型处理
let bytes = fs::read("不存在的文件").unwrap_or_else(
|e| {
if e.kind() == ErrorKind::NotFound { panic!("没找到文件,{}", e) } else { panic!("其它错误, {}", e) }
});
// 4. 遇到错误直接 panic(慎用)
fs::read("不存在的文件").unwrap();
fs::read("不存在的文件").expect("遇到错误 panic");
fs::read("不存在的文件").expect_err("遇到正确 panic");
知识点
程序默认:Panic时,Rust沿着调用栈反方向遍历调用函数,并依次清理数据。也可以立即停止,这样就不会清理,内存只能由操作系统回收,如果想如此,就这样:
# Cargo.toml
[profile.release]
panic = 'abort'
Rust有两种错误
- Panic:不应该有的错误
- Result:这里
关于 Panic
// 1. 手动触发 Panic
panic!("panic");
// 2. 无意中触发 Panic
let num = 5 / 0;
// 3. 对 Result 类型使用 unwrap, expect。
控制流程
let y = {
let x = 3;
x + 1
};
// y=4,因为代码块最后一句是它的返回值
// 函数的最后一句也是返回值,但最后一句不要加分号
fn my_func(x: i32) -> i32 {
x + 1
}
if 语句
// 1. 不会自动转换为 bool 值,必须手动转换
if x < 0 {
println!("x<0")
} else if x < 1 {
println!("0<=x<1")
} else { println!("x>=1") }
if 实现三目运算符
let age = 20;
// 每个分支必须都是相同类型
let sex = if age > 18 { "adult" } else { "child" };
loop:不停的执行
- 可以与break,continue连用
let mut x = 1; loop { x = x + 1; if x > 20 { break; } else if x % 3 == 0 { continue; } println!("{}", x); }
- break 可以像 return 一样使用,然后赋值
let mut cnt = 1; let res = loop { cnt += 1; if cnt > 10 { break "end"; } };
- break可以指定一个 生命周期
'search: // 下面break的时候,直接跳到这里(跳了2层) for i in 0..10 { for j in 0..10 { println!("i = {}, j = {}", i, j); if j == 5 { break 'search; } } }
while 条件循环
while cnt < 10 {
cnt += 1;
}
for 循环
let lst = [10, 20, 30, 40, 50];
for element in lst {
println!("the value is: {}", element);
}
for num in (1..4).rev() {
println!("{}", num);
}
匿名函数
let func_even = |x: u32| x % 2 == 0;
// 指定类型就必须加大括号
let func_even2 = |x: u32| -> bool{ x % 2 == 0 };
函数指针
fn add_one(x: i32) -> i32 {
x + 1
}
fn do_twice(f: fn(i32) -> i32, arg: i32) -> i32 {
f(arg) + f(arg)
}
fn main() {
let answer = do_twice(add_one, 5);
println!("The answer is: {}", answer);
}
所有权和生命周期
所有权让 Rust 无需 GC
内存管理的方式
- 垃圾回收机制,不断寻找不再使用的内存,例如 Java
- 开发者写明内存分配和释放,例如 C
- Rust:所有权机制
所有权规则
- 每一个值都有一个 owner (变量)
- 值在任一时刻有且只有一个所有者。
- 当owner(变量)离开作用域,这个值将被丢弃。
堆栈
- 栈 Stack:
- 存的数据大小固定
- 后进先出,新数据总在栈顶
- 不需要寻址
- 堆 Heap
- 堆内存上开辟地址,标记为已用,然后返回该地址的指针
- 访问速度比Stack慢,因为经过了一次指针
clone和copy
clone()
拷贝堆和栈上的数据,特别消耗资源copy()
只拷贝栈上的数据,需要类型实现Copy Trait
作用域
{
let a = 1;
}
// a 的作用域结束,这里失效了
println!("{}", a); //所以报错
let x = 5;
let y = x; // 这里发生了 Copy,所以可以正常运行
println!("{}{}", x, y);
// 但是这样就会报错,因为 s1 的数据被“移动”到s2了
let s1 = String::from("hello");
let s2 = s1;
println!("{}{}", s1, s2);
// 改成这样就可以了
let s1 = String::from("hello");
let s2 = s1.clone();
println!("{}{}", s1, s2);
如果类型实现了 copy trait,那么就可以用等号。哪些实现了 copy trait 呢:
- 所有的整型、浮点型,例如
u32, f64
- bool 类型
- 字符类型
char
- 元组,当且仅当其包含的类型也都实现 Copy 的时候。比如,
(i32, i32)
实现了 Copy,但(i32, String)
就没有。
哪些没有?
- String
- struct
- 其它
所有权与函数
向函数传值的时候,等同于赋值,所以规则是同上的
fn main() {
let s = String::from("hello");
takes_ownership(s);
// println!("{}", s); // 所有权已经没了,会报错
let x = 5;
makes_copy(x); // 因为是 copy 的,所以所有权还在
println!("{}", x); // 不会报错
}
fn takes_ownership(some_string: String) {
// 运行结束,some_string 调用了 drop 方法,内存释放
}
fn makes_copy(some_integer: i32) {
// 这里的some_integer 也会调用 drop 方法,但它是 copy 来的,对之前的值没有影响
}
另外,函数return一个值的时候,所有权转移出去了(因此不会drop掉)
(代码略)
- 但不要 return 它的引用,因为对应的值会drop。进而导致这个引用指向一个无效的内存。Rust 编译器不通过
借用
上面这种情况,把一个 String 传入函数后,就失去所有权了。但是x1 往往不希望这种情况发生。所以有了借用的概念
fn main() {
let s1 = String::from("hello");
// &s1 创建了一个指向 s1 的引用,但没有其所有权。
// 函数可以使用 s1,但不获取所有权
let len = calculate_length(&s1);
println!("The length of '{}' is {}.", s1, len);
}
fn calculate_length(s: &String) -> usize {
// 因为没有所有权,所以不能更改。例如,不能 s.push_str("aaaa");
s.len()
// 因为没有所有权,所以函数结束后不触发 drop
}
但是x2 有时候还想让它在函数中可变,这就需要使用 可变借用
fn main() {
// 1. s1 要加 mut,因为 s1 是要改动的
let mut s1 = String::from("hello");
// 2. &mut 表示可变引用
let len = calculate_length(&mut s1);
println!("The length of '{}' is {}.", s1, len);
// s1 变成了 'helloaaaa'
}
// 3. 声明函数的时候,也要明确加 &mut
fn calculate_length(s: &mut String) -> usize {
// 就可以改 s 了
s.push_str("aaaa");
s.len()
}
但是x3,同一个值,同一时间,只能有一个可变引用。这是为了防止数据竞争(data race),它会导致程序结果不可知。
- 一个引用的作用域:从声明开始,到最后一次使用结束
- 可变引用和可变引用之间不能有生命周期重叠
- 不可变引用和不可变引用之间可以有重叠
- 可变引用和不可变引用不能有生命周期重叠
- 总结来说,可变引用不能和任何引用重叠
let mut s = String::from("hello");
let r1 = &mut s;
let r2 = &mut s;
println!("{}, {}", r1, r2); // 报错,因为生命周期重叠了。
// 不可变引用的最后一次使用后,又声明了可变引用,就没有问题
let mut s = String::from("hello");
let r1 = &s; // 没问题
let r2 = &s; // 没问题
println!("{} and {}", r1, r2);
// 此位置之后 r1 和 r2 不再使用
let r3 = &mut s; // 没问题
println!("{}", r3);
slice
let mut s = String::from("hello world");
let s1_1: &str = &s[3..s.len()];
let s1_2 = &s[3..];
let mut s2_1 = &s[..];
println!("{},{},{}", s1_1, s1_2, s2_1);
- 切片等同指针
- 字符串slice相当于不可变引用。因此生命周期按照不可变引用来理解
- 字符串的字面值
&str
,就是一个 slice
关于所有权的其它知识点
- 引用struct后,可以直接用
r.field
,等价于(*r).field
- 在做比较时,也无需解引用
r1<=r2;r1==r2
,等价于 ` (rx) == (ry)` - 但是
std::ptr::eq(rx, ry)
是判断引用是否指向同一块内存 - Rust的引用不可能为空
a.as_ref()
let a=&mut s;
let ref mut a = s;
所有权与 Vec
// 获取一个元素的引用,等同于获取整个 vec 的引用
let mut a: Vec<String> = vec!["1".to_string(), "2".to_string(), "3".to_string(), "4".to_string()];
let mut a1 = &mut a[1]; // 获取 整个 a 的可变引用
let mut a2 = &mut a[2]; // 又获取 整个 a 的可变引用
a1.push('c');
a2.push('c'); // 生命周期重叠,编译不通过
一些分离所有权的方法
let (left, right) = a.split_at_mut(idx); // 所有权分为两片,left 为 [0, mid),right 为 [mid ,len)
let item = a.remove(idx); // 把 item 拿出来,分离所有权,然后 shift, O(n) 复杂度
let item = a.swap_remove(idx); // 把 item 拿出来,分离所有权,然后把最后一个缓过来,O(1) 复杂度
use std::mem;
let a2 = mem::replace(&mut a[2], "9".to_string()); // 把 a[2] 拿出来,分离所有权,原位置放入 "9".to_string()
生命周期
// 约定:输入的生命周期>=输出的生命周期
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() {
return x;
}
return y;
}
// late bound / early bound
// 'a: 'c 是指定 a 的生命周期比 c 长
fn longest<'a: 'c, 'b: 'c, 'c>(x: &'a str, y: &'b str) -> &'c str {
if x.len() > y.len() {
return x;
}
return y;
}
早限定和晚限定
fn f<'a>(inp:&'a str)->&'a str{inp} // late bound,实际调用时,才能确定具体的生命周期
fn g<'a:'a>(){} // early bound
// late bound,使用时候必须指定生命周期,否则报错
// let pf=f::<'static> as fn(); // 这个报错
let pf=f as fn(&'static str)->&'static str;
// early bound 则不用
let pg=g::<'static> as fn();
let pg=g as fn();
// println!("{}",pf==pg);
其它规则
- 如果单入参,出参的生命周期与入参一样
- 如果多入参,出参生命周期无法推断,需要手动定义
- 如果是类方法,出参生命周期与self一致
静态变量
可变静态变量
static 和 const 是不同的
static mut COUNTER: u32 = 0;
fn add_one() {
unsafe {
COUNTER += 1;
}
}
fn main() {
add_one();
// 读和写都必须在 unsafe 中
unsafe {
println!("{}", COUNTER);
}
}
区别
- const :存储的是数据本身。编译时直接内连,每次使用时重新分配栈内存,内存地址会变化。
- static:存储的是内存地址。全局只分配一次内存,内存地址固定。
以下情况使用 static
- 存储大量数据
- 固定地址
- 要求内部可变性
使用时 static 比 const 多了一次寻址操作
lazy_static
// lazy_static = "*"
#[macro_use]
extern crate lazy_static;
// use lazy_static;
use std::collections::HashMap;
lazy_static! {
static ref PRIVILEGES: HashMap<&'static str, Vec<&'static str>> = {
println!("生成静态变量");
let mut map = HashMap::new();
map.insert("James", vec!["user", "admin"]);
map.insert("Jim", vec!["user"]);
map
};
}
fn show_access(name: &str) {
let access = PRIVILEGES.get(name);
println!("{}: {:?}", name, access);
}
fn main() {
println!("开始运行");
let access = PRIVILEGES.get("James");
println!("James: {:?}", access);
show_access("Jim");
}
智能指针
最简单的指针就是引用,它没有额外开销
智能指针是一些数据结构,类似指针但有额外的元数据和功能。
- Box 用于在堆上分配值
- Rc 允许多重所有权引用计数类型
- Ref和RefMut 可以通过 RefCell 是运行时执行借用的类型,而不是编译时
智能指针一般有两个trait
- Deref trait:使智能指针结构体的实力拥有与引用一致的行为
- Drop trait:指向的数据可以自动被释放掉
Box
Box
- 数据在堆上,元数据在栈上
- 递归类型无法在编译时确定具体大小,因此需要用 Box
生成和释放
- 底层使用类似
malloc
和free
这样底层的内存分配器来分配和释放 - 生成:先在堆上分配数据,然后从栈上复制到堆上,返回一个
Box<T>
对象,其元数据存放在栈上 - 释放:
- 时机:实例超出其作用域时(例如函数的结尾,或者该变量不再被使用)
- 调用drop函数:先释放栈,后释放堆
let b = Box::new(5);
println!("b = {}", b)
// 这个代码出现递归,因此无法通过编译
// enum List {
// Cons(i32, List),
// Nil,
// }
// 改成这样
enum List {
Cons(i32, Box<List>),
Nil,
}
// 需要空间是i32和一个Box对应的指针,就打破了无限递归
use crate::List::{Cons, Nil};
fn main() {
let list = Cons(1, Box::new(
Cons(2, Box::new(
Cons(3, Box::new(Nil))))));
}
Rc
Rc
- 堆上的数据
- 多重多有权
- rc 通过不可变引用共享只读数据
- RefCell 与 Rc 联合使用可以绕开不可变的限制
名字 | 持有者数量 | 可变性 | 线程 |
---|---|---|---|
Box | 1个 | 编译时检查可变+不可变 | |
Rc | 多个 | 编译时检查不可变 | 只能用于单线程 |
RefCell | 1个 | 运行时检查可变和不可变 | 只能用于单线程 |
一个带叉链表
use std::rc::Rc;
#[derive(PartialEq, Eq, Clone, Debug)]
pub struct ListNode {
pub val: i32,
pub next: Option<Rc<ListNode>>,
}
fn main() {
let a = Rc::new(ListNode { val: 1, next: Some(Rc::new(ListNode { val: 2, next: None })) });
println!("引用计数数量 = {}", Rc::strong_count(&a));
let b = ListNode { val: 6, next: Some(Rc::clone(&a)) };
println!("引用计数数量 = {}", Rc::strong_count(&a));
let c = ListNode { val: -6, next: Some(Rc::clone(&a)) };
println!("引用计数数量 = {}", Rc::strong_count(&a));
}
待添加:RefCell
unsafe
unsafe可以实现这些能力
- 解引用裸指针。
- 调用不安全的函数或方法。
- 访问或修改可变的静态变量。
- 实现不安全trait。
裸指针与引用、智能指针的区别在于:
- 允许忽略借用规则,可以同时拥有指向同一个内存地址的可变和不可变指针,或者拥有指向同一个地址的多个可变指针。
- 不能保证自己总是指向了有效的内存地址。•
- 允许为空。
- 没有实现任何自动清理机制。
裸指针
示例
let mut num = 5;
// 不可变裸指针
let r1 = &num as *const i32;
// 可变裸指针
let r2 = &mut num as *mut i32;
// 裸指针只能在 unsafe 里面引用
unsafe {
// 生命周期重叠
println!("r1 = {}", *r1);
println!("r2 = {}", *r2);
}
这段代码试图用一个随意的内存地址来创建拥有10 000个元素的切片。
use std::slice;
let address = 0x01234usize;
let r = address as *mut i32;
let slice : &[i32] = unsafe {
slice::from_raw_parts_mut(r, 10000)
};
调用别的语言时,无法检查其它语言的安全性,所以也要用 unsafe
extern "C" {
fn abs(input: i32) -> i32;
}
unsafe {
println!("Absolute value of -3 according to C: {}", abs(-3));
}
其它
- 高级 trait:https://weread.qq.com/web/reader/d733256071eeeed9d7322fdk07e323f027707e1cd7dc674
- 高级类型:https://weread.qq.com/web/reader/d733256071eeeed9d7322fdkda432420278da4fb5c6e9ad
- 高级函数和闭包:https://weread.qq.com/web/reader/d733256071eeeed9d7322fdk4c5327a02794c56ff4ce24c
- 宏:https://weread.qq.com/web/reader/d733256071eeeed9d7322fdka0a32dd027aa0a080f42962
参考
https://rustwiki.org/zh-CN/book/ch03-05-control-flow.html
系列文章:https://blog.csdn.net/feiyanaffection?type=blog