Rust rust 快速入门

runtoweb3 · 2024年08月20日 · 52 次阅读

Rust 快速入门

工程管理工具-cargo
cargo new world_hello   # 创建rust工程
cd world_hello 
cargo run # 运行项目,会自动编译,运行
cargo build # 编译项目,生成target目录,会从Cargo.toml的dependency获取依赖,从create.io下载依赖
./target/debug/test1  # 运行编译好的命令
cargo build --release # release编译,会优化代码
./target/release/test1 # 运行
打印数据

更多格式化输出,https://course.rs/basic/formatted-output.html#debug-%E7%89%B9%E5%BE%81

使用println!宏来打印这些数据。花括号{}用于占位符,将变量的值依次填充进去。

有些类型没有实现 std::fmt::Display,就要用{:?}占位符

fn main() {
    let name = "Alice";
    let age = 30;
    let height = 1.65;
    println!("Name: {}, Age: {}, Height: {}", name, age, height);
}

打印结构体,需要在定义 struct 加上#[derive(Debug)]

#[derive(Debug)]
struct User {
    active: bool,
    username: String,
    id: u64,
}
fn main() {
    let user1 = User{
        active:true,
        username:String::from("aaa"),
        id:1
    };
    print!("{:?} \n", user1);
}
语句和表达式

语句会执行一些操作但是不会返回一个值,需要分号结尾;

表达式会进行求值,然后返回一个值,不能有分号;

调用一个函数,调用宏,调用{}语句块,都是一个表达式,会有返回值

let x = x + 1; // 语句
let y = y + 5; // 语句
x + y // 表达式

数据类型和赋值

基本数据类型

所有整数类型比如 u32
布尔类型bool它的值是 true  false
所有浮点数类型比如 f64
字符类型char
元组当且仅当其包含的类型也都是 Copy 的时候比如(i32, i32)  Copy  (i32, String) 就不是
不可变引用 &T 例如转移所有权中的最后一个例子但是注意: 可变引用 &mut T 是不可以 Copy的

复合类型

字符串与切片
元祖
结构体
枚举
数组

Rust 基本类型都是通过自动拷贝的方式来赋值的。

Rust 复杂类型的赋值,可以称为所有权转移或者移动 (move),就像是浅拷贝,同时让第一个变量失效

let s1 = String::from("hello");
let s2 = s1; // 移动,

println!("{}, world!", s1); // 编译错误,use of moved value: `s1`
println!("{}, world!", s2);
  1. Rust 中每一个值都被一个变量所拥有,该变量被称为值的所有者
  2. 一个值同时只能被一个变量所拥有,或者说一个值只能拥有一个所有者
  3. 当所有者 (变量) 离开作用域范围时,这个值将被丢弃 (drop)
克隆 (深拷贝)

Rust 永远也不会自动创建数据的“深拷贝”,如果需要用到深拷贝,就调用 clone()

函数的传值与返回

和变量赋值一样,函数传参也会出现 move(复杂类型)或者 copy(基本类型)的现象

let s1 = String::from("hello");
show(s1);
print!("{}\n",s1);// 编译错误,use of moved value: `s1`
引用与解引用(引用通常也叫借用)

常规引用是一个指针类型,指向了对象存储的内存地址。

fn main() {
    let s1 = String::from("hello");
    show(&s1); // 参数是一个引用,并没有发生转移
    print!("{}\n",s1);
}
fn show(s:&String){
    print!("{}\n",s.len());
}

可变引用,可修改引用的值

fn main() {
    let mut s1 = String::from("hello"); // 声明一个可变变量
    add_str(&mut s1); // 可变引用
    print!("{}\n",s1);
}
fn add_str(s:&mut String){
    s.push_str(" world"); // 只有可变引用,才能修改引用的值
    print!("{}\n",s.len());
}

引用的特点

1.同一时刻你只能拥有要么一个可变引用, 要么任意多个不可变引用
2.引用必须总是有效的(变量在离开作用域后就自动释放其占用的内存所以不能像另外一个作用域返回引用本作用域的变量)

复合类型

字符串与切片(与 go 的切片很不一样)

切片允许你引用集合中部分连续的元素序列,而不是引用整个集合,切片本质也是引用。rust 有两种切片,分别是字符串切片&str,数组切片

切片只有两个字段

  1. ptr:这是一个指向切片首元素的原始指针。它的类型是 *const T
  2. len:这是切片的长度。它的类型是 usize

特点:切片只是对数据的部分引用,而且长度固定,可以通过切片获取新的切片。

let s1 = String::from("hello");
let s2: &str = &s1[1..3]; // 字符串切片的类型标识是&str, s2是一个切片,切片对象包含指针和len,指针指向第二个元素,长度是2
print!("{} \n",s2) // el

字符串字面量也是切片

let s: &str = "hello";//编译器在编译的时候直接将字符串字面量以硬编码的方式写入程序的二进制文件中,当程序被加载时,字符串字面量保存中Read Only Memory 字段中。如果有两个相同的字面量,他们的地址相同

String

Rust 在语言级别,只有一种字符串类型: str,它通常是以引用类型出现 &str,但是在标准库里,还有多种不同用途的字符串类型,其中使用最广的即是 String 类型。str 类型是硬编码进可执行文件,也无法被修改,但是 String 则是一个可增长、可改变且具有所有权的 UTF-8 编码字符串,String 类型是变长的,所以需要在堆上分配。

  1. pointer :heap 中值的内存地址
  2. length :当前值的长度、当前元素个数。
  3. capacity :当前缓冲区的容量,可以容纳元素的个数,当前字符串的长度超过当前分配的capacity 会重新分配内存,会将当前字符串拷贝到新分配的内存中。

String 与&str 相互转换

// 字符串切片转String
let s = String::from("hello,world");
let s1 = "hello,world".to_string();// 当我们调用 &str 的 to_string 方法时,实际上就是创建一个新的 String 对象,其内容是 &str 的深拷贝。
// String转&str
let s = String::from("hello,world");
print!("String={}\n", s);
print!("&str={} \n", &s); // 所有元素
print!("&str={} \n", &s[1..3]); // 只要下标1-2的元素
元组

元组是由多种类型组合到一起形成的,元组的长度是固定的,元组中元素的顺序也是固定的。

fn main() {
    let s1 = String::from("hello");
    let (s2, len) = calculate_length(&s1); // 使用模式匹配解构元组
    let res = calculate_length(&s1); // 使用.下标来访问
    println!("The length of '{}' is {} \n", s2, len);
    print!("The length of '{}' is {} \n",res.0, res.1);
}

fn calculate_length(s: &str) -> (&str, usize) { // 函数返回一个元组
    let length = s.len(); 
    (s, length)
}
结构体-struct

由多个类型组合在一起,有结构体名称,有字段

如果要修改结构体字段,必须声明为可变类型

实例化结构体,必须为每个字段赋值(不然编译报错)

struct User {
    active: bool,
    username: String,
    id: u64,
}
fn main() {
    let mut user1 = User{ // 实例化结构体,必须为每个字段赋值
        active:true,
        username:String::from("aaa"),
        id:1
    };
    user1.username = String::from("bbb"); // 修改结构体
    print!("{},{},{} \n", user1.active, user1.username, user1.id);
}

结构体所有权

1.如果是整个 struct 发生 move,则 user1 不能再使用

let user1 = User{
    active:true,
    username:String::from("aaa"),
    id:1
};
let user2 = user1;
print!("{},{},{} \n", user1.active, user1.username, user1.id); // 报错

2.如果只是 struct 某个字段发生 move, user1 除了发生 move 的字段不能使用,其他字段还可以使用

let user1 = User{
    active:true,
    username:String::from("aaa"),
    id:1
};
let user3 = User{
    active:user1.active,
    username:user1.username, // 发生move
    id:user1.id
};
print!("{},{} \n", user1.active,user1.id); // 正常

3.结构体字段如果需要使用引用类型,就必须加上生命周期,否则就会报错。

Method-面向对象编程

通过 struct 和 impl 来实现

有两种方法,一种是函数没有 self,成为关联函数,一种是有 self,成为 method

pub struct Brc20 {
    pub name: String,
}
impl Brc20 {
    pub fn new(name: String ) -> Brc20 { // 这个函数没有self, 只能通过Brc20::new()调用,称为关联函数
        Brc20 {
            name
        }
    }
    pub fn process(self) -> String { // 直接通过实例brc20.process()调用
        let mut result = String::new();
        result += &self.name;
        result
    }
}
fn main() {
        let handler = Brc20::new("aaa".to_string());
    let res = handler.process();
    print!("res:{}", res)
}

self,&self, &mut self

  • self 表示 Rectangle 的所有权转移到该方法中,这种形式用的较少
  • &self 表示该方法对 Rectangle 的不可变借用
  • &mut self 表示可变借用,如果要修改对象属性,用这个
枚举-enum

枚举类型是一个类型,它会包含所有可能的枚举成员,而枚举值是该类型中的具体某个成员,类似 C 的 Union 类型,里面的成员可以是不同的类型。

struct A {
    number: u8,
}
enum PokerCard {
    Clubs(u8), // 关联一个u8
    Spades, // 不关联任何值
    Diamonds { // 关联一个匿名struct
        number: u8, 
        suit: String,
    },
    Hearts(A), // 关联一个struct
}
fn main() {
    let c1 = PokerCard::Spades;
    let c2 = PokerCard::Diamonds { number: 8, suit: String::from("Diamonds")};
    let c3 = PokerCard::Hearts(A { number: 7 });
    let c4 = PokerCard::Clubs(9);
    print_suit(c1);
    print_suit(c2);
    print_suit(c3);
    print_suit(c4);
}
fn print_suit(p: PokerCard) { // 打印枚举变量
    match p {
        PokerCard::Clubs(value) => println!("Clubs: {}", value),
        PokerCard::Spades => println!("Spades"),
        PokerCard::Diamonds {number, suit} => println!("Diamonds: {} of {}", number, suit),
        PokerCard::Hearts(A { number }) => println!("Hearts: {}", number),  // CORRECT CODE
    }
}
数组

在 Rust 中,最常用的数组有两种,第一种是速度很快但是长度固定的 array,第二种是可动态增长的但是有性能损耗的 Vector

array

  • 长度固定
  • 元素必须有相同的类型
  • 依次线性排列
let arr1:[u8,3] = [1,2,3];
let arr2 = ["hello", "world"]

println!("{:?}",arr1); // 1,2,3
println!("{:?}",arr2); // 0,0,0

数组切片

let arr1: [i32; 3] = [1,2,3]; // 数组类型[T,len]
let s1: &[i32] = &arr1[1..2]; // 数组切片&[T]
println!("{:?}",arr1);// [1,2,3]
println!("{:?}",s1);// [2]
  • 数组类型容易跟数组切片混淆,[T;n] 描述了一个数组的类型,而 [T] 描述了切片的类型,因为切片是运行期的数据结构,它的长度无法在编译期得知,因此不能用 [T;n] 的形式去描述
  • [u8; 3][u8; 4]是不同的类型,数组的长度也是类型的一部分
  • 在实际开发中,使用最多的是数组切片 [T],我们往往通过引用的方式去使用&[T],因为后者有固定的类型大小
动态数组-Vector

Vector,HashMap 都是标准库封装的类型,它们底层的数据都存储在内存堆上,然后通过一个存储在栈中的引用类型来访问,可以动态扩展

跟结构体一样,Vector 类型在超出作用域范围后,会被自动删除,内部的元素也会被删除

let mut arr1 = Vec::new(); // 实例化vector对象,
let mut arr2 = vec![1,2,4]; // 使用宏实例化对象,并且同时初始化元素
arr2.push(3) // 添加元素

// 读取元素,下标或者.get()
fn main() {
    let arr2 = vec![1,2,4]; // 使用宏实例化对象,并且同时初始化元素
    let arr3 = vec!["welcome","hello"];
    let first = arr2[0];
    let first_s = arr3[0];
        match arr2.get(1) { // 通过get()访问,不会越界报错
        Some(k) => println!("第二个元素是 {k}"),
        None => println!("去你的第二个元素,根本没有!"),
    }
}
vector 元素的所有权

如果 Vector 的元素类型是复杂类型,不能直接 arr[0] 来赋值

let mut arr1 = vec![String::from("hello"), String::from("world")];
let a = arr1[0]; // 会编译错误,有两种解决1.使用引用 2.通过remove(),pop发生所有权转移
let a = arr1.remove(0);// 会把元素所有权转移给a,同时元素会移动填充0下标,元素多的时候会消耗性能,vec长度也会变化
let b = &arr1[1];// 直接引用, 如果需要得到String, b.to_string()会生成新的Stringd对象
KV 存储 HashMap

它们底层的数据都存储在内存堆上,然后通过一个存储在栈中的引用类型来访问。

泛型

函数中使用泛型
fn add<T>(a:T, b:T)->T{ 
    a+b  // 编译会报错,因为不是所有的类型都能相加,需要使用trait对T进行限制,我们称之为特征约束
}
结构体中使用泛型
struct Point<T> {
    x: T,
    y: T,
}
fn main() {
    let integer = Point { x: 5, y: 10 }; // 自动识别T为int
    let float = Point { x: 1.0, y: 4.0 };// 自动识别T为float
}
枚举中使用泛型
enum Result<T, E> {
    Ok(T),
    Err(E),
}
fn divide(numerator: f64, denominator: f64) -> Result<f64, &'static str> { // 返回值是一个泛型
    if denominator == 0.0 {
        Err("Cannot divide by zero")
    } else {
        Ok(numerator / denominator)
    }
}
方法中使用泛型
struct Point<T> {
    x: T,
    y: T,
}
impl<T> Point<T> {
    fn x(&self) -> &T {
        &self.x
    }
}
fn main() {
    let p = Point { x: 5, y: 10 };
    println!("p.x = {}", p.x());
}

泛型的性能

Rust 是在编译期为泛型对应的多个类型,生成各自的代码,(相当于编译器帮你写了多份代码),因此损失了编译速度和增大了最终生成文件的大小,但是对性能不影响。

宏(macro)

声明宏

匹配对应模式然后以另一部分代码替换当前代码

let v: Vec<u32> = vec![1, 2, 3]; // vec! 是一个声明宏
过程宏

过程宏接收 Rust 代码作为输入,在这些代码上进行操作,然后产生另一些代码作为输出


自定义 derive 宏

特征 Trait

类似于面向对象的 interface{},定义了一组可以被共享的行为,只要实现了特征,你就能使用这组行为

如果不同的类型具有相同的行为,那么我们就可以定义一个特征,然后为这些类型实现该特征。

定义 Trait

pub trait Person {
    fn say(&self)->String;
}

Trait 的关联类型

trait 除了可以定义方法,还可以定义类型,实现 Trait 特征时,也需要实现关联类型,像泛型的 T,需要指明类型

pub struct Puppy;
trait Animal {
    type Baby; // 关联类型
    fn have_baby(&self) ->Self::Baby;
}
impl Animal for Dog {
    type Baby = Puppy; // 确认关联类型
    fn have_baby(&self) -> Self::Baby {
        println!("A puppy is born.");
        Puppy
    }
}

实现 Trait

struct User {
    id:i32,
    name:String,
}
// 实现trait
impl Person for User {
    fn say(&self)->String{
        format!("id is {}, name is {}",self.id, self.name)
    }
}
trait 的孤儿规则

如果你想要为类型 A 实现特征 T,那么 A 或者 T 至少有一个是在当前作用域中定义的!

trait 的默认实现

定义具有默认实现的方法,这样其它类型无需再实现该方法,或者也可以选择重载该方法

pub trait Person {
    fn author(&self)->String;
    fn say(&self) { // 有具体的实现,其他类型可以不实现或者重载这个方法
        print!("{} is saying", self.author());
    }
}

trait 作为参数使用

fn notify(item: &impl Person) { // 参数是trait, 写法就是 impl trait
    item.say();
}
fn notify2(item: &(impl Person+Display) { // 多重约束,参数必须实现Person和Display特征
    item.say();
}
fn main() {
    let u1 = User{name:String::from("aa")};
    notify(&u1)
}
特征约束 (trait bound)

trait 作为参数使用,我们使用 impl trait 其实是一个语法糖,本质是这样:

fn notify3<T: Person>(user:&T){ // 对T类型,必须实现Person特征进行限制,T: Person就是特征约束
    user.say();
}

多重约束

fn notify3<T: Person+Display>(user:&T){ // 对T类型,必须实现Person特征进行限制,T: Person就是特征约束
    user.say();
}
函数返回中的 impl Trait

可以通过 impl Trait 来说明一个函数返回了一个类型,该类型实现了某个特征。

但是这种写法有个缺点,只可以返回一种具体的类型,这种类型实现了 Person trait

fn createp(n:&str)->impl Person{
    User{
        name:String::from(n)
    }
}
fn main() {
    let u1 = createp("aa");
    let u2 = createp("bb");
}
Trait 对象

上面 User 实现了 Person, 如果又有一个 Child 实现了 Person.

Trait 对象指向实现了 Person 特征的类型的实例,也就是指向了 User 或者 Child 的实例,这种映射关系是存储在一张表中,可以在运行时通过特征对象找到具体调用的类型方法。

特征对象:Box,当成一个引用即可,只不过它包裹的值会被强制分配在堆上。

fn createp(n:&str, b:bool) -> Box<dyn Person>{ // 返回一个特征对象(类似智能指针,当做一个引用即可)
    if b {
        Box::new(User{
            name:String::from(n)
        })
    }else{
        Box::new(Child{ // 通过Box::new()创建特征对象
            name:String::from(n),
            age:1
        })
    }
}
fn notify(item: Box<dyn Person>) { // 参数是特征对象
    item.say();
}
fn main() {
    let u1 = createp("aa",true); 
    let u2 = createp("bb",false);
    notify(u1);
    notify(u2);
}

特征对象原理

泛型是在编译期完成处理的:编译器会为每一个泛型参数对应的具体类型生成一份代码,这种方式是静态分发 (static dispatch),因为是在编译期完成的,对于运行期性能完全没有任何影响。

与静态分发相对应的是动态分发 (dynamic dispatch),在这种情况下,直到运行时,才能确定需要调用什么方法。之前代码中的关键字 dyn 正是在强调这一“动态”的特点。

Box, 包含了两个指针

ptr: 指向实现了特征 Person 的具体类型的实例,比如类型 User 的实例、类型 Child 的实例

vptr指向一个虚表vtable,保存了实例对于可以调用的实现于特征 Person 的方法

trait 对象的限制

不是所有的 trait 都有 trait 对象,必须满足一定条件的 trait

  • 方法的返回类型不能是 Self
  • 方法没有任何泛型参数
通过 derive 派生特征

编译器会自动实现 Trait

#[derive(Debug)] 
struct User {
    active: bool,
    username: String,
    id: u64,
}
# 编译器自动实现
impl std::fmt::Debug for User {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        write!(f, "User {{ active: {:?}, username: {:?}, id: {:?} }}", self.active, self.username, self.id)
    }
}

生命周期

生命周期可以定义为一个引用所能持续的范围,是编译器用于预防悬垂引用(在对应的值已经被析构后仍被使用)的方式。这在 Rust 中特别重要,因为 Rust 放弃了垃圾收集器并选择手动管理内存。

大部分情况下,rust 编译器可以自动识别引用的生命周期,判断悬垂引用,但是还有一些情况需要我们使用生命周期标注来告诉编译器。

悬垂指针
{
    let r;
    {
        let x = 5;
        r = &x;
    }
    println!("r: {}", r);//r引用了内部花括号中的 x 变量,但是 x 会在内部花括号 } 处被释放,此时r就是悬垂指针
}
生命周期标注语法
&i32        // 一个引用
&'a i32     // 具有显式生命周期的引用
&'a mut i32 // 具有显式生命周期的可变引用
函数中的生命周期
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
    if x.len() > y.len() {
        x
    } else {
        y
    }
}
结构体中的生命周期

当结构体中出现引用字段时,就应该标注字段的生命周期和整个结构体的生命周期

结构体引用字段的生命周期应该大于等于结构体的生命周期

struct ImportantExcerpt<'a> {
    part: &'a str,
}
方法中的生命周期
泛型中使用生命周期
静态生命周期 &'static

&'static 对于生命周期有着非常强的要求:一个引用必须要活得跟剩下的程序一样久,才能被标注为 &'static

错误处理

panic

panic 可以是被动触发或者主动触发,如果是子线程触发,只会终止触发的那个线程,其他程序不受影响

fn main() {
    panic!("crash and burn");
}
Result 枚举用于处理函数返回
enum Result<T, E> {
    Ok(T),
    Err(E),
}

在 Rust 编程语言中,这就是一种惯例,如果一个函数可能会失败,那它应该返回Result类型而不是直接返回值。

Result是一种枚举(enum),它被广泛用于错误处理

use std::fs::File;
use std::io;
use std::io::Read;
use std::error::Error;
fn main() {
    _ = read_content();
}
fn read_content() ->Result<String, Box<dyn Error>> { // 注意Result有两个成员Ok,Err,成员可以携带数据,比如这个String就是Ok的
    let mut f = File::open("test.txt")?;
    let mut s = String::new();
    f.read_to_string(&mut s)?;
    Ok(s)
}
从 Result 获取结果或者 err
let res = handler.process(); // 返回的是Result
match res {
  Ok(value) => {
      println!("Success: {}", value);
  }
  Err(e) => {
      eprintln!("Error: {}", e);
  }
}
快速处理 Result
宏?,发生错误直接返回

当函数返回值是 Result 时,函数内可以使用宏?来快速传播 Err

fn run(config:&Config) -> Result<(), Box<dyn Error>> {
    let content = fs::read_to_string(&config.file_path)?;// 发生错误马上返回
    print!("{},{}",config.query,content);
    Ok(()) // 函数执行正确没有返回值,使用空元组 ()占位
}
unwrap,发生错误,直接 panic

Result 是枚举,要读取里面的数据,需要用 match 处理,但是也有快速的方法

use std::fs::File;

fn main() {
    let f = File::open("hello.txt").unwrap(); // 如果成功,直接返回Ok(T)关联的值,如果失败,直接panic
    let f = File::open("hello.txt").expect("Failed to open hello.txt");// 和unwarp一样,但是会打印自定义panic日志
    let f  = File::open("hello.txt").unwarp_or_else(闭包) // 成功发挥Ok关联的值,失败执行闭包,不会Panic
}
Option 枚举用于处理空值

其他语言一般用 null 处理空值,

enum Option<T> {
    Some(T),
    None,
}
let maybe_value = Some(42); // 实例化Option实例,绑定一个值
let value = maybe_value.unwrap_or_else(|| 0);// 如果是None就返回0
println!("{}", value);  // Prints 42
unwrap_or_else 处理 Result,Option

Result,Option 有一个 unwrap_or_else 方法,参数是一个闭包函数,当 Result 是 Err 或者 Option 是 None 时,会调用这个闭包

闭包 Closure

也就是匿名函数

|参数列表| -> 返回值类型 {
    // 函数体
}
let add = |x, y| -> i32 { // 有返回值
    x + y
};
let run = |x| { // 没有返回值

}
let result = add(1, 2);
println!("{}", result); // 打印: 3
闭包中捕获作用域中的值
fn main() {
    let s = String::from("Hello, world!");
    let run = ||{
        print!("{}", s); //闭包函数可以使用作用域内的值,而不用当做参数传进去
    };
    run();
}
迭代器 Iterator

实现了 Iterator trait 的类型,就可以在 Rust 中被视为迭代器

pub trait Iterator {
    type Item;
    fn next(&mut self) -> Option<Self::Item>;
    // 方法with default implementations elided
}

常见的集合类型,例如 Vector、Array、HashSet、HashMap, 字符串 String 类型虽然不是迭代器,但是可以通过.iter().into_iter() 方法,生成迭代器。

  • into_iter 会夺走所有权
  • iter 是借用
  • iter_mut 是可变借用
fn main() {
    let arr = vec![1,3,6,8,9,10,11,12,13,14,15];
    let arr_iter = arr.iter(); // 借用
    for v in arr_iter{
        println!("{}", v) // v就是借用每一个元素
    }
}
fn main() {
    let arr = vec![1,3,6,8,9,10,11,12,13,14,15];
    let arr_into_inter = arr.into_iter(); // 元素所有权转移
    for v in arr_into_inter{
        println!("{}", v) //
    }
}
fn main() {
    let arr = vec![1,3,6,8,9,10,11,12,13,14,15];
    let arr_mut_iter = arr.iter_mut(); // 可变借用
        for v in arr_mut_iter{
        *v += 1; // 修改借用元素的值
    }
}
消费者适配器

只要迭代器上的某个方法 A 在其内部调用了 next 方法,那么 A 就被称为消费性适配器:因为 next 方法会消耗掉迭代器上的元素,所以方法 A 的调用也会消耗掉迭代器上的元素。

let arr = vec![1,3,6,8,9,10,11,12,13,14,15];
let arr_ter = arr.iter();
let s:i32 = arr_ter.sum(); // 会把迭代器中的所有元素相加,执行sum后,会转移所有的元素所有权
迭代器适配器

迭代器有些方法会返回一个新的迭代器,方便链式调用,结尾一定要有消费者适配器返回数据

let v1: Vec<i32> = vec![1, 2, 3];
let v2: Vec<_> = v1.iter().map(|x| x + 1).collect();

智能指针

Rust 中最常见的指针就是 引用,引用以 & 符号为标志并借用了它们所指向的值。除了引用数据没有任何其他特殊功能,也没有额外开销。智能指针smart pointers)是一类数据结构,它们的表现类似指针,但是也拥有额外的元数据和功能

智能指针实现了 Deref 和 Drop trait

常用的智能指针
Box,用于在堆上分配值
fn main() {
        let x = 5;
    let y = Box::new(x); // y是一个指针,*y才是指针指向的值
    assert_eq!(5, x);
    assert_eq!(5, *y); 
}
Rc引用计数智能指针

仅适合单线程使用

允许变量有多个 owner,指针会记录引用的数量,数量为 0 时释放变量

fn main() {
    let five = Rc::new(5);
    let shared_five = Rc::clone(&five); // 只会增加引用数值
    let another_shared_five = Rc::clone(&five);
    println!("{}", *five); // 输出:5
    println!("{}", *shared_five); // 输出:5
    println!("{}", *another_shared_five); // 输出:5
    // 使用 Rc::strong_count 查看引用计数
    println!("{}", Rc::strong_count(&five));  // 输出: 3
}
RefCell在运行时而不是在编译时执行借用规则的类型,也叫做动态借用检查器

Rust 的静态借用规则:

一个或者多个不可变引用(即 &T)。
仅一个可变引用(即 &mut T)。
内部可变性:不可变值的可变借用

它允许你即使在有不可变引用时也可以改变数据,这通常是借用规则所不允许的

use std::cell::RefCell;

struct Container {
    inner_value: RefCell<i32>,
}
impl Container {
    fn new(value: i32) -> Container {
        Container {
            inner_value: RefCell::new(value),
        }
    } 
    fn increment(&self) {
        *self.inner_value.borrow_mut() += 1;
    }
    fn get_value(&self) -> i32 {
        *self.inner_value.borrow()
    }
}

fn main() {
    let container = Container::new(5); // container是不可变的
    println!("Initial value: {}", container.get_value());
    container.increment();
    println!("After increment: {}", container.get_value());
}

原理

borrow(): 这个方法返回一个 Ref<T>,它是一个智能指针,包裹着 RefCell 的不可变引用。你可以同时借用多个不可变引用.

borrow_mut(): 这个方法返回一个 RefMut<T>,它是一个智能指针,包裹着 RefCell 的可变引用。你只能借用一个活跃的可变引用.
暂无回复。
需要 登录 后方可回复, 如果你还没有账号请 注册新账号