Rust 小记
编辑
              
              185
            
            
          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
- 
              
              
  分享
