模 块 | 问题 | 答案 |
安装 | 如何安装rust | 1.linux mac安装rust 1. curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh 2. 执行 rustc -v 查看是否安装成功 和 版本 |
环境搭建 | 1.如何用命令新建项目 cargo new project 2.如何构建项目 1.cargo build 2.cargo run | |
cargo教程 | cargo的作用 | 1.项目管理:通过cargo 构建 运行 测试项目 1.允许开发者配置构建选项 如编译器选项,特性 和 目标平台 2.通过 cargo new命令快速创建项目 3.通过 cargo test 命令来运行项目的单元测试 4.通过 cargo publish 将库发布到 crates.io 上 5.只从跨平台构建 2.包管理:通过在项目中引入第三方依赖项 1.通过cargo.toml 文件管理依赖项 2.允许开发者搜索 添加 管理 第三方库 |
输出到命令行 | println!()用法 | 1.println!("a is {}",a); {}是占位符 2.如果想把a输出两遍,如何输出 println!("a is {0}, a again is {0}",a ) |
基础语法 | 如何声明变量 | 1.rust是强类型语言 2.通过let 关键字 声明 let a = 123; 3.声明后 a的值不可改变 ,如果想a的值可以改变需要用 let mut a = 123;a=456 |
常量与不可变变量的区别 | 1.let a = 123; let a = 456; 以上程序合法 2.const a : i32 =123; let a = 456; 以上程序不合法 | |
重影 是什么 | 变量的名称可以被重新使用 let x= 5 let x = x + 1 let x = x * 2 x = 12 | |
重影和赋值的区别 | 1.重影:用同一个名字重新代表另一个变量实体 类型,可变属性,值都可以改变 2.可变变量赋值:仅仅值能改变 let mut s = "123" s = s.len() 以上程序会报错,因为s是字符传类型的,不能赋值给整型 | |
rust的数据类型 | rust的整型数据类型有哪些 | i8 u8 i16 u16 i32 u32 i64 u64 i128 u128 isize usize |
isize和usize的解释及用法 | 1.解释 1.这两个数据类型可以存储数据的长度取决于所处平台。如32位架构的 处理器则存储32位的整型 2.用法 1.数组索引 1.数组索引通常用usize类型 let array: [i32; 5] = [1,2,3,4,5] 2.指针运算 3.集合大小 4.内存地址 | |
浮点类型 | 1.f32 表示32位浮点数, f64表示64位浮点数 2.现代计算器对两个浮点类型的处理速度基本相同,但是64位浮点数精度更高 | |
数学运算 | let sum = 5 + 10; // 加 let difference = 95.5 - 4.3; // 减 let product = 4 * 30; // 乘 let quotient = 56.7 / 32.2; // 除 let remainder = 43 % 5; // 求余 | |
字符型 | 1.字符类型用 char 表示 2.char类型大小位4字节(单个char类型变量占用4字节,不是字符串) 如果一个字符 串是 hello, 那么它包含5个字符,因此会占用 5 * 4 = 20个字节空间 3.用法 1.声明类型(用单引号) let letter = 'A' 2.char类型和字符串类型是不一样的 1.单个字符 let c:char = 'a' //单个字符 2.字符串切片 let s: &str = "hello" //字符串切片 3.可变字符串 let string: String = String::from("world") //可变字符串 3.char转换为整数类型 let char = 'A' let int_vlaue = ch as u32 | |
复合类型 | 1.元组用()包含一组数据 1.定义元组 let tuple_example = (10, "hello", 3.14) 2.访问元组中元素 let first = tuple_example.0 3.解构元组 1.let (x,y,z) = tumple println !("{} {} {}",x,y,z) 2.数组用[]包含相同数据 1.定义数组 let a =[1,2,3,4,5] 2.访问数组元素 let first = a[0] 3.定义可变数组 let mut a = [1,2,3] | |
rust注释 | 代码注释 1. /**/ 2.// 文档注释 /// | |
函数 | 定义函数 | 1.定义函数使用fn关键字。 2.函数可以有参数 也可以没有 3.函数可以有返回值 也可以不返回值 fn function_name(parameters) -> return_type { //函数体 //最后一个表达式通常是返回值(如果没有分号) } |
不带参数的函数 | fn main() { say_hello() } fn say_hello() { println!("hello,world"); } | |
带参数的函数 | fn main() { let name = "Alice"; greet(name); } fn greet(name: &str) { println!("Hello, {}!", name); } | |
返回值的函数 | fn main() { let result = add(5, 3); println!("The result is {}", result); } fn add(x: i32, y: i32) -> i32 { x + y // 这里没有分号,所以这是返回值 } | |
函数中的表达式 | 1.表达式是什么 1.表达式是计算某个值的代码片段 2.表达式不以分号(;)结束 3.如 x+1 就是表达式 2.举例: fn calculate(x: i32, y: i32) -> i32 { let z = x * y; z + 10 // 这里是返回值 } | |
控制流 | 1.函数中可以使用 if loop for while等循环 fn main() { let number = 6; if number % 4 == 0 { println!("number is divisible by 4"); } else if number % 3 == 0 { println!("number is divisible by 3"); } else if number % 2 == 0 { println!("number is divisible by 2"); } else { println!("number is not divisible by 4, 3, or 2"); } } | |
函数中使用闭包 | fn main() { let plus_one = |x: i32| -> i32 { x + 1 }; let result = plus_one(5); println!("The result is {}", result); } | |
条件语句 | 1.if语句 if number < 5 { } else { } rust中条件表达式必须是bool类型 let number = 3; if number {} 会报错 if <condition> {block 1} else { block 2 } block1和block2是函数体表达式 let number = if a > 3 { 0 } else { 1 } 2.if let语句:用于处理模式匹配,它允许你将一个特定的模式与一个值进行比较,如果匹配,则执行对应的代码块 let some_value = Some(3); if let Some(3) = some_value { println!("three"); } else { println!("not three"); } | |
循环 | rust循环 | 1.while循环 1.while number != 4 {} 2. while i < 10 { i += 1; } 2.for循环 let a = [10,20,30,40] for i in a.iter(){ println !("值为:{}",i) } //通过下标访问数组 for i in 0..5 { println!("a[{}] = {}",i,a[i]) } 3. loop循环 1.无限循环 2.loop {} 会一直执行 直到遇到break let mut counter = 0; loop { counter += 1; println!("Counter: {}", counter); if counter == 10 { break; } } |
迭代器 | 迭代器 | 1.高效便利数据的方式 2.不需要管理或者循环索引 3.迭代过程中不能修改元素 |
如何创建迭代器 | 1.创建借用迭代器 let vec = vec![1,2,3.4] let iter = vec.iter(); 2.创建可变借用迭代器(允许在遍历过程中修改集合元素 ) let mut vec = vec![1,2,3,4,5] let iter_mut = vec.iter_mut(); for num in iter_mut { *num *= 2; //修改每个元素 } 3.创建所有权迭代器 let vec = vec![1,2,3,4.5] let into_iter = vec.into_iter(); for num in iter { println!("{}",num); } // 下面的代码会导致编译错误,因为 `vec` 已经被 `into_iter` 消耗了 // println!("{:?}", vec); // 编译错误:borrow of moved value | |
常见的迭代器方法 | 1.使用map和filter fn main() { let numbers = vec![1, 2, 3, 4, 5]; // 创建一个迭代器,对每个元素乘以 2 let doubled: Vec<i32> = numbers.iter().map(|x| x * 2).collect(); //collect:将迭代器中的元素收集到一个集合中 // 创建一个迭代器,筛选出偶数 let even_numbers: Vec<i32> = numbers.into_iter().filter(|x| x % 2 == 0).collect(); println!("Doubled: {:?}", doubled); println!("Even numbers: {:?}", even_numbers); } 2.使用fold计算总和 fn main() { let numbers = vec![1, 2, 3, 4, 5]; // 使用 fold 计算总和 let sum: i32 = numbers.iter().fold(0, |acc, &x| acc + x); //fold:将迭代器中的元素累积成一个单一的值 println!("Sum: {}", sum); } 3.迭代器的链式调用 fn main() { let numbers = vec![1, 2, 3, 4, 5]; // 链式调用:筛选出偶数,然后对每个元素乘以 2,最后收集到一个 Vec 中 let result: Vec<i32> = numbers.into_iter() .filter(|x| x % 2 == 0) .map(|x| x * 2) .collect(); println!("Result: {:?}", result); } | |
闭包 | 闭包定义 声明 使用 | 1.闭包是什么 1.闭包允许在其定义的作用域之外访问变量 2.闭包如何声明 let a = | 传递给闭包的参数 | -> 闭包返回的类型 { 闭包的实现 } 3.如何调用闭包 像调用函数一样被调用 let result = 函数名(参数1,参数2) 4.闭包可以使用闭包外的环境变量 let x = 5; let square = |num| num * x println !("{}",square(3)) //输出15 5.获取所有权:获取到外部变量的使用权后,闭包可以访问该变量,外部不能访问 let s = String::from("hello") let print_s = move || println !("{}",s ) //move:获取变量的所有权 而不是借用它们 print_s() //prilntln !("{}",s ) //这行代码会报错,因为s的所有权已经转到了闭包 |
闭包作为参数 | 6.闭包如何作为参数 1.定义了函数,它可以接收一个泛型参数,这个泛型参数可以是任何类型 2.但不能使用这个参数,因为它还不是一个具体类型 3.需要给泛型 取一个名字 比如F 4.rust编译器不知道F是什么类型,它需要一些帮助来解释F是什么 这就是泛型约束 fn call_fn<F>(f: F) where F: Fn() { //<F>:泛型参数的声明 where F:Fn() F必须实现Fn() trait f();F } | |
闭包和错误处理 | 7.闭包和错误处理 1.错误处理 fn divide(a: i32, b: i32) -> Result<i32, String> { if b == 0 { return Err("Divisionby zero".to_string()); } Ok(a / b) } 2.闭包的错误处理 .filter(|x| x % 2 === 0 ) .collect::<Result<Vec<_>,String>>()? 解释 .collect::<Result<Vec<_>,String>>() .collect:Vec类型的一个方法,将迭代器中的元素收集到一个向量中 ::作用域解析运算符 访问结构体 枚举 模块 和关联函数的成员 <> 指定泛型参数的类型约束 Result<> result类型,包含两个参数 Vec:可变动态数组 <_>: 定义泛型类型的参数 _:类型参数占位符 | |
闭包和多线程 | 8.闭包和多线程 1.在不同线程中使用共享数据和执行任务 如何在多线程中使用闭包 1.使用move关键字 fn main(){ let s = String::from("hello") let handle = thread::spawn(move || { println!("{}",s); }) handle.join().unwrap(); } | |
闭包和生命周期 | 生命周期:生命周期用于确保引用始终有效 1.借用环境中的变量 let x = 5; let print_x = || println!("x: {}",x); print_x(); 2.可变借用环境中的变量 let mut y = 10; let mut add_to_y = || y+=1 add_to_y(); println!("y:{}",y); 3.所有权转移 let z = String::from("hello") let move_z = move || println!("z:{}",z); move_z(); | |
闭包的类型 | 1.Fn:表示闭包可以多次被调用 每次调用都会借用环境中的变量 2.FnMut:闭包可以多次被调用 但每次可变地借用环境中地变量 3.FnOnce:闭包只能被调用一次 因为调用时会消耗环境中的变量。这些变量的所有权会被移动到闭包中,因此在闭包外部不能再使用这些变量 | |
所有权 | 所有权是什么 | 1为内存安全和并发提供了强大的保障 2.同时避免了垃圾回收带来的性能开销 |
所有权的规则 | 1.rust中的每个值都有一个变量,称其为所有者 2.一次只能有一个所有者 3.所有者不在程序运行范围时,该值直接被删除 | |
变量范围 | 变量范围是变量的一个属性,其代表变量的可行域 | |
内存和分配 | 栈:已知且固定大小的数据,分配到栈上 堆: 大小未知或者可能变化的数据,比如string类型,rust分配到堆上 | |
变量与数据交互的方式 | 1.移动:想象下传球,你把球传给另一个人 你就没有这个球的了 let a = String::from(); //a给了b a无效了 let b = a; 2.克隆:你得到和别人一摸一样的球 let c = Photo::new("我的") let d = phtot.clone() 3.拷贝:有些东西很轻 你可以轻松复制很多份 let e = 123456 let f = e // e 和 f都有效 4.不可变引用 let g = String::from("a") let h = &g h可以查看g 但不能修改 5.可变引用 let i = String::from("a") let j = &mut i; j和i都可以查看和修改 | |
函数返回值的所有权机制 | 1.函数返回值的所有权机制 1.当一个函数返回一个东西时,如果这个东西是小的(比如数字,布尔),那么函数就像在复印一张纸,给你一张复印件,它还留着原来那张纸 2.但是如果这个东西比较大(比如一段文件,一个列表),那么函数就直接把东西给了你了,自己不留着,你获取了这个东西的所有权 2.举例 fn give_away_list() -> Vec<i32> { let my_list = vec![1,2,3] my_list //返回my list // println!("Inside function: {:?}", my_list); // 错误:使用已移动的值 } fn main(){ let my_list = give_away_list(); //上面函数中的my_list所有权已经转移到了这里的my_list 所以上面函数打印my list时会报错 println!("Revicewd list:{:?}",my_list) } 在give_away_list函数内部再次访问 my_list时会报错,因为my_list的所有权已经给了my_list | |
引用与租借 | 引用 引用的概念 像一个标签或指针,指向某个数据在内存中的位置,但并不拥有那个数据 引用的方式 通过&符号创建 let a = &value 租借 租借的概念 就像在图书馆看书,你可以租借书阅读,但是这本书不属于你,你看完后还得还书 租借的两种方式 1.不可变租借:以只读模式修改借过来的书,不能在书上做任何标记或修改 1.通过 & 进行不可变租借。如: let a = &b 2.可变租借:可以在借过来的书上做标记什么的,但是书最后还要还给图书馆 1.通过 &mut 进行可变租借。 例: let a = &mut b | |
垂悬引用 | 垂悬引用:你有个朋友的地址,你打算去找他,但是你的朋友已经搬家了。你手里的地址(指针)还在, 但它指向的房子(数据)已经不再是你的朋友的家了。这时候,你手里的地址就是一个垂悬引用。 举例: fn main() { let reference_to_nothing = dangle(); } fn dangle() -> &String { let s = String::from("hello"); &s } 很显然,伴随着 dangle 函数的结束,其局部变量的值本身没有被当作返回值,被释放了。但它的引用却被返回,这个引用所指向的值已经不能确定的存在,故不允许其出现。 | |
切片 | 切片的概念 | 引用集合中一段连续的元素 |
切片的类型 | 1.字符串切片:对String类型的引用 如 &str 2.数组切片:对数组或向量的引用 如 &[i32] | |
如何创建切片 | 1.let array = [1,2,3,4,5] let slice = &array[1..4] | |
切片的工作方式 | 1.切片不拥有数据 只是对数据的引用 2.切片长度是固定的,一旦创建,其长度不变(和golang不同) 3.可以通过 for 循环遍历切片 | |
切片的性能 | 1.使用切片比复制整个数据结构高效 2.切片让函数能接受不同长度的数据序列 就像一个万能的容器,不管你放进去几个苹果,它都能装得下。不需要为每个苹果数量准备一个专门的容器。 举例 fn print_number(slice: &[i32]) { for number in slice { println !("{}", number ) } } | |
切片的常用方法 | len() 返回切片的长度 is_empty() 检查切片是否为空 first() 返回切片第一个元素的引用 iter() 返回切片的迭代器 let numbers = vec ![1,2,3,4] let slice = &number[..] let mut iterator = slice.iter() //获取迭代器 where let Some(number) = iterator.next(){ print!("{}",number ) } | |
结构体 | 结构体的定义的三种方式 | 1. struct StructName { field1: Type1 field2: Type2 } 2.元组结构体 (元组:可以包含多个不同类型元组的集合) 1.字段只有类型,没有名称 2.因为有些地方用结构体的时候不需要名称 3.如何定义 struct StructName(i32, i32, i32) 3.单元结构体 1.单元结构体没有字段,类似于空元组(()) 2.单元结构体用于实现某些trait 或作为标记 struct AlwaysEqual; fn main() { let subject = AlwaysEqual; // 可以在这里实现某些 trait 或使用 subject 作为标记 } |
如何实例化结构体 | 1.实例化普通结构体 let point = {x:10, y:20} 2.假如你有一个现有结构体,并且想创建一个新的实例,但只需要更改其中一部分,就可以使用结构体更新语法 struct User { username: String, email: String, sign_in_count: u64, active: bool, } fn main() { let user1 = User { username: String::from("john_doe"), email: String::from("john@example.com"), sign_in_count: 1, active: true, }; // 使用结构体更新语法创建新的实例 let user2 = User { username: String::from("jane_doe"), email: String::from("jane@example.com"), ..user1 // 复制 user1 的其他字段 }; println!("User2: {}", user2.username); } 3.使用new函数实例化 struct User { username: String, email: String, sign_in_count: u64, active: bool, } impl User { // 构造函数 fn new(username: String, email: String, sign_in_count: u64, active: bool) -> User { User { username, email, sign_in_count, active, } } } fn main() { // 使用构造函数创建结构体实例 let user1 = User::new( String::from("john_doe"), String::from("john@example.com"), 1, true, ); println!("User: {}", user1.username); } 3.元组结构体实例化 struct Color(u8, u8, u8); // 代表 RGB 颜色 struct Point3D(f64, f64, f64); fn main() { let black = Color(0, 0, 0); let origin = Point3D(0.0, 0.0, 0.0); println!("Black color: {}, {}, {}", black.0, black.1, black.2); println!("Origin point: {}, {}, {}", origin.0, origin.1, origin.2); } 4.单元结构体实例化(直接使用结构体名称即可) struct AlwaysEqual; fn main() { let subject = AlwaysEqual; // 可以在这里实现某些 trait 或使用 subject 作为标记 println!("Instance of AlwaysEqual created."); } | |
结构体的所有权 | 结构体所有权的规则 1.创建结构体时,就相当于书架上上了新书,这些新书(字段)属于这个书架 let bookshelf = bookshelf { book1:"书1", book2:"书2" } 2.赋值和转移, 当你把这个书架(结构体实例)给了别人,别人获得了这个书架和这个书架 上的书,这些东西都不属于你了 let friendshelf = myshelf //书架给了你朋友你 不属于你了 3.使用引用:如果你只想让别人看书架和书,不想给给人,可以用&来借用结构体字段 fn show_books(bookshelf: &Bookshelf) { println!("书架上有:{} 和 {}", bookshelf.book1, bookshelf.book2); } show_books(&my_bookshelf); // 借用书架上的书来展示,`my_bookshelf` 依然有效 | |
如何输出结构体 | 如何输出结构体 1.在最开头写 #[derive(Debug)] 2.输出的时候用 {:?}这种格式 println!("rect1 is {:?}", rect1); | |
结构体方法 | 1.结构体方法写在impl中 2.结构体方法的第一个参数是self 可以是self &self &mut self &self:借用结构体实例 不允许修改 &mut self:可变引用 允许修改结构体实例 self: 移动结构体实例 方法结束后实例不再有效 示例 1.struct Rectangle { width:u32, height:u32, } 2.impl Rectangle{ fn area($self) -> u32 { self.width * self.height } } | |
结构体关联函数 | 1.在impl中 却没有 &self参数 2.不需要示例(实例化后的结构体),但需要声明在哪个impl中 #[derive(Debug)] struct Rectangle { width: u32, height: u32, } impl Rectangle { fn create(width: u32, height: u32) -> Rectangle { Rectangle { width, height } } } fn main() { let rect = Rectangle::create(30, 50); println!("{:?}", rect); } | |
枚举类 | 如何定义使用枚举类 | 1.定义 enum Book{ Parpery(u32), Electronic(String), } 2.实例化 let book = Book::Parpery(1001) let ebook = Book::Electronic(String::from("url://...")) |
match语法 | match action { Action::Move {x, y} => println!("移动到({}, {})",x,y), Action::Attack => println!("执行攻击"), Action::Rest => println!("休息一下"), } | |
忽略某些枚举 | 不用匹配的情况用 _ 表示 |
组织管理 | 箱是什么 | 箱是编译器编译得最小单位,可以是库或可执行文件 |
箱中的种类 | 1.库箱:可以被其他项目引用的箱 2.二进制箱:生成可执行问价你的箱 | |
包是什么 | 1.一个包是一个或多个箱的集合。 2.每个包包含一个cargo.toml文件,定义了包的元数据和依赖项 | |
模块是什么 | 组织代码的工具 将相关功能,类型,宏 分组到一起,并且可以控制他们的可见性 | |
如何使用模块 | 1.定义:mod my_module {} 2.使用定义好的模块 my_module::public_function | |
难以发现的模块 | 1.文件名称使用.rs命名时,可以不写 mod xx {} 2.别的文件想引用这个模块的直接 直接 mod xx; | |
引用标准库 | 1.rust官方标准库:List of all items in this crate 2.导入标准库使用函数 use std::f64::consts::PI; fn main() { println!("{}", (PI / 2.0).sin()); } | |
错误处理 | 1.Result<T, E>(结果) 2.Option<T>(选项) 3.panic!(恐慌) | |
错误处理的方法 | fn divide(a: i32, b: i32) -> Result<i32, String> { if b == 0 { Err("Division by zero is not allowed!".to_string()) } else { Ok(a / b) } } 1.match fn main() { let result = divide(10, 2); match result { Ok(answer) => println!("The result is: {}", answer), Err(e) => println!("Error: {}", e), } } 2.if let fn main() { let result = divide(10, 0); if let Ok(answer) = result { println!("The result is: {}", answer); } // 如果是 Err,我们不打印任何东西 } | |
3.unwarp 和 expect fn main() { let answer = divide(10, 2).unwrap(); // 如果是 Err,这里会 panic println!("The result is: {}", answer); // 使用 expect 提供更明确的错误信息 let answer = divide(10, 0).expect("Cannot divide by zero!"); println!("The result is: {}", answer); // 这行不会执行,因为上面会 panic } 4. 使用?运算符传播错误 fn main() -> Result<(), String> { let answer = divide(10, 2)?; println!("The result is: {}", answer); Ok(()) } | ||
kind | 分类不同的错误类型 use std::io; use std::io::Read; use std::fs::File; fn read_text_from_file(path: &str) -> Result<String, io::Error> { let mut f = File::open(path)?; let mut s = String::new(); f.read_to_string(&mut s)?; Ok(s) } fn main() { let str_file = read_text_from_file("hello.txt"); match str_file { Ok(s) => println!("{}", s), Err(e) => { match e.kind() { io::ErrorKind::NotFound => { println!("No such file"); }, _ => { println!("Cannot read the file"); } } } } } | |
rust泛型 | 泛型是什么 | 就像是占位符,写代码的时候用什么类型,先泛型占用下。 等到真的使用时,再根据需要把占位符换成具体类型 |
为什么需要泛型 | 比如所 你想写一个函数,两个数相加,参数只能是某个具体类型 。 但如果用泛型,参数可以是任意类型 | |
泛型怎么用 | 1.泛型函数 fn print_value<T>(value: T){ println !("Value is :{:?}",value); } 2.泛型结构体 struct Wrapper<T> { value: T, } 3.泛型枚举 enum Option<T> { Some(T), None, } | |
特性 | 特性是什么 | 比如说你开了一家餐厅,你需要厨师,但是不是随便一个人能当的。你要求厨师会做饭, 这个 会做饭 就是一种技能 ,就是我们说的特性 |
特性怎么用 | 1.定义一个特性:先定义一个技能标准,比如 会做饭 的厨师得会炒菜, 在rust中就会定义一个trait 例如:定义一个特性 会做饭 trait CanCook{ fn cook(&self, recipe: &str) -> String; } 2.让类型实现特性:根据你定义的特性,找到会炒菜的人。让一个类型实现trait 例如: 让厨师实现会做饭这个技能中要求的所有方法 1. 定义一个厨师 struct Chef { name: String, } 2. 让厨师实现会做饭这个方法 impl CanCook for Chef { fn cook(&self, recipe: &str) ->String { format("{} is Cooking {}", self.name, recipe) } } | |
特性有啥特别的地方 | 1.自带技能:你这个技能证书里不光有要求,还附赠一些基本技能,比如每个厨师都得会开火 1.写技能(特性)中的默认方法(默认会的动作) trait CanCook { //声明一个动作 要求会这个技能的人必须会的动作 fn cook(&self, recipe:&str) -> String; //默认动作 会这个技能的人默认会的动作 fn light_fire(&self) -> String { "Turning on the stove...".to_string() } } 2. 会这个技能的厨师不用不用学也会light_fire动作,所以厨师学习这个技能的时候,不用学习light_fire动作 impl CanCook for Chef { //厨师需要学习这个动作 fn cook(&self, recipe: &str) -> String { format!("{} is cooking {}", self.name, recipe) } //厨师不需要学习light_fire这个动作,也能直接用它 } 3. 厨师会的动作 fn main() { let chef = Chet { name: "Alice".to_string(). } //厨师会cook println!("{}", chef.cook("spaghetti")); //厨师会 light_fire println!("{}", chef.light_fire()); } | |
2.一身多技;一个人可以学会很多技能,一个类型也可以实现很多特性。如,一个厨师不光会炒菜,还会烤面包。 1.有一个烤面包的特性 trait CanBake { fn Bake(&self, item: &str) -> string } 2.厨师同时可以继承上面的会炒菜,和 烤面包的特性 impl CanBake for Chef { fn Bake(&self, item: &str) -> String { format!("{} is baking {}", self.name, item) } } | ||
特性在函数里的应用 | 1.特性当参数:如果你要办一个厨师大赛,你不会管厨师是哪里人,长什么样,你只关心他们会不会炒菜。在 Rust 函数里,这就是用 impl Trait 当参数,只要你会炒菜,就能参加比赛。 1.定义一个厨师大赛的函数 参加这个大赛的人都得是实现CanCook这个特性 fn chef_competition(chef: impl CanCook){ println !("{}", chef.cook("gourmet meal")) } 2.实现CanCook的厨师参数厨师大赛 fn main(){ let chef =Chef { name: "Bob".to_string(), } //假设chef已经实现了CanCook特性 //chef参加厨师大赛 chef_competition(chef); } | |
2.特性当返回值:如果你说,我要派一个厨师去参加比赛,这个厨师得会炒菜。这就是一个函数返回一个实现了特定特性的类型,但是你只能派一个厨师,不能一会儿派一个,一会儿又换一个。 1.定义一个厨师大赛的函数,该函数返回一个会炒饭的厨师 fn send_chef_to_competition() -> impl CanCook { Chef { name: "Alice".to_string(), } } 2.调用该函数,得到一个会炒饭的厨师,可以使用该厨师会的动作 let chef = send_chef_to_competition(); // 厨师开始烹饪 println!("{}", chef.cook("souffle")); | ||
生命周期 | 生命周期的概念 | 生命周期注释:给钥匙上贴了一个标签,写着只能周一到周五使用 fn main() { let message = String::from("hello world") //message的生命周期开始 let message_ref = &message print_message(message_ref); } fn print_message<'a>(message: &'a str){ //引用变量作为参数传递在函数间传递时,需要加 'a 标签(生命周期注释) println !("{}", message) } |
具体知识点 | 1.借用和所有权的区别 借用:你借用了朋友的车,但是车还是朋友的,你用完要还给他 fn main(){ let my_car = Car { model: "Tesla".to_string() } drive_car(&my_car) //借用了car // drive_car这个函数借用了my_car, 但是my_car还是属于main这个函数 println !("Finish driving {}",my_car.model) //main这个函数依旧可以使用car } 所有权:你买了一个二手车 可以随用使用,也可以卖掉,完全由你控制。原来的人无法使用 let my_car = Car { model: "Tesla".to_string() } buy_car(my_car) //buy_car这个函数买了我的车 获得了车的所有权 println !("{}",my_car) //我无法继续使用这个车了 buy_car这个函数可以随意使用 | |
2.函数中的生命周期 你开车进了一个临时停车场(函数),车辆(数据)可以进来,但是当停车场关闭(函数结束),所有车辆必 须离开(数据清理) // 定义一个函数来处理消息 fn process_message<'a>(message: &'a str) { println!("Processing message: {}", message); } fn main() { let message = String::from("Hello, world!"); // 调用 process_message 函数 process_message(message.as_str()); //main函数时message.as_str()的所有者,因此main函数结束时,其数据被清理 } | ||
3.结构体中的生命周期 1.有一个房间放有多个保险箱(每个保险箱是一个结构体),这个房间里的保险箱钥匙的有效期(结构体字段)必须保留到房间 钥匙的有效期 struct Book<'a> { content: &'a str 结构体字段的生命周期注释 } impl<'a>Book<'a>{ fn new(title: &'a str) ->self { //实例化该结构体 Book {title} } fn title(&self) -> &str { //返回结构体的title self.title } } fn main() { let title = String::from("Rust Programming"); let book = Book::new(&title); // `book` 借用了 `title` 的一个切片 println!("The book title is: {}", book.title()); // 当 `title` 在这里仍然有效时,`book` 可以安全地使用 `title` 的引用。 } | ||
文件与io | 文件读取 | 1.一次性读取整个文件 1.如果文件不是很大,可以读取整个文件到内存中, 通过 std::fs::read_to_string 函数实现 let content = fs::read_to_string("/path/to/file.txt").expect("Failed to read file"); println!("File contents: {}",content) 2.逐行读取 let file = File::open("path/to/file.txt").expect("Failed to open file"); let reader = BufReader::new(file); for line in reader.lines() { let line = line.expect("Failed to read line"); println!("{}", line); } |
文件写入 | 1.写入文本 let data = "hello world" fs::write("path/file.txt",data).expect("failed to wirte file") //fs:file system 的缩写 2.更复杂的写入:通过std::io.Write trait来写入 use std::fs::File; use std::io::{self, Write}; // 引入必要的模块 fn main() { let mut file = File::create("path/to/output.txt").expect("Failed to create file"); //创建文件句柄 writeln!(file, "Hello, Rustaceans!").expect("Failed to write to file");} | |
创建文件夹 | 通过 std::fs::create_dir_all 创建文件夹 fs::create_dir_all("path").expect("failed") | |
删除文件 | 通过 std::fs::remove_file 删除目录 fs::remove_file("path").expect("failed") | |
删除空目录 | 通过std::fs::remove_dir 删除空目录 fs::remove_dir("path").expect("failed") | |
删除非空目录 | 需要递归删除子目录和文件,通过 fs_extra来支持 extern crate fs_extra; use fs_extra::dir::remove_dir_all; fn main() { remove_dir_all("path/to/non_empty_directory").expect("Failed to remove directory recursively"); } | |
集合与字符串 | 向量 | 1.向量是什么 1.存放多值的单数据结构,该结构将相同类型的值线性的存放在内存中 线性:在内存中以连续的方式排列 2.向量如何定义 1.声明一个空的向量 let v :Vec<i32> = Vec::new(); //创建类型为i32的空向量 vec:vector 表示动态增长或缩小长度的数组 2.通过数组创建向量 vec = vec![1,2,3,4] //通过数组创建向量 vec:宏的名字 !:告诉rust,这是一个宏调用 [1,2,3,4] 传给宏的参数,传给宏一组数字,宏会帮我们创建一个包含这些数字的向量 3.如何往向量中添加元素 let mut vector = vec![1,2,4,8] vector.push(16) 4.将一个向量拼接到另一个向量的尾部 let mut v1: Vec<i32> = vec![1,2,3,8] let mut v2: Vec<i32> = vec![16,32,64] v1.append(&mut v2); |
5.获取向量中的某个值 1.通过get方法获取 它返回一个option类 let mut v = vec![1,2,3,4,5] println!("{}", match v.get(0)) { Some(third) => println!("This third element is :{}",third), None => println("There is no third element."), } 2.通过索引 let v = vec![1,2,3,4,5]; let third = v[2]; 注意:如果尝试访问一个不存在的索引,程序将会panic并崩溃 | ||
字符串 | 1.字符串的类型 1.str:不可变,固定长度的字符串切片,通过以 &str形式出现 2.String:一个可增长的 堆分配的字符串类型。 str:就像一本书的内容,你只读,不可修改,也不能带走(拥有数据) String:就像自己的笔记,可以在上面写字,也可以随意携带 2.字符串字面量 let s = "Hello, world"; 3.创建字符串 1.创建一个空的字符串 let mut s = String::new(); 2.用 to_string() 或 string::from() 将字符串切片转为String let s1 = "Hello".to_string() 4.字符串修改 1.使用push_str() 和 push() 方法向 String 添加文本 let mut s = String::from("Hello") s.push_str(", world!); s.push("!") 2.使用 + 或者 format! 宏来拼接字符串 let s1 = String::from("Hello, "); let s2 = String::from("world!"); let s3 = s1 + &s2 s1被移动:s1的所有权被转移 s2被借用:s2暂时被借用 但是所有权还在 | |
5.如何访问字符串中的字符 1.不允许通过索引直接访问字符串中的字符 如 let h = s[0] 2.使用 chars() 方法迭代字符串中的字符 for c in "你好 世界".chars() { println!("{}",c); } 3.使用bytes()方法迭代字符串中的字符 for b in "你好 世界".bytes() { println!("{}",b) } 6.字符串切片 1.允许使用字符串切片来引用字符串的一部分 但必须确保切片的边界在字符串边界 let s = "你好 世界" let Slice = &s[0..3] 7.Rust字符串与UTF-8 1.rust的String类型保证是有效的UTF-8 2.rust的char类型代表一个Unicdoe标量值,意味着可以表示比ASCII更广泛的字符 ASCII:只包含128个字符,包括英文字母 数字和 一些特殊符号 Unicode:可以表示ASCII中的字符,也可以表示ASCII之外的字符 如中文的 ”汉“ 日语,或表情符号 | ||
映射表 | 1.创建hashMap use std::collections::HashMap; let mut map = HashMap::new() 2.向hashMap中插入元素 map.insert(String::from("key1"),10) 3.访问元素 使用get方法通过键来访问值 let value = map.get("key1") 4.更新已存在的元素 1.map.insert(String::from("key1"),20) 2.map.entry(String::from("key1")).and_modify(|e| * e +=1); |e| *e +=1是一个闭包的写法 1.|e|:定义闭包, |e|表示闭包接收一个参数e 在上面例子中,e是一个可变引用,指向hashMap中与特定键关联的值 2. *e: e是一个可变引用,需要使用 * 来访问它指向的实际值 3. *e +=1: 将e指向的值增加1 | |
5.如何遍历hashMap 1.使用for循环遍历 hashMap中所有键值对 for (key,value) in &map{ println!("{}:{}",key,value) } 6.哈希函数 1.默认情况下,hashMap使用SipHash算法作为哈希函数,但可以通过指定不同的hasher来改变这一点 use std::collections::HashMap; use std::collections::hash_map::DefaultHasher; let mut map = HashMap::with_hasher(DefaultHasher::new()); 7.性能 1.时间复杂度:平均情况下,插入 删除 查找的时间复杂度为O(1) 2.空间复杂度:HashMap 通常比存储同样数量元素的 Vec 需要更多的空间,因为它需要存储额外的哈希表信息 | ||
面向对象 | 封装 | 1.可以使用结构体struct来封装数据,使用关联函数和方法实现对数据的操作 例如 struct Rectangle { width: u32, height: u32, } impl Rectangle { fn new(width: u32, height: u32)-> Rectangle { Rectangle { width, heoght} } fn area(&self) -> u32 { self.width * self.height } |
继承 | 1.rust不支持继承 但是可以通过其他方式实现继承 2.rust实现继承的方式 1.组合 1.比喻:一个学生有一个书包,则学生类可以继承书包类型的字段 2.代码示例 //1.定义Book结构体 struct Book { title: String, author: String, } //2.Book的方法 impl Book { // Book自己的方法 fn print_details(&self) { println!("Title: {}, Author: {}", self.title, self.author); } } //3.liabrary用book实例 struct Labrary { book:Book } | |
//4.实现library的构造函数 impl Library { fn new(book: Book) -> Library { Library {book} } } //5. main函数中使用library和book fn main() { // 创建一个Book实例 let my_book = Book { title: String::from("Rust编程之道"), author: String::from("张三"), }; // 使用Book实例来创建Library实例 let my_library = Library::new(my_book); // 通过Library实例直接调用Book实例的print_details方法 my_library.book.print_details(); } | ||
3.使用使用trait实现继承 // 定义一个 Trait,描述用户的基本行为 trait UserBehavior { fn display_info(&self); } // 实现 UserBehavior Trait impl UserBehavior for User { fn display_info(&self) { println!("Username: {}, Email: {}", self.username, self.email); } } impl UserBehavior for Admin { fn display_info(&self) { println!("Username: {}, Email: {}, Admin Level: {}", self.user.username, self.user.email, self.admin_level); } } // 用户信息结构体 struct User { username: String, email: String, } | ||
impl User { pub fn new(username: String, email: String) -> User { User { username, email } } } // 管理员结构体,包含用户信息 struct Admin { user: User, admin_level: u8, } impl Admin { pub fn new(username: String, email: String, admin_level: u8) -> Admin { let user = User::new(username, email); Admin { user, admin_level } } pub fn manage_users(&self) { println!("Managing users..."); } } | ||
// 泛型函数,接受实现了 UserBehavior Trait 的引用 fn display_info_for_anyone<T: UserBehavior>(person: &T) { person.display_info(); } fn main() { let user = User::new(String::from("user"), String::from("user@example.com")); let admin = Admin::new(String::from("admin"), String::from("admin@example.com"), 5); display_info_for_anyone(&user); display_info_for_anyone(&admin); } | ||
并发编程 | rust并发编程的几个概念 | 1.线程 1.线程是操作系统调度的基本单位,可独立执行任务 2. rust提供了 std::thread 模块,用于创建和管理线程 2.所有权和借用 1.rust通过所有权和借用机制来确保内存安全 2.在并发环境下,所有权和借用规则有助于防止数据竞争等问题 3.原子操作 1.原子操作可以在多线程下安全更新共享数据 2.使用 std::sync::atomic 实现原子操作 4.通道 1.通道提供一种在多线程间传递消息的机制,有助于实现无锁编程 2.rust提供了 std::sync::mpsc模块,用于实现线程间的通信 mpsc:Multiple Producer ,Single Consumter 多个生产者线程发送消息到一个 通道,单个消费者线程接受该通道信息 5.Arc(Atomic Refrence Counting) 1.Arc可以在多个线程间共享数据 同时提供引用计数来管理生命周期 2.rust提供了 std::sync::Arc 用于实现多线程环境下智能指针 解释 1.什么是自动引用计数 每创建一个对象,系统会该对象分配一个计数器,每当有一个新的引用指向该对象时,计数器就会增加 当引用不再存在时,计数器就会减少 |
rust并发编程示例_创建线程 | use std::thread; use std::time::Duration; fn main() { println!("Hello from the main thread!"); // 创建一个新的线程 let handle = thread::spawn(|| { //spawn:生成,创建。它接受一个必包作为参数,定义了新线程将要执行的代码 // || : 作为必包的参数分隔符 ||表示该必包不接受任何参数 for i in 1..10 { println!("Hello from the spawned thread! {}", i); thread::sleep(Duration::from_millis(1)); } }); // 等待新线程结束 handle.join().unwrap(); //unwarp:解包,展开 println!("Back to the main thread!"); } | |
rust并发编程示例_使用通道传递消息 | use std::sync::mpsc; use std::thread; fn main() { // 创建一个通道 let (tx, rx) = mpsc::channel(); // 创建一个线程来发送消息 let tx1 = tx.clone(); thread::spawn(move || { //move:当一个必包通过move关键字捕获变量时,它将取得这些变量的所有权 let vals = vec![ //vec! 创建向量 String::from("hello"), String::from("world"), ]; for val in vals { tx1.send(val).unwrap(); thread::sleep(std::time::Duration::from_secs(1)); } }); thread::spawn(move || { let vals = vec![ String::from("foo"), String::from("bar"), ]; for val in vals { tx.send(val).unwrap(); thread::sleep(std::time::Duration::from_secs(1)); } }); // 接收消息 for received in rx { println!("Got: {}", received); } } | |
rust并发编程示例_使用Arc实现共享数据 | use std::sync::{Arc, Mutex}; use std::thread; fn main() { let counter = Arc::new(Mutex::new(0)); //创建共享计数器 用 Arc 来允许这个互斥锁的所有权在多个线程之间共享 //1.Mutext::new(0) :创建一个互斥锁,它内部包含了一个初始值为0的整数 //比喻:你有一个数字(初始值是 0),并且你把它放在一个保险箱(互斥锁)里,这样只有拥有钥匙(锁)的人才能打开它并修改里面的数字。 //2.Arc::new:增加引用计数,每个线程都有自己的数据引用 //比喻:然后,你制作了这个保险箱的多个副本(使用 Arc),每个副本都可以被一个线程安全地使用,而原始的数字始终保持一致和安全。 let mut handles = Vec::new(); //存储每个新创建的线程的句柄 //1.Vec::new() 创建了一个空的动态数组。动态数组可以存储一系列的元素,并且可以根据需要自动增长或缩小。 //比喻:创建了一个名为 handles 的新盒子(变量),并且这个盒子是可变的,意味着你可以往里面放东西或者把里面的东西拿出来。 //这个盒子(handles)目前是空的,但它是专门用来存放“句柄”的。 for _ in 0..10 { let counter_clone = Arc::clone(&counter); // Arc::clone 方法来增加 Arc 的引用计数,这样每个线程都可以拥有 counter 的一个引用 let handle = thread::spawn(move || { let mut num = counter_clone.lock().unwrap(); 使用 lock 方法来获取互斥锁,这会阻塞当前线程直到锁可用 *num += 1; //一旦获取了互斥锁,就增加包装在互斥锁内部的计数器的值 }); handles.push(handle); //将新线程的句柄添加到 handles 向量中。 } for handle in handles { handle.join().unwrap(); //使用 join 方法等待每个线程完成 } println!("Final count: {}", *counter.lock().unwrap()); //再次获取互斥锁,打印出最终的计数器值 //unwrap():这个方法用于处理 Result 类型。在这个上下文中,lock() 方法返回的是一个 Result 类型,它可以是 Ok,表示成功获取了锁,或者 Err,表示在尝试获取锁时发生了错误。unwrap() 方法会检查 Result,如果是 Ok,它会返回里面的值(在这个例子中是锁住的计数器),如果是 Err,程序将会 panic(即异常终止) } | |
Arc上述代码解释(比喻) | 想象一下,你有一个特殊的计数器(就像一个可以计数的魔法盒子),这个计数器放在一个保险箱里,这样只有拥有钥匙的人才能打开它并改变里面的数字。现在,你想让10个朋友在不同的房间里同时增加这个计数器的值,而且每个人只能增加一次。 以下是这段代码的步骤: 创建一个计数器和一把锁: let counter = Arc::new(Mutex::new(0)); 这行代码创建了一个初始值为0的计数器,并且把它放在一个互斥锁(Mutex)里,这样可以确保一次只有一个朋友能改变计数器的值。 然后,使用 Arc(原子引用计数)来创建这个计数器的一个安全副本,这样多个朋友(线程)都可以拥有这个计数器的引用,而不会担心数据的安全问题。 准备一个空列表来存放朋友的遥控器(线程句柄): let mut handles = Vec::new(); 这行代码创建了一个空的列表,用来存放每个朋友房间的遥控器。遥控器可以用来在最后确认朋友是否已经完成了增加计数器的任务。 创建10个朋友(线程)来增加计数器的值: for _ in 0..10 { ... } 这个循环会执行10次,每次都创建一个新的朋友(线程)。 在循环内部,每个朋友都获得了一个计数器副本的遥控器(Arc::clone(&counter)),并且通过 thread::spawn 创建了一个新的线程。 在新线程中,朋友会先打开保险箱(counter_clone.lock().unwrap()),然后增加计数器的值(*num += 1;),最后保险箱会自动上锁。 等待所有朋友完成增加计数器的任务: for handle in handles { handle.join().unwrap(); } 这行代码遍历列表中的所有遥控器,并使用 join 方法等待每个朋友完成他们的任务。这就像是在每个房间外等待,直到朋友告诉你他们已经完成了增加计数器的任务。 打印最终的计数器值: println!("Final count: {}", *counter.lock().unwrap()); 最后,打开保险箱,查看计数器的最终值,并打印出来。这个值应该是10,因为每个朋友都增加了1。 总的来说,这段代码展示了如何在 Rust 中使用多线程安全地增加一个共享计数器的值。 | |
rust宏 | 什么是宏 | 宏是编译时自动生成代码的技术。 比喻:想象一下,你在做饭时有一个食谱,这个食谱告诉你一步一步做菜。宏就像一个食谱,它告诉计算机如何生成一段代码 |
宏的类型 | 1.声明式宏 作用:让你用一种模板的方法生成代码,可以把它想象成填空题 例子: macro_rules! max { ($x:expr, $y:expr) => { if $x > $y { $x } else { $y } }; } let m = max!(3, 5); // 输出结果将是 5 在这个例子中 max!就是一个填空题,你只需要填两个数字,它就会帮你生成一个if语句来比较两个数字,并返回较大的那个 | |
2.程序式宏 作用:可以生成rust代码的函数,可以把它想象成一个“智能食谱”,这个食谱不仅告诉你怎么做菜,还能根据不同的食材自动调整做法 例子: // 引入必要的 crate extern crate proc_macro; use proc_macro::TokenStream; use quote::quote; use syn::parse_macro_input; #[proc_macro_derive(Counter)] //标记表明这是一个过程宏 用于派生(derive)宏,并且它的关联的宏的名称是Counter pub fn derive_counter(input: TokenStream) -> TokenStream { // 解析输入的结构体定义 let input = parse_macro_input!(input as syn::DeriveInput); //将输入的 TokenStream 解析为 syn::DeriveInput 类型 // 根据解析得到的信息生成代码 let name = &input.ident; let gen = quote! { //quote! 宏用于生成 Rust 代码 impl #name { //生成了一个 impl 块,这个块为结构体实现了一个名为 count_fields 的方法 fn count_fields() -> usize { // 返回结构体字段的数量 5 // 假设结构体有五个字段 } } }; // 返回生成的代码 将生成的代码转换回 TokenStream 并返回它 gen.into() } #[derive(Counter)] //告诉 Rust 编译器使用我们定义的 Counter 过程宏来为 MyStruct 结构体生成代码 struct MyStruct { field1: u32, field2: String, // ...更多字段 } | ||
如何使用宏 | 1.直接调用: max!(3, 5) 2.作为属性:#[derive(Counter)] 作为结构体定义的一部分 | |
使用宏的注意事项 | 1.错误信息不友好:宏是在编译中运行的,如果宏有错误,错误信息可能不如rust代码那么清晰 2.过度使用造成复杂性 | |
智能指针 | Box<T> | 概念:简单的智能指针 用于在堆上分配数据 比喻:想象你有一件很重的东西(比如一大本书),你不想一直拿着它。所以,你把它放在一个盒子里(Box),然后只拿着盒子的标签(指针)。这样,你只需要记得标签的位置,就能找到那件东西。 用途: 1.当你需要在堆上分配数据,而不是在栈上时 //堆和栈的区别: 栈:操作系统自动管理 存储函数的局部变量。 就像一摞盘子,用完就放回 堆:程序员手动管理 存储程序运行动态分配的内存 如对象 数组。 就像一个仓库, 东西可以随意存放,但需要自己收拾 2.当类型的实际大小只有在运行时才能确定 3.想要将大对象的所有权转移给函数,但又不想复制数据 |
Rc<T> | 概念:引用计数,允许多个所有者共享同一数据 比喻:假设你和你的朋友都想读同一本书,但是你们不想每个人都购买,于是你们决定一起买一本。每个人都有个借阅卡Rc<T>,当你或者你朋友想读书时, 拿出借阅卡,当所有人都读完并且还借阅卡后,这书才会被处理掉 用途 1.在单线程环境中共享数据 2.希望多个所有者可以访问同一个数据,但不需要同时修改它 例子 use std::rc::Rc; let a = Rc::new(5); let b = Rc::clone(&a); println!("a = {}, b = {}", a, b); | |
Arc<T> | 概念:是Rc<T>的线程安全版本,全程是Atomic Reference Counting 它与Rc<T>类似,但可以安全的多线程间共享数据 比喻:假设你和你朋友不仅想在同一本书上做笔记,还想同时做这件事情。为了安全起见,你们决定使用一种特殊的借阅卡Arc<T> 这种卡可以确保多人使用 也不会出错 用途 1.需要在多个线程中共享数据 2.希望多个线程可以安全地访问同一个数据 例子 use std::sync::Arc; use std::thread; let a = Arc::new(5); let b = Arc::clone(&a) let handle = thread::spawn(move || { println!("b in thread = {}",b ) }) | |
RefCell<T> | 概念:提供运行检查时所有规则;通常情况下rust所有权规则是在编译时检查的,但是RefCell<T>允许你在运行时改变数据,即使在不可变引用存在的情况下 比喻:假设你有一本图书馆的书,图书馆规定你不能在书上做笔记。但是,你发现了一个秘密房间(RefCell<T>),在这个房间里,你可以自由地在书上做笔记,只要没有人发现。如果有人发现了,程序会报错。 用途: 1.当你需要在不可变引用存在的情况下修改数据 2.当你需要在运行时动态地检查借用规则 例子: use std::cell::RefCall; let c = RefCell::new(5); { let mut m = c.borrow_mut(); *m +=1 } println!("c = {}", c.borrow()); // 获取不可变引用 | |
Cell<T> | 概念:允许你修改内部地值,即使在不可变引用地情况下,但它只能用于被复制地类型(即实现 Copy 或 Clone地类型) 比喻:想象你有一块橡皮擦,你可以在任何时候使用它擦掉写下的字,即使书已经在别人手上。 用途:需要在不可变引用存在的情况下修改数据,但数据类型可以复制 例子: use std::cell::Cell; let d = Cell::new(5); // 创建一个 Cell d.set(6); // 修改数据 println!("d = {}", d.get()); // 获取数据 | |
UnsafeCell<T> | 概念:是 Cell<T> 的基础,它允许你修改任何类型的值,但使用它时必须非常小心,因为它绕过了 Rust 的所有权和生命周期系统,可能会导致不安全的行为 比喻:想象你有一个魔法盒子,你可以随时修改里面的东西,但如果你不小心,可能会引发灾难 用途: 1.当你需要在不可变引用存在的情况下修改任何类型的数据,但需要非常小心以避免不安全的行为 | |
异步编程 | rust异步编程的基础概念 | 1.异步函数:使用async关键字定义一个异步函数,异步函数返回一个实现了Future特性的类型,这个Feature类型代表一个可能还未完成的计算 Feature特性的类型:rust的Feature就像一个将来会完成的任务 或者 一个将来会实现的承诺 2.Future: 代表异步操作最终的结果。 3.轮询:由异步运行(tokio 或 async-std)来管理 4.任务调度器:管理多个任务执行 |
异步编程的基本结构 | 1.定义异步函数 async fn my_async_function() -> Result<String, std::io::Error> { Ok("Hello World".to_string()) } 2.调用异步函数 let result = my_async_function().await; 解释: 1.调用异步函数 实际上是在创建 Future 2.Future代表了一个异步操作,但它本身不会立即开始执行 3.运行异步代码 #[tokio::main] async fn main(){ let result = my_async_function().await println!("{}",result.unwrap()); } 解释: 1.要使第二步的Future开始执行,必须放入一个异步运行(Tokio,Async-Std等) 2.异步运行时负责调度和执行这些Future | |
异步流 | 1.什么是异步流 1.异步流就像是一个流水线,会不断给你发过来数据。而你每收到一条数据,就可以马上处理这条数据 2.不需要等所有数据都到齐了再处理 2.为什么使用异步流 1.节省内存:不一次性把所有数据放到内存 2.提高效率:尽早处理已经接收的数据 不用等待所有数据都准备好 3.示例 #[tokio::main] async fn main() { let mut message = receive_message(); //receive_message返回一个异步流 while let Some(message) = messages.next().await { //每次接收到一条消息,就可以立即处理这条消息 match message { Ok(msg) => println!("收到消息: {}", msg), Err(e) => eprintln!("出错: {}", e), } } } | |
rust实现并发编程的方式 | 1.使用 tokio::spawn 启动新的异步任务 2.使用 tokio::join! 等待多个任务完成 | |
并发编程的示例 | 1.定义异步函数 use reqwest; async fn download_data(url: &str) -> Result<String, reqwest::Error> { let response = reqwest::get(url).await?; //发起网络请求 使用.await等待请求完成 response.text().await //获取响应的文本内容 } 2.启动多个异步任务 use tokio; #[tokio::main] async fn main() { let urls = vec![ //包含需要下载的URL "https://example.com/data1", "https://example.com/data2", "https://example.com/data3", ]; let tasks: Vec<_> = urls.into_iter() .map(|url| tokio::spawn(download_data(url))) //map方法遍历urls,每个url调用tokio::spawn启动一个新的异步任务 .collect(); //返回JoinHandle 讲joinHandle收集到一个向量tasks 3.等待所有任务执行 for task in tasks { match task.await { //返回一个Result<_,_> 表示任务的执行结果 Ok(result) => match result { Ok(data) => println!("下载完成: {}", data), Err(e) => eprintln!("下载出错: {}", e), }, Err(e) => eprintln!("任务出错: {}", e), } } } |