Rust 小记
编辑
22
2024-09-18

Rust
语言特性
- 无 GC,内存在占用它的变量离开作用域后被自动释放(隐式地调用
Target.drop()
) - 编译器保证永远不会产生空指针和悬垂引用
- 支持自动引用与解引用,无需使用
->
运算符 - 使用 Trait 和 Trait Bound 指定泛型类型的行为,在编译时即检查行为
基础语句与表达式
let
声明不可变变量(一次赋值),mut
使变量可变,变量名称可以通过 Shadowing 复用const
声明常量- 注意变量在作用域
{}
内外的变化 - 元组
tup: (type, type...)
,解构赋值(t1, t2...) = tup
,使用.index
访问(1,2,3).0 == 1
- 数组
arr: [type; count] = [v1,v2...]
,使用[index]
访问 - 函数
fn name(p1: type1, p2: type2...) -> Type { body }
- 多数函数隐式返回最后一个表达式,可省略显式的 return 语句
- 多数块都被视为表达式,表达式的结尾没有分号,作用域同样被视为表达式
- 分支
if condition { s1 } else { s2 }
为表达式,可以在condition
中使用let
语句 - 无限循环
'name: loop { body }
可使用前导单引号具名,配合break 'name
- 条件循环
while condition { body }
- for 循环
for item in Collection || (L..R) { body }
,遍历集合和 Range
规范
- 函数和变量命名遵循
snake_case
- 使用
//
创建注释 - 使用
dbg!
宏来以 Debug 格式输出信息
所有权 Ownership
规则
变量的所有权总是遵循相同的模式:将值赋给另一个变量时移动它。当持有堆中数据值的变量离开作用域时,其值将通过
drop()
被清理掉,除非数据被移动为另一个变量所有。
- 每个值在任一时刻都有且只有一个所有者
- 当所有者离开作用域,该值将被销毁
移动 Move
let old = String::from("test");
let new = old; // 此时old被销毁,不可访问
对于一个存储于堆栈上的数据,例如 String
(非字面量),任意一个变量 old 被拷贝到新变量 new 中后,old 将被销毁,由此防止指向同一堆内存的指针被二次释放造成内存污染。
因而在 Rust 中没有浅拷贝和深拷贝的概念,本质为:move(old) -> new
克隆 Clone
let old = String::from("test");
let new = old.clone(); // 创建了old的拷贝,深度复制了堆上的数据,此时old可访问
复制 Copy
对于只在栈上的数据(比如整型),其值将会在作用域内保持有效性,而不会被移动。 // Trait
向函数传递参数导致的所有权交换
向函数参数传递值可能会产生移动(堆栈)或者复制(栈)。在调用时要注意作用域和数据类型。
fn main() {
let s = String::from("hello"); // 堆栈量
takes_ownership(s);
let x = 5; // 栈量
makes_copy(x);
// 此时 s 不可访问,而 x 可访问
fn takes_ownership(some_string: String) {
println!("{}", some_string);
} // 移动,s -> some_string 占用的内存被释放
fn makes_copy(some_integer: i32) {
println!("{}", some_integer);
} // 复制,无事发生
函数返回值导致的所有权交换
同样的,函数的返回值同样导致所有权交换。
fn main() {
let s1 = g1(); // is1(g1) -> s1
let s2 = String::from("hello");
let s3 = g2(s2); // s2 -> is2(g2) -> s3
} // 此时 s1, s2, s3 均被销毁
fn g1() -> String {
let is1 = String::from("yours");
is1
}
fn g2(is2: String) -> String {
is2
}
引用与借用 Ref & Borrow
如果只使用变量的值,而不交换所有权,需要使用引用 & 。引用类似于指向某一地址的指针,但引用确保所指向的地址存在有效值。创建一个引用的行为,称为借用。
在默认情况下,引用的值是不可变的。可以使用 &mut
运算符创建可变引用。
注意:在任意给定时间,要么只能有一个可变引用,要么只能有多个不可变引用。
切片与部分引用 Slice
使用切片运算符 []
可以创建针对于字符串和数组等类型的部分引用。
结构体 Struct
- 定义:
struct name { field: value... }
。 - 更新:
let s1 = S{..s0};
,注意:更新结构体时同样产生了数据交换,因此仍然适用于所有权交换规则。 - 元组结构体:
struct name(type1, type2, type3...);
- 类单元结构体:
struct name;
主要用于在某个类型上实现 Trait。 - 在结构体中存储一个引用,需要指定其生命周期,以确保数据的有效性。
方法语法 Method & Impl
- 在结构体、枚举和 Trait 对象上下文中定义的函数,被称为方法。其第一个参数总为
self
。 - 使用
impl Struct { fn... }
创建方法定义块,其中定义的函数(不以self
为第一个参数的函数)被称为关联函数。 impl
方法定义块可以存在多个。
枚举 Enum
- 定义:
enum name{ E1(type), E2(type), E3(type)... }
- 调用枚举值:
Name::E1(value)
模式匹配表达式 Match
- 使用 match 运算符进行模式匹配:
match value { pattern1 => result1 ... }
- 匹配
Option<T>
:None => {} / Some(x) => {x}
- 匹配是穷尽的,因此必须要覆盖所有分支。
- match 的
other
作为通用分支,作为未匹配到值时的 fallback 分支。 - match 的
_
分支作为不想使用以上分支值的时候执行的语句
集合类型 Collection
Vector
let v: Vec<i32> = Vec::new();
let v = vec![1,2,3]; // 使用 vec! 宏
- Vector 的数据存储在堆上
- 销毁 Vector 时也会销毁其所有元素。
String
let s = "123"; // &str
let mut s = String::new();
let s = String::from("123")
let s = "123".to_string();
- String 本质上为 Vec
的封装,默认以 UTF-8 编码 - String 不支持索引
- 使用
+
运算符或!format
宏拼接字符串:let s = format!("{s1}-{s2}")
HashMap
use std::collections::HashMap
let mut c = HashMap::new()
c.insert(String::from("key"), value) // 插入
c.get("key").copied().unwrap_or(0) // 访问
- HashMap 的数据存储在堆上
- 对于拥有所有权的值,其所有权将被转移到 HashMap 上。
错误处理 Error Handle
panic!("reason") // 通过宏引发 panic
let result = match process1 { // 通过 Result 套娃处理错误
Ok(x) => x,
Err(e1) => match e1.kind(){
ErrorKind::NotFound => match process2 {
Ok(y) => y,
Err(e2) => panic!("~");
}
}
}
let mut file = File::open("test.txt")?; // 通过?运算符传递错误
-
使用
panic
处理不可恢复的错误 -
使用
Result<T, E> { Ok(T), Err(E) }
处理可恢复的错误Result.unwarp()
:若Result
为Err(~)
则调用panic!
Result.expect(msg)
:若Result
为Err(~)
则调用panic!(msg)
-
使用 ? 运算符传播错误,可以使用链式方法调用,但只能在返回
Result
或者其它实现了FromResidual
的类型的函数中使用?
运算符。
泛型 Generics
- 通过
<T>
定义,可用于函数、结构体、枚举和方法定义。 - 因为 Rust 编译器会对泛型进行单态化,所以不会产生额外开销。
特征 Trait
// 定义 trait
(pub) trait Trait{
fn function(&self) -> String;
}
// 为结构体实现 trait
impl Trait for Struct {
fn function(&self) -> String {
String::from("method body");
}
}
// trait 作为参数
pub fn test(item: &impl Trait) {
println!("{}", item.function());
}
// 等同于 trait bound 写法
pub fn test<T: Trait>(item: &T){
println!("{}", item.function());
}
// 使用 where 的 trait bound
fn func<T, U>(t: &T, u: &U) -> i32 where T: Display + Clone, U: Clone + Debug {...}
// 返回实现了 trait 的类型
fn func() -> impl Trait {...}
- Trait 定义与其他类型共享的功能,类似于
interface
,是一种将方法签名组合起来的方法。 - 若为 Trait 中的方法提供方法体,则为该方法的默认实现,默认实现可以被重载。
生命周期 Lifecycle
&i32 // 引用
&'a i32 // 显式生命周期引用
&'a mut i32 // 显式生命周期可变引用
// 函数中的生命周期注解
fun func<'a>(x: &'a str, t: &'a str) -> &'a str { if x.len()>y.len*() x else y }
// 结构体定义中的生命周期注解
struct Struct<'a> {
part: &'a str
}
// 方法定义中的生命周期注解
impl<'a> Struct<'a> {...}
// 静态生命周期
let s: &'static str = "...";
- 每一个引用都有其生命周期,即引用保持有效的作用域。
- 生命周期的主要目标是避免悬垂引用。
- 生命周期注解描述多个引用生命周期的相互关系,不改变生命周期长短,也不影响其生命周期。
- 0
- 0
-
分享