亚泰国际建设股份有限公司网站,app推广方案模板,自建公司网站,免费做网站推广Rust 进阶学习 文章目录 Rust 进阶学习所有权作用域移动和克隆涉及函数的所有权机制涉及参数的所有权涉及返回值的所有权 引用和租借可变引用 枚举类枚举成员的属性枚举匹配 结构体结构体方法结构体关联函数 错误处理不可恢复错误可恢复错误 Rust代码组织管理Module默认的Modul…Rust 进阶学习 文章目录 Rust 进阶学习所有权作用域移动和克隆涉及函数的所有权机制涉及参数的所有权涉及返回值的所有权 引用和租借可变引用 枚举类枚举成员的属性枚举匹配 结构体结构体方法结构体关联函数 错误处理不可恢复错误可恢复错误 Rust代码组织管理Module默认的Module引用Module CratePackage Rust面向对象trait特性默认特性小结 所有权
所有权规则
每个值都有一个变量这个变量称为所有者每个值同时只能有一个所有者当所有者超出作用域时它在内存中对应的值会被删除。
作用域
最简单的就是函数内的变量在函数体外无法使用因为在函数体外变量已经被删除
fn demo_func(){let demo_var 1;println!({}, demo_var);//函数体内的变量可以使用
}
fn main() {demo_func();println!(demo_var: {}, demo_var);//超出作用域无法使用
}移动和克隆
移动需要分两种情况讨论。
对于基础数据类型以及由基础数据类型构成的复合类型都是保存在栈内存中的数据值进行变量赋值时会在内存中复制一个同样的值。
如
fn main() {let x 5;let y x;println!(x: {}, y: {}, x, y);/* x: 5, y: 5 */
}对于保存在堆中的值变量赋值时会进行“移动”。
如对String变量进行变量间的赋值
fn main() {let s1 String::from(hello);let s2 s1; println!(s2: {}, s2); // s2通过移动指向了字符串hello的内存可以打印println!(s1: {}, s1); // s1 已经失效编译报错。
}s1赋值给s2后因为任何值只有一个所有者s1不能再指向hello字符串这个内存值所以s1不再有效。即进行了所谓的“移动”。
如果想要实现变量值之间的复制需用通过克隆。如
fn main() {let s1 String::from(hello);let s2 s1.clone(); // 将hello字符串克隆一份s2指向克隆后的内存println!(s2: {}, s2); // s2指向克隆后的hello字符串println!(s1: {}, s1);
}通过克隆在内存中生成了一份相同的值让新的变量指向它不会导致原有内存值所有权被剥夺。
涉及函数的所有权机制
涉及参数的所有权
对于堆中的变量值所有权会跟随函数参数进行转移
fn main() {let str String::from(param);string_print(str);// 此处str已经无效param字符串的所有权已经被转移到函数中print!(str: {}, str);
}fn string_print(str: String) {println!({}, str);
}基础数据类型变量值不会转移
fn main() {let number 10;number_print(number);println!(number: {}, number);
}
/* 输出
number_print: 10
number: 10
*/fn number_print(num: i32) {println!(number_print: {}, num);
}涉及返回值的所有权
当变量被函数当作返回值返回时所有权也会转移
fn main() {let s1 return_string();println!(s1: {}, s1);
}fn return_string() - String {let str String::from(a string);return str;
}来看一下更复杂的情况返回函数参数
fn main() {let s1 return_string();println!(s1: {}, s1);// 字符串hello所有权从函数中移动到s1let return_str return_string_from_param(s1);// 字符串hello所有权移入函数中再从函数中移出// println!(s1: {}, s1);// 此处s1已经无效之前的内存值已经其他所有者占用println!(return_str: {}, return_str);}fn return_string() - String {let str String::from(hello);return str;
}fn return_string_from_param(str: String) - String {return str;
}还是上面的原则所有权会随函数参数移动函数内的值也会随返回值移动。
引用和租借
引用不会占用所有权通过引用可以访问变量值但不能修改值。
fn main() {let str String::from(hello);let str_reference str;//引用strprintln!(str: {}, str_reference: {}, str, str_reference);// str: hello, str_reference: hello// str_reference.push_str(world);// 报错引用无法修改原有的值
}引用可以理解为类似c指针指向。引用变量会指向变量本身但变量值的所有权还是归原变量所有。通过引用访问变量值是对所有权的“租借”。“租借”只能进行值访问不能进行值修改。
引用指向的是变量本身当变量失去内存值的所有权导致无效时引用也同样无效。如
fn main() {let str String::from(hello);// str获得hello字符串的所有权let str_reference str;//引用strlet str_replace str;// hello字符串所有权被移动str无效println!(str_replace: {}, str_replace);// println!(str_reference: {}, str_reference);//报错引用指向的是strstr已经无效引用也无效
}最后一行关闭注释编译时会报错提示你变量已经被租借无法再移动
boysserver:~/rust_study/demo$ cargo runCompiling demo v0.1.0 (/home/boys/rust_study/demo)
error[E0505]: cannot move out of str because it is borrowed-- src/main.rs:4:23|
2 | let str String::from(hello);// str获得hello字符串的所有权| --- binding str declared here
3 | let str_reference str;//引用str| ---- borrow of str occurs here
4 | let str_replace str;// hello字符串所有权被移动str无效| ^^^ move out of str occurs here
5 | println!(str_replace: {}, str_replace);
6 | println!(str_reference: {}, str_reference);//报错引用指向的是strstr已经无效引用也无效| ------------- borrow later used hereFor more information about this error, try rustc --explain E0505.
error: could not compile demo (bin demo) due to previous error可变引用
普通引用无法修改原有内存值通过可变引用可以修改。需要加关键字 mut。
fn main() {let mut str String::from(hello );// str是mut可变的let str_reference mut str;//可变引用strstr_reference.push_str(world);println!(str_reference: {}, str_reference);//str_reference: hello world
}为了防止出现并发访问问题可变引用不可以多重引用。如
fn main() {let mut string String::from(hello);let s1 mut string;let s2 mut string;println!(s1: {}, s2: {}, s1, s2);//此处打印会报错不能进行多重可变引用
}会提示
cannot borrow string as mutable more than once at a time不可变引用因为无法修改变量的内存值是只读的不会产生竞态因此可以多重引用。
fn main() {let string String::from(hello);let s1 string;let s2 string;println!(s1: {}, s2: {}, s1, s2);// 不可变引用因为只是读取了变量内存值可以进行多重引用
}可变引用和不可变引用不能同时使用。非常容易理解因为可变引用可以修改内存值这会导致竞态、并发访问问题。RUST中是不允许的。
枚举类
枚举是对某类事物可能情况的列举。使用enum关键字定义枚举。
如枚举操作系统类型
enum OperateSystem{Android,IOS,Linux,Unix,Windows,
}打印枚举需要添加宏
#[derive(Debug)]使用 {:?}占位符格式化为字符串打印出来
#[derive(Debug)]
enum OperateSystem{Android,IOS,Linux,Unix,Windows,
}
fn main() {let os OperateSystem::Android;println!(the os is: {:?}, os);//the os is: Android
}枚举成员的属性
rust中的枚举可以包含属性属性可以是不同类型。如
#[derive(Debug)]
enum Shape{Circle{ radius: f64},Rectangle{ width: f32, length: f32},
}
fn main() {let circle Shape::Circle { radius: 3.0};println!(circle: {:?}, circle);let rectangle Shape::Rectangle { width: 3.0, length: 4.0 };println!(rectangle: {:?}, rectangle);/* 打印输出: circle: Circle { radius: 3.0 }rectangle: Rectangle { width: 3.0, length: 4.0 }*/
}上面代码定义了一个Shape形状枚举有两个成员分别是圆形和矩形。
圆形有一个半径radius属性使用f64类型
矩形有两个属性分别是长度length和宽度width都使用f32类型。
枚举类成员的属性也可以是匿名的如
#[derive(Debug)]
enum Shape{Circle(f64),Rectangle(f32, f32),
}
fn main() {let circle Shape::Circle(3.0);println!(circle: {:?}, circle);let rectangle Shape::Rectangle(3.0, 4.0);println!(rectangle: {:?}, rectangle);/* 打印输出: circle: Circle(3.0)rectangle: Rectangle(3.0, 4.0)*/
}枚举匹配
使用match语法进行匹配
#[derive(Debug)]
enum Shape{Circle{ radius: f64},Rectangle{ width: f32, length: f32},
}
fn main() {let circle Shape::Circle { radius: 3.0};let rectangle Shape::Rectangle { width: 3.0, length: 4.0 };match circle {Shape::Circle { radius } println!(circle radius: {}, radius),Shape::Rectangle { width, length } println!(rectangle width: {}, length: {}, width, length)}
}
匿名属性也可以在match语句中临时设置一个参数名如
#[derive(Debug)]
enum Shape{Circle( f64 ),Rectangle( f32, f32),
}
fn main() {let circle Shape::Circle(3.0);match circle {Shape::Circle(radius) {println!(circle radius: {}, radius)},Shape::Rectangle(width, length) {println!(rectangle width: {}, length: {}, width, length)},}
}结构体
结构体方法
结构体方法的形式与函数一致之所以叫结构体方法是因为它的第一个参数是结构体本身的引用。
直接上例子
struct Rectangle {width: f32,length: f32
}impl Rectangle {/* 使用impl为结构体添加方法 */fn get_area(self) - f32 {// 第1个参数是对结构体本身的引用return self.width*self.length}
}
fn main() {let rectangle Rectangle { width: 3.0, length: 4.0 };let area rectangle.get_area();println!(rectangles area: {}, area);
}self参数只是在声明时需要添加调用时不需要传入。
结构体关联函数
结构体关联函数不依赖于结构体实例。
与结构体方法的直观区别是方法没有self作为第1个参数的要求。
struct Rectangle {width: f32,length: f32
}impl Rectangle {/* 使用impl为结构体添加方法 */fn create_instance(width_set: f32, length_set: f32) - Rectangle {Rectangle { width: width_set, length: length_set }}fn get_area(self) - f32 {// 第1个参数是对结构体本身的引用return self.width*self.length}
}
fn main() {let rectangle Rectangle::create_instance(5.0, 6.0);let area rectangle.get_area();println!(rectangles area: {}, area);
}错误处理
不可恢复错误
Rust中的不可恢复错误Unrecoverable Error指的是发生了无法正常处理的错误比如数组越界、空指针解引用等。当程序遇到不可恢复错误时会触发panic恐慌并终止程序的运行。
可以使用panic宏主动触发panic如
fn main() {panic!(just a test panic!);println!(can not run here.);
}运行时会主动触发pianic来终止程序运行
boysserver:~/rust_study/demo$ ./target/debug/demo
thread main panicked at just a test panic!, src/main.rs:2:5
note: run with RUST_BACKTRACE1 environment variable to display a backtrace根据提示加入RUST_BACKTRACE1环境变量运行可以追踪到panic发生的地方
boysserver:~/rust_study/demo$ RUST_BACKTRACE1 ./target/debug/demo
thread main panicked at just a test panic!, src/main.rs:2:5
stack backtrace:0: rust_begin_unwindat /rustc/90c541806f23a127002de5b4038be731ba1458ca/library/std/src/panicking.rs:578:51: core::panicking::panic_fmtat /rustc/90c541806f23a127002de5b4038be731ba1458ca/library/core/src/panicking.rs:67:142: demo::mainat ./src/main.rs:2:53: core::ops::function::FnOnce::call_onceat /rustc/90c541806f23a127002de5b4038be731ba1458ca/library/core/src/ops/function.rs:250:5
note: Some details are omitted, run with RUST_BACKTRACEfull for a verbose backtrace.可恢复错误
可恢复错误指的是那些在运行时可以被程序处理和修复的错误这些错误通常是可以预见和处理的。具体而言可恢复错误可以包括但不限于以下情况
文件操作错误 如文件不存在、无法打开文件、读取或写入文件失败等。网络通信错误 如连接超时、服务器错误、网络断开等。输入验证错误 如用户输入不合法、缺少必要参数等。数据库操作错误 如查询失败、连接错误、事务回滚等。外部资源错误 如磁盘空间不足、内存耗尽等。解析和转换错误 如JSON解析错误、数据类型转换失败等。并发操作错误 如竞争条件、死锁等。配置和环境错误 如配置文件损坏、依赖缺失等。
可恢复错误与不可恢复错误例如硬件故障、内存错误、空指针引用等不同后者通常无法在运行时被程序处理和修复需要采取其他措施如程序重启、报告错误给用户等。
对于可恢复错误使用Result类型可以提供一种良好的错误处理机制使得程序能够检测到错误并根据需要采取适当的处理措施。如
use std::{fs::File, error};fn main() {let fp File::open(./file.txt);match fp {Result::Ok(file) {println!(open {:?} success, file);},Result::Err(error) {println!(open file fail: {}, error);}}
}没有文件时会输出
boysserver:~/rust_study/demo/target/debug$ ./demo
open file fail: No such file or directory (os error 2)Rust代码组织管理
Rust 代码组织中有三个重要的概念Crate(箱)、Package(包)、Module(模块)。
Module
Module模块类似于命名空间用来管理代码的作用域。
使用mod关键字来定义一个Module引用时使用符号 ::
下面是一个示例
mod utility {pub fn print_num(num: i32){println!({}, num);}
}fn main() {utility::print_num(33);
}Module内的成员是有访问权限控制的默认的访问权限是在Module内要在Module外能引用需要加入pub关键字声明表明这个成员是public的。如
mod utility {fn debug_print(num: i32) {println!(num {}, hex: {:#x}, num, num);}pub fn print_num(num: i32){println!({}, num);debug_print(num);// 只有内部才能使用}
}fn main() {utility::print_num(33);// utility::debug_print(33);/* module外使用会报错 */
}Module内可以定义其他的Module形成层次。如
mod utility {pub mod printer{pub fn println(num: i32){println!({}, num);}}pub mod calculator{pub fn get_numbers_sum(num1: i32, num2: i32) - i32{num1num2}}
}fn main() {let sum utility::calculator::get_numbers_sum(1, 1);utility::printer::println(sum);
}默认的Module
创建.rs文件时就会产生一个与文件名同名的Module。如
在ulitity.rs中代码如下
pub mod printer{pub fn println(num: i32){println!({}, num);}
}
pub mod calculator{pub fn get_numbers_sum(num1: i32, num2: i32) - i32{num1num2}
}虽然我们没有使用mod关键字声明一个Module来包含这两个module但默认已经使用了文件名作为默认的Module。
在main.rs中声明一下即可调用
mod utility;/* 声明Module */
fn main() {let sum utility::calculator::get_numbers_sum(1, 1);utility::printer::println(sum);
}引用Module
使用use关键字对一个Module或Module内的成员进行引用。如
mod utility {pub mod printer{pub fn println(num: i32){println!({}, num);}}pub mod calculator{pub fn get_numbers_sum(num1: i32, num2: i32) - i32{num1num2}}
}use utility::printer;/* 引用printer module */
use utility::calculator::get_numbers_sum;/* 引用calculator内的成员函数: get_numbers_sum */
fn main() {let sum get_numbers_sum(1, 1);/* 使用时不用再注明前面的module */printer::println(sum);
}
Crate
Crate 是 Rust 的构建块用于组织和管理代码它可以包含一个或多个Module模块和其他项如函数、结构体、枚举等。
Crate可以分为2种类型
lib即库类型Cratebin即二进制Crate
库Crate可以生成库文件给其他代码共享、复用二进制Crate生成的是可执行文件。
通常情况下Rust 项目的 Crate 遵循一种约定俗成的目录结构有助于组织和管理代码。
以下是一个常见的 Rust 项目目录结构示例
my_project/
├── src/ # 存放源代码
│ ├── main.rs # 二进制 Crate 的入口文件
│ └── lib.rs # 库 Crate 的入口文件
├── tests/ # 存放测试代码
├── examples/ # 存放示例代码
├── Cargo.toml # 描述包和依赖关系的文件
└── README.md # 项目文档Package
Package包包含了Crate可以同时包含多个Crate。但多个Crate里面只能有一个库Crate可以有多个二进制Create。
Rust面向对象
trait特性
trait特性简单地说就是一组通用的属性当需要使用这些特性时只需要通过impl关键字实现他们即可拥有这些特性。
如
trait Person{/* 定义一个关于Person的特性 */fn get_name(self) - str;/* 获取姓名的接口 */
}上面定义了一个Person的特性特性包含一个获取姓名的接口。当需要获得这个特性时即可通过impl关键字实现如
trait Person{/* 定义一个关于Person的特性 */fn get_name(self) - str;/* 获取姓名的接口 */
}
struct XiaoMing{name: String,
}
struct ZhangSan{name: String
}impl Person for XiaoMing {/* 为xiaoming实现Person特性 */fn get_name(self) - str {self.name}
}
impl Person for ZhangSan {/* 为zhangsan实现Person特性 */fn get_name(self) - str {self.name}
}fn main() {let ming XiaoMing{name: String::from(mingming),};println!(xiaomings name: {}, ming.get_name());let zhang ZhangSan{name: String::from(zhangsan)};println!(zhang_sans name: {}, zhang.get_name());
}默认特性
trait可以实现定义的接口如果实现这个特性的类型没有对该接口覆盖则调用这个接口时还是调用trait中默认接口。如
trait Person{/* 定义一个关于Person的特性 */fn get_name(self) - str;/* 获取姓名的接口 */fn tell_age(self){/* 默认实现未实现会调用此默认接口 */println!(unkown age);}
}
struct XiaoMing{name: String,
}
struct ZhangSan{name: String
}impl Person for XiaoMing {/* 为xiaoming实现Person特性 */fn get_name(self) - str {self.name}fn tell_age(self){println!(xiaoming is 18 years old);}
}
impl Person for ZhangSan {/* 为zhangsan实现Person特性 */fn get_name(self) - str {self.name}
}fn main() {let ming XiaoMing{name: String::from(mingming),};ming.tell_age();let zhang ZhangSan{name: String::from(zhangsan)};zhang.tell_age();/* 输出结果xiaoming is 18 years oldunkown age*/
}小结
train特性有点类似cpp中的虚函数和纯虚函数没有实现的接口在impl时必须实现。有默认实现的如果impl未实现则会调用默认的实现。