

























学而不思则罔,思而不学则殆。孔老夫子常常教导我们学习方法,历经千年而常青。随口小提一下,不要无脑黑孔老夫子,他的部分治国思想有落后性,但整体要取其精华去其糟粕,学习思想也类似。不要把精力放在骂孔老夫子上,要学习他的优秀思想。
对于 C/C++ 开发者来说,Rust 的语法和底层逻辑既熟悉又陌生。对于主要从事c/c++开发12年之久的小兵来说,Rust 语言的底层设计理念(将内存管理、所有权和类型系统彻底暴露给程序员)导致了许多表面上看起来理所当然,但底层逻辑反直觉的语法。因为15年接触的项目使用到了rust,就用了不到一年,项目已结束,后面就不怎么接触rust了。直到几年前一个偶然的机会,再次重新点燃了rust学习和使用的火焰。
除了可变引用,以下是 Rust 中最常用、但也最容易让 C/C++ 开发者产生思维冲突和困惑的语法特性:
'a)'a 标注引用之间的绑定关系。ref 与 ref mutmatch 或 if let 中,普通变量绑定会默认移动(Move)所有权。如果你只想借用部分字段,必须使用 ref。let ref x = y; 等价于 let x = &y;。在 C++ 中,你在左侧写 & 表示引用;而在 Rust 的模式匹配中,左侧写 & 表示解引用匹配,写 ref 才表示创建引用。let p = &y;(右侧的 &)表示创建引用,获取 y 的地址。let &x = y;(左侧的 &)才是解引用匹配,它会剥离掉 y 的引用,把绑定的值复制或移动给 x。在 match 或 if let 中匹配一个结构体或枚举时,如果你不小心,也会把里面的字段给 Move 掉。
struct User { name: String, age: u32 }
let user = User { name: String::from("Alice"), age: 30 };
match user {
User { name, age } => { ... } // 💥 极其隐蔽的陷阱!
}
name 变量会把 user.name 的所有权 Move 出来!这导致匹配完成后,整个 user 变量直接报废(变成了部分初始化状态)。User { ref name, age },现代 Rust 允许你在匹配目标前加引用:
match &user { // 👈 对整个对象取引用
User { name, age } => { ... } // 此时 name 自动推导为 &String
}
dyn Trait)Base*。Trait(类似虚基类)不能直接作为变量类型或返回值,必须写成 &dyn Trait 或 Box<dyn Trait>。dyn Trait 是胖指针(Fat Pointer),包含两个部分:指向数据的指针和指向虚函数表的指针。因此,dyn Trait 是一个不确定大小的类型(Unsized),必须隐藏在指针后面。Copy 特征std::move。std::move 的代码。Copy trait(如 i32),赋值才会变成复制。C++ 移动后留下一个“有效但未定义”的空壳,而 Rust 移动后该内存直接不可访问。move ||)[&]、[=]、[values = std::move(x)]。move 关键字。move,闭包会默认尝试以最小权限(不可变引用 -> 可变引用 -> 移动)来捕获外部变量。加上 move 关键字则强制将所有捕获变量的所有权转移进闭包,这在多线程(如 thread::spawn)传输闭包时是强制要求的。let mut text = String::from("Rust");
// 情况 A:不加 move
let print_text = || println!("{}", text);
// 👆 此时闭包内部捕获的是 text 的【引用】(&String)
// 情况 B:加了 move
let print_text_move = move || println!("{}", text);
// 👆 此时 text 的【所有权】被强行移动到了闭包内部
operator->() 和 operator*() 运算符重载。String 类型的变量可以直接传给接收 &str 的函数?为什么 &Box<T> 可以直接当 &T 用?Deref trait,会自动插入解引用操作(例如把 &String 隐式转换为 &str)。这种“悄悄发生”的转换经常让习惯了 C++ 严格类型匹配的开发者感到困惑。Rust 为了让智能指针(如 Box<T>, Rc<T>, Arc<T>, String)用起来像普通引用,引入了 Deref 特性。但这会导致你在调用方法时,根本分不清是谁在调用。
let s: String = String::from("hello");
// String 没有 .len() 方法,.len() 是定义在 str 切片上的。
// 但由于 Deref 存在,Rust 会自动把 &String 变成 &str
println!("{}", s.len());
&T、&mut T、*T(解引用一次)、**T(解引用两次)去匹配方法签名。&&&&&value.method() 和 value.method() 的效果完全一样,初学者往往会彻底迷失在指针的层级里。下班下班,回去休息。^-^
.iter() 与 .into_iter() 的隐式所有权转移当你对一个集合进行循环时,写法的微小差异会导致完全不同的所有权行为。
let words = vec![String::from("hello"), String::from("world")];
// 困惑点 A:for-in 循环默认会“吃掉”集合
for w in words { ... }
// 👆 这里的 words 发生了 Move!循环结束后,words 变量彻底死掉,无法再使用。
// 困惑点 B:.iter() 和 .into_iter() 截然不同
for w in words.iter() { ... } // ✅ w 的类型是 &String(借用,words 还能用)
for w in words.into_iter() { ... } // 💥 w 的类型是 String(Move,words 完蛋)
for-in 只是单纯的遍历。但在 Rust 中,for-in 语法糖本质上是调用了 .into_iter()。如果这个集合没有实现 Copy,它就会被无情地 Move 掉。String 与 &str 的相加(+ 运算符的非对称性)在字符串拼接时,Rust 的类型限制极其死板,且不支持对称操作。
let s1 = String::from("hello ");
let s2 = String::from("world");
let s3 = s1 + &s2; // ✅ 成功编译
// let s4 = &s1 + &s2; // ❌ 编译报错!
// let s5 = s1 + s2; // ❌ 编译报错!
String(不能带 &),而右边必须是 &str(或 &String)?+ 运算符本质上重载的是这个函数:
fn add(self, rhs: &str) -> String // 👈 注意 self 是没有 & 的!
s1 + &s2 执行后,s1 的所有权已经被无情地消耗掉了(底层复用了 s1 的内存缓冲区以提高性能)。你不能用两个引用相加,因为那样就必须分配一块全新的内存,Rust 拒绝隐式做这种高开销的操作。参考资料:
rust工程化实践卷III juler 2015-11
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。