Cargo Workspaces
Workspace(工作空间)是一组共享同一个Cargo.lock文件和输出目录的包
Vorkspace例子:
- 一个Binary crate: main
- 两个Library Crate:
例:
在项目顶级文件夹下创建Cargo.toml:
1 2 3 4 5
| [workspace]
members = [ "adder" ]
|
使用命令:
此时得到顶级文件夹下adder的子文件夹,该文件夹含有自己的toml和src。
添加一个lib_crate:
此时顶级Cargo.toml:
1 2 3 4 5 6
| [workspace]
members = [ "adder", "add_one", ]
|
让adder依赖add_one
adder/Cargo.toml:
1 2 3 4 5 6 7
| [package] name = "adder" version = "0.1.0" edition = "2024"
[dependencies] add_one ={path = "../add_one"}
|
解决解析器警告
Cargo.toml:
1 2 3 4 5 6 7 8
| [workspace]
members = [ "adder", "add_one", ]
resolver = "3"
|
指定执行:
workspace中只有顶层一个Cargo.lock,为了解决项目同依赖的问题。
但是你仍然需要在每个子文件夹下的 Cargo.toml单独标明互相使用的外部包,比如adder依赖add_one,而add_one依赖rand,所以你也得在adder中写明依赖于rand。
Smart Pointers 智能指针
指针:比如引用,只有指向地址的功能
智能指针:带有额外的元数据和功能
引用仅借用数据,智能指针通常拥有数据。
Box
允许你将数据存储在Heap上
场景:
- 在需要知道确切大小的上下文中,却使用一个在编译时无法确定大小的类型
- 有大量数据,想要转移所有权,但需确保在转移时数据不会被复制
- 当你想要拥有一个值,且你只关心它是否实现了某Trait,而不是具体的类型
例:
1 2 3 4 5 6 7 8 9 10 11 12 13
| use List::{Cons, Nil};
fn main() { let b = Box::new(5); println!("{b}");
let list = Cons(1, Box::new(Cons(2, Box::new(Cons(3, Box::new(Nil)))))); }
enum List { Cons(i32, Box<List>), Nil }
|
Deref Trait
- Deref trait允许你自定义解引用运算符 * 的行为
- 通过适当实现Deref trait,可以让智能指针像普通引用来使用
- 你编写的用于引用的代码,也能用于智能指针
实现 Deref trait 后可以把一个类型像引用一样来使用。
例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| use std::ops::Deref;
fn main() { let x = 5; let y = MyBox::new(x);
assert_eq!(5, x); assert_eq!(5, *y); }
struct MyBox<T>(T);
impl<T> MyBox<T> { fn new(x: T) -> MyBox<T> { MyBox(x) } }
impl <T> Deref for MyBox<T> { type Target = T; fn deref(&self) -> &Self::Target { &self.0 } }
|
Deref coercion 隐式解引用转换
- 隐式解引用转换能将实现了Deref trait的类型的引用转换为另一个类型的引用
- 例如:由于String实现了返回&str的Deref trait,所以可以将&String转换为&str
- 编写函数和方法调用时,不需要添加太多显式的&和*
- 允许编写能同时适用于引用或智能指针的代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| use std::ops::Deref;
fn main() { let m = MyBox::new(String::from("Rust")); hello("Hello World"); hello(&m); }
fn hello(name: &str) { println!("Hello, {}!", name); }
struct MyBox<T>(T);
impl<T> MyBox<T> { fn new(x: T) -> MyBox<T> { MyBox(x) } }
impl <T> Deref for MyBox<T> { type Target = T; fn deref(&self) -> &Self::Target { &self.0 } }
|
Deref Coercion与可变性
- Deref:对不可变引用的*的重载
- DerefMut:对可变引用的*的重载
三种解引用转换场景:
- &T->&U (T:Deref<Target=U>)
- &mut T->&mut U (T:DerefMut<Target=U>)
- &mut T->&U (T:Deref<Target=U>)
Drop Trait
- 用于定义一个值即将超出作用域时的清理行为
- 实现智能指针时几乎总是会用到Drop trait的功能
- Rust 编译器会自动插入Drop 实现中的代码,避免资源泄漏
- Drop trait 只要求实现 drop 方法,参数是对 self 的可变引l用
- Drop trait在prelude里,无需手动引l入
例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| struct CustomSmartPointer { data: String, }
impl Drop for CustomSmartPointer { fn drop(&mut self) { println!("{}", self.data); } }
fn main() { let c = CustomSmartPointer { data: String::from("my stuff") };
let d = CustomSmartPointer { data: String::from("other stuff") }; println!("Start"); }
|
释放顺序与创建顺序相反。
使用std::mem:.drop可提前drop值
- Rust不允许手动调用Drop trait的drop方法
- 如果你想在值的作用域结束前强制丢弃它,你必须调用标准库提供的 std:mem:drop函数
- std:mem:drop的调用不会干扰Rust的自动清理机制
- 因为它通过接管值的所有权(ownership)并在调用后销毁它,避免了双重释放(double free)问题
手动销毁某变量:
Rc 引用计数智能指针
Reference Counting
- 有些情况下,一个值可能有多个所有者
- RC 可以开启“多重所有权”
Rc的使用场景
- 想在Heap上分配一些数据,供程序的多个部分读取,但在编译时无法确定哪个部分会最后完成对数据的使用
- 只可用于单线程场景
例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| use crate::List::{Cons, Nil}; use std::rc::Rc;
enum List { Cons(i32, Rc<List>), Nil }
fn main() { let a = Rc::new(Cons(5, Rc::new(Cons(10, Rc::new(Nil))))); println!("{}", Rc::strong_count(&a)); let b = Cons(3, Rc::clone(&a)); println!("{}", Rc::strong_count(&a)); { let c = Cons(4, Rc::clone(&a)); println!("{}", Rc::strong_count(&a)); } println!("{}", Rc::strong_count(&a)); }
|
RefCell 和内部可变性模式
- 对数据不可变引用时也可以修改数据
- 数据结构内部使用unsafe代码来绕过Rust通常的规则
- unsafe代码:手动检查规则,而不是依赖编译器
- 只有当能保证在运行时借用规则被遵守时,才可使用内部可变性模式的类型
在运行时通过RefCell<-T>强制执行借用规则
- RefCell类型表示对其所持有数据的单一所有权
- 回顾借用规则:
- 在任何时刻,要么拥有一个可变引用,要么任意数量的不可变引用
- 引用必须始终有效
只适用单线程
当确信你的代码是安全的,但是编译器无法理解或保证时你才能使用这个。
例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70
| pub trait Messenger { fn send(&self, msg: &str); }
pub struct LimitTracker<'a, T: Messenger> { messenger: &'a T, value: usize, max: usize }
impl<'a, T> LimitTracker<'a, T> where T: Messenger, { pub fn new(messenger: &'a T, max: usize) -> LimitTracker<'a, T> { LimitTracker { messenger, value: 0, max } }
pub fn set_value(&mut self, value: usize) { self.value = value;
let percentage_of_max = self.value as f64 / self.max as f64;
if percentage_of_max >= 1.0 { self.messenger.send("Error: 1"); } else if percentage_of_max >= 0.9 { self.messenger.send("Error: 2"); } else if percentage_of_max >= 0.75 { self.messenger.send("Error: 3"); } } }
#[cfg(test)] mod tests { use super::*; use std::cell::RefCell;
struct MockMessenger { sent_messages: RefCell<Vec<String>>, }
impl MockMessenger { fn new() -> MockMessenger { MockMessenger { sent_messages: RefCell::new(vec![]), } } }
impl Messenger for MockMessenger { fn send(&self, message: &str) { self.sent_messages.borrow_mut().push(String::from(message)); } }
#[test] fn it_sends_on_over_75_percent_warning_message() { let mock_messenger = MockMessenger::new(); let mut limit_tracker = LimitTracker::new(&mock_messenger, 100);
limit_tracker.set_value(80);
assert_eq!(mock_messenger.sent_messages.borrow().len(), 1); } }
|
组合Rc 和 RefCell 实现多个所有者对可变数据的访问
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| use crate::List::{Cons, Nil}; use std::rc::Rc; use std::cell::{Ref, RefCell};
#[derive(Debug)]
enum List { Cons(Rc<RefCell<i32>>, Rc<List>), Nil }
fn main() { let value = Rc::new(RefCell::new(5));
let a = Rc::new(Cons(Rc::clone(&value), Rc::new(Nil)));
let b = Cons(Rc::new(RefCell::new(3)), Rc::clone(&a)); let c = Cons(Rc::new(RefCell::new(4)), Rc::clone(&a));
*value.borrow_mut() += 10;
println!("{a:?}"); println!("{b:?}"); println!("{c:?}"); }
|