所有权
三条基本规则
所有权有以下三条规则:
- Rust 中的每个值都有一个变量,称为其所有者。
- 一次只能有一个所有者。
- 当所有者不在程序运行范围时,该值将被删除。
变量范围
变量范围是变量的一个属性,其代表变量的可行域,默认从声明变量开始有效直到变量所在域结束。
{
// 在声明以前,变量 s 无效
let s = "runoob";
// 这里是变量 s 的可用范围
}
// 变量范围已经结束,变量 s 无效变量与数据交互的方式
变量与数据交互方式主要有移动(Move)和克隆(Clone)两种
移动
- 多个变量可以在 Rust 中以不同的方式与相同的数据交互
- 如果将变量当作参数传入函数,那么它和移动的效果是一样的
- 被当作函数返回值的变量所有权将会被移动出函数并返回到调用函数的地方,而不会直接被无效释放
fn main() {
let x = 5;
let y = x; // x 的指向资源已经移动到了 y, 后续不能再使用 x 了(因为 x 指向的资源被 y 占用了)
let s = String::from("hello"); // s 被声明有效
let z = takes_ownership(s); // s 的值被当作参数传入函数, 从这里开始已经无效
println!("z: {}", z);
}
fn takes_ownership(some_string: String) {
println!("{}", some_string); // some_string 指向的资源被占用,移动
let other_string = String::from("world"); // 内部字符串
other_string // 当作返回值移动出函数
} // 函数结束, 参数 some_string 在这里释放克隆
Rust会尽可能地降低程序的运行成本,所以默认情况下,长度较大的数据存放在堆中,且采用移动的方式进行数据交互。但如果需要将数据单纯的复制一份以供他用,可以使用数据的第二种交互方式——克隆。
fn main() {
let s1 = String::from("hello");
let s2 = s1.clone();
println!("s1 = {}, s2 = {}", s1, s2);
}引用(租借)
引用不会获得值的所有权。引用只能租借(Borrow)值的所有权。 引用本身也是一个类型并具有一个值,这个值记录的是别的值所在的位置但引用不具有所指值的所有权 创建一个引用, 匹配并解引用一个值
fn main() {
let s1 = String::from("hello");
let mut s2 = &s1; // s2 只是引用 s1, s1 仍可以用
println!("{}", s1);
}垂悬引用
垂悬引用,指一个引用,指向一个已经释放的资源
fn main() {
let reference_to_nothing = dangle();
}
fn dangle() -> &String {
let s = String::from("hello");
&s
}ref
获取所有权,只想在解构时借用 let ref x = y 可以写成 let x = &y
错误
不可恢复错误
不可恢复的错误一定会导致程序受到致命的打击而终止运行。
fn main() {
panic!("error occured");
println!("Hello, Rust");
}运行结果:
thread 'main' panicked at 'error occured', src\main.rs:3:5
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace.可恢复的错误
在 Rust 中通过 Result<T, E> 枚举类作返回值来进行异常表达
enum Result<T, E> {
Ok(T),
Err(E),
}- 如果想使一个可恢复错误按不可恢复错误处理,Result 类提供了两个办法:unwrap() 和 expect(message: &str) :
use std::fs::File;
fn main() {
let f1 = File::open("hello.txt").unwrap();
let f2 = File::open("hello.txt").expect("Failed to open.");
}这段程序相当于在 Result 为 Err 时调用 panic! 宏。两者的区别在于 expect 能够向 panic! 宏发送一段指定的错误信息。
- 可恢复的错误的传递 可以用 Err 或者 ? 来传递
fn f(i: i32) -> Result<i32, bool> {
if i >= 0 { Ok(i) }
else { Err(false) } // Err 传递
}
fn g(i: i32) -> Result<i32, bool> {
let t = f(i)?; // ? 传递
Ok(t)
}
fn main() {
let r = g(10000);
if let Ok(v) = r {
println!("Ok: g(10000) = {}", v);
} else {
println!("Err");
}
}异步编程
Future
Future 是 Rust 中表示异步操作的抽象。它是一个可能还没有完成的计算,将来某个时刻会返回一个值或一个错误。
async/await
- async 关键字用于定义一个异步函数,它返回一个 Future。
- await 关键字用于暂停当前 Future 的执行,直到它完成。
// 引入所需的依赖库
use tokio;
use tokio::time::{self, Duration};
// 异步函数,模拟异步任务
async fn async_task() -> u32 {
// 模拟异步操作,等待 1 秒钟
time::sleep(Duration::from_secs(1)).await;
// 返回结果
42
}
// 异步任务执行函数
async fn execute_async_task() {
// 调用异步任务,并等待其完成
let result = async_task().await;
// 输出结果
println!("Async task result: {}", result);
}
// 主函数
#[tokio::main]
async fn main() {
println!("Start executing async task...");
// 调用异步任务执行函数,并等待其完成
execute_async_task().await;
println!("Async task completed!");
}运行异步函数
- tokio::main/tokio::spawn, 用于在异步运行时中启动新的异步任务
- task::block_on/futures::executor::block_on 等函数来执行异步任务
use async_std::task;
fn main() {
task::block_on(print_hello());
}结构体
Rust 中的结构体(Struct)与元组(Tuple)都可以将若干个类型不一定相同的数据捆绑在一起形成整体
结构体定义
struct Rectangle {
width: u32,
height: u32,
}
let runoob = Rectangle {
width: 3,
height: 6
};结构体方法
方法(Method)和函数(Function)类似,只不过它是用来操作结构体实例的。
impl Rectangle {
fn area(&self) -> u32 {
self.width * self.height
}
}
fn main() {
let rect1 = Rectangle { width: 30, height: 50 };
println!("rect1's area is {}", rect1.area());
}结构体函数
结构体函数在 impl 块中却没有 &self 参数
impl Rectangle {
fn create(width: u32, height: u32) -> Rectangle {
Rectangle { width, height }
}
}
fn main() {
let rect = Rectangle::create(30, 50);
println!("{:?}", rect);
}元组结构体
元组结构体是一种形式是元组的结构体. 它存在的意义是为了处理那些需要定义类型(经常使用)又不想太复杂的简单数据
struct Color(u8, u8, u8);
struct Point(f64, f64);
let black = Color(0, 0, 0);
let origin = Point(0.0, 0.0);特性
特性可以定义方法作为默认方法, 也可以为空. 结构体既可以重新定义方法,也可以不重新定义方法使用默认的方法
trait Descriptive {
fn describe(&self) -> String {
String::from("[Object]")
}
}
struct Person {
name: String,
age: u8
}
impl Descriptive for Person {
/// 可实现也不可以实现
fn describe(&self) -> String {
format!("{} {}", self.name, self.age)
}
}
fn main() {
let cali = Person {
name: String::from("Cali"),
age: 24
};
println!("{}", cali.describe());
}特性做参数
多情况下我们需要传递一个函数做参数,例如回调函数、设置按钮事件等。在 Rust 中可以通过传递特性参数来实现。
fn output(object: impl Descriptive) {
println!("{}", object.describe());
}特性作类型表示时如果涉及多个特性,可以用 + 符号表示
fn notify(item: impl Summary + Display)
fn notify<T: Summary + Display>(item: T)或者
fn some_function<T, U>(t: T, u: U) -> i32
where T: Display + Clone,
U: Clone + Debug特性做返回值
fn some_function(bool bl) -> impl Descriptive {
if bl {
return A {};
} else {
return B {};
}
}智能指针
Box<T>
在堆上分配一块内存,并将值存储在这个内存中
let b = Box::new(5);
println!("b = {}", b);Mutex<T>
互斥锁,它保证了在任何时刻只有一个线程可以访问 Mutex 内部的数据
use std::sync::Mutex;
let m = Mutex::new(5);
let mut data = m.lock().unwrap();Arc<T>
使用原子操作来更新引用计数, 可以安全地在多线程环境中共享数据
use std::sync::Arc;
let data = Arc::new(5);
let data_clone = Arc::clone(&data);其他常见锁
Rc<T>适用于单线程环境下的数据共享RefCell<T>允许在不可变引用的情况下修改数据RwLock<T>是一种读取-写入锁,允许多个读取者同时访问数据,但在写入时是排他的Weak<T>是Rc<T>的非拥有智能指针,它不增加引用计数,用于解决循环引用问题
闭包
闭包的声明
let closure_name = |参数列表| 表达式或语句块;
let calculate = |a, b, c| a * b + c;
let result = calculate(1, 2, 3);捕获外部变量
闭包可以捕获周围环境中的变量,这意味着它可以访问定义闭包时所在作用域中的变量。 默认情况下,闭包会借用它捕获的环境中的变量,这意味着闭包可以使用这些变量,但不能改变它们的所有权
let x = 10;
let add_x = |y| x + y;
println!("{}", add_x(5)); // 输出 15
println!("{}", x); // 仍然可以使用 x强制移动
通过 move 关键字获取外部变量的所有权,或者通过借用的方式获取外部变量的引用
let s = String::from("hello");
let print_s = move || println!("{}", s);
print_s(); // 输出 "hello"
// println!("{}", s); // 这行代码将会报错,因为 s 的所有权已经被转移给了闭包闭包的特性
- 闭包可以作为函数参数
- 闭包可以作为返回值
闭包特性
- Fn: 不需要修改捕获的变量,闭包可以多次调用。
- FnMut: 需要修改捕获的变量,闭包可以多次调用。
- FnOnce: 只需要捕获所有权,闭包只能调用一次。
数组和元组
- 数组只能是同一类类型
- 元组可以是不同类类型