rust入门

发布时间:2026/5/25 3:23:44

rust入门 1所有权什么是所有权拥有什么资源被哪个变量拥有。1、移动赋值如下赋值操作中在c中这样的操作会进行拷贝构造而在rust中这样会移动。c默认情况下给了程序员最大的自由度而rust正好相反。let s1 String::from(hello);let s2 s1; // 所有权从s1移动到s2s1已经是无效的了不能再使用对于基本数据类型整型、浮点型、bool、char等数据保存到栈上默认支持Copy语义赋值操作执行的是拷贝而不是移动。可以把这些保存在栈上的基本数据类型看做无资源的数据无资源那么也谈不上所有权。每一条规律都不是绝对的。let data1 10;let data2 data1;println!(data1: {}, data2: {}, data1, data2);个人感觉这种赋值操作直接从语法上不支持直接赋值必须用move操作才可以这样更明确。2、调用函数①值传递与赋值类似也是会转移所有权。赋值和值传递就类似于c中的unique_ptr,即独占指针语义是类似的。rust的变量默认都是unique的并且是const的。把权限开放到最小实现精细化的控制。②引用传递和c有不一样的地方c的函数形参如果接受引用那么就是std::string str这样的方式函数调用处的实参不需要使用来指定rust中的函数声明以及实参调用的地方都需要使用来指定是一个引用。否则就会报错。个人认为rust这种语法更明确不会有歧义。let s7 String::from(borrowing);let len calculate_length(s7); // 传递引用不转移所有权fn calculate_length(s: String) - usize {s.len()} // s离开作用域但因为它没有所有权所以不会drop任何东西引用没有所有权3、引用赋值引用在rust中也叫借用因为有了所有权所以借用的概念更加贴切更加形象。①默认是不可变引用可变应用需要加mut即mut②变量本身是mut即可变的这个变量才可以被可变引用如果这个变量不是可变的那么就不能被可变引用。mut变量可以被不可变引用也可以被可变引用非mut变量只能被不可变引用。最终决定于所有权借用的权限要小于原本的权限。④在同一个作用域只能有一个可变引用或者1个或多个不可变引用可变引用和不可变引用不能同时存在。这个约束类似于linux中的读写锁在同一时刻写锁和读锁不能同时加锁同一时刻只能有一个写锁同一时刻可以有多个读锁。linux内核中中断中不能使用mutex中断不能睡眠没有上下文不能回来中断和线程的临界资源只能使用自旋锁自旋锁保护的区域不能睡眠。⑤当不可变引用和可变引用同时存在或者多个可变引用同时存在时引用的作用域需要根据实际的代码情况进行分割代码不能存在自相矛盾的地方编译器会根据使用情况自动判断是否合理。比如下边的代码打印r3的话就说明r3的作用域要持续到println这一行但是在中间又有个可变引用r4这就是自相矛盾。如果println这一行打印r4那么便没有自相矛盾r3的作用域在r4这一行之前就结束了。let r3 mut s9; // 可变引用 - 在不可变引用作用域结束后允许let r4 mut s9;println!(r3: {}, r3);let mut s9 String::from(rules);let r1 s9; // 不可变引用let r2 s9; // 另一个不可变引用 - 允许println!(r1: {}, r2: {}, r1, r2);let r3 mut s9; // 可变引用 - 在不可变引用作用域结束后允许println!(r3: {}, r3);//println!(r3: {}, r2: {}, r3, r2);//同时使用可变引用和不可变引用4、可以使用clone方法类似于c中的拷贝构造fn main() { // 示例1: 所有权转移 println!( 所有权转移 ); let s1 String::from(hello); // s1 String::from( world); let s2 s1; // 所有权从s1移动到s2 //println!({}, s1); // 这行会编译错误s1不再有效 println!(s2: {}, s2); // 示例2: 克隆深拷贝 println!(\n 克隆 ); let s3 String::from(world); let s4 s3.clone(); // 创建数据的完整副本 println!(s3: {}, s4: {}, s3, s4); // 两者都有效 // 示例3: 函数调用中的所有权 println!(\n 函数中的所有权 ); let s5 String::from(rust); takes_ownership(s5); // s5的所有权移动到函数中 //println!({}, s5); // 错误s5不再有效 let s6 gives_ownership(); // 从函数获取所有权 println!(从函数获取的: {}, s6); // 示例4: 引用借用 println!(\n 引用借用); let s7 String::from(borrowing); let len calculate_length(s7); // 传递引用不转移所有权 println!({} 的长度是: {}, s7, len); // s7仍然有效 // 示例5: 可变引用 println!(\n 可变引用 ); let mut s8 String::from(hello); change_string(s8); println!(修改后: {}, s8); // 示例6: 引用规则验证 println!(\n 引用规则 ); let mut s9 String::from(rules); let r1 s9; // 不可变引用 let r2 s9; // 另一个不可变引用 - 允许 println!(r1: {}, r2: {}, r1, r2); let r3 mut s9; // 可变引用 - 在不可变引用作用域结束后允许 println!(r3: {}, r3); //println!(r3: {}, r2: {}, r3, r2);//同时使用可变引用和不可变引用 } fn takes_ownership(some_string: String) { println!(在函数中: {}, some_string); } // some_string离开作用域drop被调用 fn gives_ownership() - String { let some_string String::from(ownership); some_string // 所有权转移给调用者 } fn calculate_length(s: String) - usize { s.len() } // s离开作用域但因为它没有所有权所以不会drop任何东西 fn change_string(s: String) { //s.push_str(, world!); }rust函数声明的格式fn 函数名(形参名形参类型)-返回值类型{}rust函数返回只需要把要返回的结果放在最后一行然后这行没有以;结尾这就表示返回。如果函数不是在最后一行返回那么需要使用return关键字。最后一行使用return也是可以的。切片如下代码s2就是切片切片天生就是引用。切片是针对String或者数据的其中一段的引用String底层也是一个数组在c中也是这样的。fn main() { let mut s1 String::from(hello world); let s2 s1[0..5]; println!(s1 {}, s2 {},s1,s2); s1.clear(); }字符串切片的类型用str来表示。String表示字符串引用这两者还是不一样的。str类型的形参也可以接收字符串引用。fn main() { let mut s String::from(hello world); let word first_word(s); println!(first word {}, word); } fn first_word(s: str) - str { let bytes s.as_bytes(); for (i, item) in bytes.iter().enumerate() { if item b { return s[0..i]; } } s[..] }2泛型泛型的概念在c中也有泛型就是将类型参数化。泛型的优点是提高代码复用率相同逻辑、不同参数类型的只需要实现一份就可以。泛型虽好但是在实际项目中尽量少用因为泛型会导致代码可读性变差维护难度提高比如使用gdb看到的调用栈函数很长。泛型只是减少了代码量但并不会减少二进制的数量针对每种类型二进制中都会有一份代码。函数模板类模板结构体模板概念落脚点是模板表示一个模板用来生成具体的函数、类、结构体。模板函数模板类模板结构体概念落脚点事函数、类、结构体是模板生成的对象。落脚点是模板那么就是一个模板否则就是模板实例化出来的一个对象。2.1函数泛型在函数名之后用来指定泛型的类型泛型的参数一般用T来表示。PartialOrd要求数据类型是可以比较大小的。use std::cmp::Ordering; fn find_maxT: PartialOrd(a: T, b: T) - T { if a b { a } else { b } } fn main() { let a 3; let b 5; println!({}, find_max(a, b)); let x a; let y b; println!({}, find_max(x, y)); }c中的函数模板使用如下1需要在函数前生成template而rust是在函数名后的中声明参数类型2c中调用模板函数时可以在函数名后通过指定参数类型(当出现模糊时必须指定没有模糊时则编译器可以自己推导)也可以不指定rust中都不需要指定rust也不支持这样的用法。#include iostream templatetypename T T find_max(T a, T b) { return a b ? a : b; } int main() { std::cout find_max(1,2) std::endl; std::cout find_max(1.0,2.0) std::endl; std::cout find_maxint(1,2.0) std::endl; std::cout find_max(a,b) std::endl; return 0; }c中的函数泛型和rust中的函数泛型有一个很重要的区别如下rust的函数模板虽然只有一个实例并且是比较u32的大小没有其它实例那么编译器也会报错不确定类型T是否支持操作。rust不如c灵活。use std::cmp::Ordering; fn find_maxT(a: T, b: T) - T { if a b { a } else { b } } fn main() { let a : u32 3; let b : u32 5; println!({}, find_max(a, b)); //let x a; //let y b; //println!({}, find_max(x, y)); }而如下c代码只有当出现这样的实例的时候才会进行检查。#include iostream templatetypename T T find_max(T a, T b) { return a b ? a : b; } class A { public: A() {} }; int main() { //std::cout find_max(1,2) std::endl; std::cout find_max(1.0,2.0) std::endl; std::cout find_maxint(1,2.0) std::endl; std::cout find_max(aaaaa,ccccc) std::endl; A a1; A a2; std::cout find_max(a1, a2) std::endl; return 0; }2.2结构体泛型结构体泛型的声明方式与函数类似均是在名字后边使用来声明。struct PointT { x: T, y: T, } fn main() { let integer Point{x:5,y:6}; let float Point{x:1.0,y:2.0}; }c中的结构体模板#include iostream template typename T struct Point { T x; T y; }; int main() { Pointint p1; Pointdouble p2; p1.x 1; p1.y 2; p2.x 1.0; p2.y 2.0; return 0; }在c中使用结构体模板声明时必须指定类型在rust中不需要这样指定。3.3枚举泛型rust枚举泛型如下所示。#[derive(Debug)] enum MyOptionT { Some(T), None, } #[derive(Debug)] enum MyResultT,E { Ok(T), Err(E), } fn main() { let some_number : MyOptioni32 MyOption::Some(5); let no_number : MyOptioni32 MyOption::None; let success: MyResultString, str MyResult::Ok(操作成功.to_string()); let fail: MyResultString, str MyResult::Err(出错了); println!({:?},{:#?}, success, fail); }c中的枚举不支持泛型。2.4c中泛型的概念2.4.1c类模板为什么不能编译到动态库中来使用c类模板为什么不能编译到动态库中来使用_c模板类变成动态库-CSDN博客2.4.2代码膨胀现象对于一个模板来说编译出的目标文件中最终有多少个这个模板的实例如下代码虽然很少这样使用但会产生5个实例。#include iostream templatetypename T, int num class A { public: A() {} void run(T data) { std::cout data: data ,num: num std::endl; } }; int main() { Aint, 1 o1; Aint, 2 o2; Aint, 3 o3; Aint, 4 o4; Aint, 5 o5; o1.run(1); o2.run(2); o3.run(3); o4.run(4); o5.run(5); return 0; }通过objdump查看符号可以看到类A的构造函数和run函数均有5个。如下代码1个实例#include iostream templatetypename T, int num 10 class A { public: A() {} void run(T data) { std::cout data: data std::endl; } }; int main() { Aint o1; Aint o2; Aint o3; Aint o4; Aint o5; o1.run(1); o2.run(2); o3.run(3); o4.run(4); o5.run(5); return 0; }下边的代码4个实例#include iostream templatetypename T, int num 10 class A { public: A() {} void run(T data) { std::cout data: data std::endl; } }; int main() { Aint, 1 o1; Aint, 1 o2; Aint, 2 o3; Aint, 3 o4; Aint, 4 o5; o1.run(1); o2.run(2); o3.run(3); o4.run(4); o5.run(5); return 0; }2.4.3模板的特化本来模板就是为了减少代码但是又出现了模板特化模板特化跟我们不是用模板直接写几个重载函数是类似的。本来是一种进步的技术但是也不能完全把旧的技术代替。类似于引用c中的引用是为了简化指针的使用但是现在随着左值引用右值引用万能引用概念的出现个人认为引用的复杂度要高于c语言中的指针。类似于这样的矛盾的现象还有很多。模板的特化分为全部特化和部分特化全部特化模板的所有参数的类型都指定部分特化是指部分模板参数特化。重载函数 全部特化模板 部分特化模板 非特化模板。(1)被特化的参数不能再出现在template列表中(2)函数模板特化直接在函数的形参中确定类型就可以类模板的特化需要在类名的后边使用指定类型下边的代码main函数的第一行调用会打印overloading第二行调用会打印part float instance。#include iostream bool isEqual(int a, int b, char c, char d) { std::cout overloading std::endl; return ((a b) (c d)); } template typename T, typename U bool isEqual(T a, T b, U c, U d) { std::cout template std::endl; return ((a b) (c d)); } template typename T bool isEqual(T a, T b, char c, char d) { std::cout part char instance std::endl; return ((a b) (c d)); } template bool isEqual(int a, int b, char c, char d) { std::cout all instance std::endl; return ((a b) (c d)); } template typename T bool isEqual(T a, T b, float c, float d) { std::cout part float instance std::endl; return ((a b) (c d)); } int main() { isEqual(1,1,a,a); isEqual(1,1,1.0f,1.0f); return 0; }3traitrust中的trait类似于c中的虚函数。trait可以有默认实现也可以没有默认实现类似于c中的虚函数和纯虚函数。trait和虚函数用来定义接口规范接口是实现多态的基础。c中的虚函数实现多态通过继承的方式来实现rust中trait实现多态类似于组合的方式更加分散。3.1example1trait xxx声明一个trait一个trait中可以声明一个或者多个方法2trait中的方法可以有默认实现也可以没有默认实现没有默认实现的trait那么trait实现中必须得实现3impl xxxtrait for xxxpub trait Summary { fn summarize_author(self) - String; fn summarize(self) - String { format!(Read more from {}..., self.summarize_author()) } } pub struct NewsArticle { pub headline : String, pub location : String, pub author : String, pub content : String } pub struct Tweet { pub username : String, pub content : String, pub author : String, pub reply : bool, pub retweet : bool } impl Summary for NewsArticle { fn summarize_author(self) - String { format!({},self.author) } } impl Summary for Tweet { fn summarize_author(self) - String { format!({},self.author) } fn summarize(self) - String { format!(summarize of Tweet {}, self.summarize_author()) } } fn main() { let news NewsArticle { headline : String::from(news—headline), location : String::from(news-location), author : String::from(news-author), content : String::from(news-content) }; println!(news article: {}, news.summarize()); let tweet Tweet { username : String::from(tweet-username), content : String::from(tweet-content), author : String::from(tweet-author), reply : false, retweet : false }; println!(tweet: {}, tweet.summarize()); }3.2trait约束在泛型中约束类型。fn largestT: PartialOrd Copy(list : [T]) - T { let mut largest list[0]; for item in list.iter() { if item largest { largest item; } } largest } fn main() { let number_list vec![1,2,3,4,5]; let result largest(number_list); println!(largest number {}, result); let char_list vec![a,b,c,d,e]; let result largest(char_list); println!(largest char {}, result); }3.3trait参数和返回值report的形参是traitmake_news_article的返回值是trait。pub trait Summary { fn summarize_author(self) - String; fn summarize(self) - String { format!(Read more from {}..., self.summarize_author()) } } pub struct NewsArticle { pub headline : String, pub location : String, pub author : String, pub content : String } pub struct Tweet { pub username : String, pub content : String, pub author : String, pub reply : bool, pub retweet : bool } impl Summary for NewsArticle { fn summarize_author(self) - String { format!({},self.author) } } impl Summary for Tweet { fn summarize_author(self) - String { format!({},self.author) } fn summarize(self) - String { format!(summarize of Tweet {}, self.summarize_author()) } } fn report(item : impl Summary) { println!(report {}, item.summarize()); } fn make_news_article() - impl Summary { NewsArticle { headline : String::from(news—headline1), location : String::from(news-location1), author : String::from(news-author1), content : String::from(news-content1) } } fn main() { let news NewsArticle { headline : String::from(news—headline), location : String::from(news-location), author : String::from(news-author), content : String::from(news-content) }; println!(news article: {}, news.summarize()); let tweet Tweet { username : String::from(tweet-username), content : String::from(tweet-content), author : String::from(tweet-author), reply : false, retweet : false }; println!(tweet: {}, tweet.summarize()); report(tweet); let news1 make_news_article(); report(news1); }4线程4.1线程类似于c创建线程可以直接传入已经定义的函数也可以传入闭包rust中的闭包和c中的lambda表达式类似。c中的lambda表达是可以捕获数据引用捕获和值捕获都可以rust中的闭包需要通过move。本例子中handle2如果只用到msg的话不需要move捕获为什么use std::thread; use std::time::Duration; fn hello_thread() { println!(hello thread); } fn print_message(msg: String) { println!(msg: {}, msg); } fn main() { let handle1 thread::spawn(hello_thread); let handle2 thread::spawn(|| { for i in 1..10 { println!(hi number {} from this spawned thread2!, i); thread::sleep(Duration::from_millis(1)); } } ); let msg String::from(hello rust thread); let v vec![1, 2, 3]; let handle3 thread::spawn(move || { print_message(msg); println!(Heres a vector: {:?}, v); } ); for i in 1..5 { println!(hi number {} from the main thread!, i); thread::sleep(Duration::from_millis(1)); } handle1.join().unwrap(); handle2.join().unwrap(); handle3.join().unwrap(); }4.2通道channel类似于go语言go语言中也有channel。use std::thread; use std::sync::mpsc; use std::time::Duration; fn main() { let (tx, rx) mpsc::channel(); let tx1 mpsc::Sender::clone(tx); let handle1 thread::spawn(move || { let vals vec![String::from(hello), String::from(thread1)]; for val in vals { tx.send(val).unwrap(); thread::sleep(Duration::from_secs(1)); } }); let handle2 thread::spawn(move || { let vals vec![String::from(hello), String::from(thread2)]; for val in vals { tx1.send(val).unwrap(); thread::sleep(Duration::from_secs(1)); } }); for recv in rx { println!(recv {}, recv); } handle1.join().unwrap(); handle2.join().unwrap(); }4.3多线程加锁c中的锁和数据是分离的而rust中的锁和数据是集成到一块的。use std::sync::{Mutex, Arc}; use std::thread; fn main() { let counter Arc::new(Mutex::new(0)); let mut handles vec![]; for _ in 0..10 { let counter Arc::clone(counter); let handle thread::spawn(move || { let mut num counter.lock().unwrap(); *num 1; }); handles.push(handle); } for handle in handles { handle.join().unwrap(); } println!(result {}, *counter.lock().unwrap()); }5智能指针c中就有智能指针的概念共享指针独占指针。顾名思义共享指针是多个指针可以持有同一个对象独占指针只能有一个指针持有对象。c中的共享指针存在循环引用导致对象无法释放进而导致内存泄漏的问题为了解决这个问题引入了weak指针。同样的rust中也有共享指针也同样存在这样的问题并且rust编译器在编译阶段无法检查出这种错误。rust的解决办法和c中的解决办法类似引入了weak指针的概念。像这种纯语法类的错误而不是业务上的bugrust编译器也无法检查出来个人认为rust相对于c的进步是很有限的。内存安全包管理是两个显而易见的优势其他方面并不明显而c的优势更加明显在内存管理方面如果都使用共享指针那么内存方面的错误也会大大减少另外c的生态更加丰富并且能直接调用系统调用。c的优势太多了。BoxT类似于c中的独占指针RcT类似于共享指针。RcT创建的对象是不可修改的因为RcT会导致共享所有权如果每个RcT都能修改对象那么就违背了rust的借用规则要求即在同一个时刻只能有一个可以修改由此引出了RefCellT这是rust独有的特点。优先考虑使用BoxT或普通引用当需要多个所有者时使用RcT当需要多个所有者且需要修改时使用RcRefCellT。专门引出RefCell来实现对象的可变性为什么不把RcT直接实现成可变的呢这样岂不是更简洁同一个作用域内只能有一个可变引用或者多个不可变引用可变引用和不可变引用不能同时存在。类似于读写锁。5.1 BoxTBoxT是可以修改的。fn main() { // 必须声明为 mut 才能修改 Box 中的值 let mut boxed_number Box::new(5); println!(原始值: {}, *boxed_number); // 修改 Box 中的值 *boxed_number 10; println!(修改后: {}, *boxed_number); // 通过可变引用修改 let reference mut *boxed_number; *reference 20; println!(再次修改: {}, boxed_number); // 会自动解引用 // 使用 deref_mut use std::ops::DerefMut; *boxed_number.deref_mut() 30; println!(通过 deref_mut 修改: {}, boxed_number); }5.2 RcTRcT不可以修改。use std::rc::Rc; fn main() { // 创建一个字符串数据用 Rc 包装 let data Rc::new(String::from(Hello, Rust!)); // 创建第一个引用计数指针 let pointer1 Rc::clone(data); println!(pointer1: {}, pointer1); println!(当前引用计数: {}, Rc::strong_count(data)); // 创建第二个引用计数指针 let pointer2 Rc::clone(data); println!(pointer2: {}, *pointer2); println!(当前引用计数: {}, Rc::strong_count(data)); { // 在作用域内创建第三个引用 let pointer3 Rc::clone(data); println!(pointer3: {}, pointer3); println!(作用域内引用计数: {}, Rc::strong_count(data)); } // pointer3 离开作用域引用计数减1 // 作用域外引用计数恢复 println!(作用域外引用计数: {}, Rc::strong_count(data)); // 所有指针指向相同的数据 println!(pointer1 和 pointer2 指向相同数据: {}, Rc::ptr_eq(pointer1, pointer2)); // 当所有 Rc 指针离开作用域数据会被自动清理 // 此时引用计数变为 0 }5.3 RefCellT5.3.1局部可变性如下代码self是不可变的但是可以通过self修改self内部的成员实现了可变性穿透也是很NB的存在。use std::cell::RefCell; pub trait Messenger { fn send( self, msg: str); } pub struct LimitTrackera, T: a Messenger { messenger: a T, value: usize, max: usize, } impla, T LimitTrackera, T where T: Messenger { pub fn new(messenger: T, max: usize) - LimitTrackerT { 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: you are over your quota!); } else if percentage_of_max 0.9 { self.messenger.send(Warnning: youve used up over 90); } else if percentage_of_max 0.75 { self.messenger.send(Warnning: youve used up over 75); } } } #[cfg(test)] mod tests { use super::*; struct MockMessenger { sent_messages: RefCellVecString, } impl MockMessenger { fn new() - MockMessenger { MockMessenger { sent_messages: RefCell::new(vec![]) } } } impl Messenger for MockMessenger { fn send( self, message: str) { //如果sent_messages不声明为RefCell那么sent_message在这里不能修改 //因为self是不可变引用 self.sent_messages.borrow_mut().push(String::from(message)); } } #[test] fn it_sends_an_over_75_percent_warning_messages() { 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); } }5.3.2拥有多重所有权的可变数据use std::rc::Rc; use std::cell::RefCell; // 定义一个简单的图节点 #[derive(Debug)] struct Node { value: i32, // 使用 RcRefCellNode 实现多重所有权 neighbors: VecRcRefCellNode, } impl Node { fn new(value: i32) - RcRefCellSelf { Rc::new(RefCell::new(Node { value, neighbors: Vec::new(), })) } fn add_neighbor(mut self, neighbor: RcRefCellNode) { self.neighbors.push(neighbor); } fn print_with_neighbors(self) { println!(Node {} has neighbors: {:?}, self.value, self.neighbors.iter().map(|n| n.borrow().value).collect::Vec_() ); } } fn main() { // 创建三个节点 let node1 Node::new(1); let node2 Node::new(2); let node3 Node::new(3); // node1 连接到 node2 和 node3 node1.borrow_mut().add_neighbor(Rc::clone(node2)); node1.borrow_mut().add_neighbor(Rc::clone(node3)); // node2 也连接到 node3共享所有权 node2.borrow_mut().add_neighbor(Rc::clone(node3)); // 验证引用计数 println!(Reference count of node3: {}, Rc::strong_count(node3)); // 应该是3 // 打印所有节点的邻居 node1.borrow().print_with_neighbors(); node2.borrow().print_with_neighbors(); node3.borrow().print_with_neighbors(); // 修改 node3 的值通过任意一个引用 node3.borrow_mut().value 33; // 查看修改是否生效 println!(\nAfter modifying node3:); println!(Node3 value from node1s neighbor: {}, node1.borrow().neighbors[1].borrow().value); println!(Node3 value from node2s neighbor: {}, node2.borrow().neighbors[0].borrow().value); // 创建一个循环引用双向连接 node3.borrow_mut().add_neighbor(Rc::clone(node1)); println!(\nAfter creating cycle:); println!(Reference count of node1: {}, Rc::strong_count(node1)); }5.4循环引用循环引用导致无法释放1定义结构体struct User为User实现Drop trait当User被释放的时候Drop trait会被调用2创建两User的两个对象alice和bobalice和bob相互引用3当进程退出的时候看不到drop函数被调用说明alice和bob两个对象没有被释放use std::rc::Rc; use std::cell::RefCell; #[derive(Debug)] struct User { name: String, friends: RefCellVecRcUser, } // 实现 Drop trait 来创建析构函数 impl Drop for User { fn drop(mut self) { println!( User {} 被释放了, self.name); } } fn create_cycle_reference() { println!( 1. 创建循环引用内存泄漏 ); let alice Rc::new(User { name: Alice.to_string(), friends: RefCell::new(Vec::new()), }); let bob Rc::new(User { name: Bob.to_string(), friends: RefCell::new(Vec::new()), }); // 相互引用形成循环 alice.friends.borrow_mut().push(Rc::clone(bob)); bob.friends.borrow_mut().push(Rc::clone(alice)); println!(引用计数 - Alice: {}, Bob: {}, Rc::strong_count(alice), Rc::strong_count(bob)); // 函数结束时这些对象应该被释放但由于循环引用它们不会被释放 println!(函数结束应该看到释放消息吗); } fn main() { create_cycle_reference(); println!(\n main函数继续执行 ); println(这里不会看到Alice和Bob的释放消息因为它们还在内存中); // 手动触发垃圾回收在Rust中实际是离开作用域时自动清理 println!(\n 程序结束 ); // 由于内存泄漏程序结束时Alice和Bob仍然不会被释放 }使用weak指针可以看到drop函数被调用(1)strong_count是强引用计数也就是几个Rc指针持有对象weak_count是弱引用计数也就是几个Weak指针持有对象。对象的释放不受weak_count约束当strong_count减为0时即使weak_count不是0那么也会释放对象。(2)Weak指针不能单独创建必须依赖Rc指针而存在通过Rc指针的downgrade来获取Weak指针通过Weak指针的upgrade方法可以判断对象是不是还存在。use std::rc::{Rc, Weak}; use std::cell::RefCell; #[derive(Debug)] struct User { name: String, friends: RefCellVecWeakUser, } impl Drop for User { fn drop(mut self) { println!(✅ User {} 被正确释放了, self.name); } } fn create_without_cycle() { println!( 2. 使用Weak避免循环引用 ); let alice Rc::new(User { name: Alice.to_string(), friends: RefCell::new(Vec::new()), }); let bob Rc::new(User { name: Bob.to_string(), friends: RefCell::new(Vec::new()), }); // 使用弱引用不会形成循环 alice.friends.borrow_mut().push(Rc::downgrade(bob)); bob.friends.borrow_mut().push(Rc::downgrade(alice)); println!(强引用计数 - Alice: {}, Bob: {}, Rc::strong_count(alice), Rc::strong_count(bob)); // 显示弱引用数量 println!(弱引用计数 - Alice: {}, Bob: {}, Rc::weak_count(alice), Rc::weak_count(bob)); println!(函数结束现在应该看到释放消息...); } fn main() { create_without_cycle(); println!(\n main函数继续执行 ); println!(Alice和Bob已经被正确释放); // 创建一个临时的用户来演示生命周期 println!(\n 3. 演示局部作用域 ); { let temp_user Rc::new(User { name: 临时用户.to_string(), friends: RefCell::new(Vec::new()), }); println!(临时用户创建强引用计数: {}, Rc::strong_count(temp_user)); // temp_user 在这个作用域结束时会被释放 } println!(作用域结束临时用户应该已被释放); println!(\n 程序结束 ); }

相关新闻