Rust 学习笔记 27:FFI 交互 (Foreign Function Interface)

Rust 学习笔记 27:FFI 交互 (Foreign Function Interface) “Rust is what C++ should have been.” (Debatable, but Rust interacts with C perfectly) FFI 允许 Rust 代码调用其他语言写的函数,或者让其他语言调用 Rust 函数。 由于 C 语言是计算机世界的"通用语" (Lingua Franca),FFI 通常指的就是与 C 语言的交互。 1. 调用 C 函数 要在 Rust 中调用 C 函数,我们需要: 使用 extern "C" 块声明外部函数签名。 在 unsafe 块中调用它们(因为编译器无法保证 C 代码的安全性)。 1extern "C" { 2 fn abs(input: i32) -> i32; 3} 4 5fn main() { 6 unsafe { 7 println!("Absolute value of -3 according to C: {}", abs(-3)); 8 } 9} Rust 默认会链接系统的标准 C 库 (libc),所以像 abs, malloc, free 这样的标准函数可以直接用。如果是第三方库,还需要在 build.rs 中配置链接参数。 ...

2026-06-12 · 2 min · 278 words · 老墨

Rust 学习笔记 26:宏入门 (Macros)

Rust 学习笔记 26:宏入门 (Macros) “Code that writes code.” 我们从第一天开始就在用宏:println!。 宏 (Macro) 允许我们在编译期生成代码。这被称为元编程 (Metaprogramming)。 1. 宏 vs 函数 特性 宏 函数 参数数量 可变参数 (println!("{}, {}", a, b)) 固定参数个数 调用时机 编译期展开 (生成代码) 运行时调用 功能 可以定义 DSL,可以操作语法树 只能执行普通逻辑 维护性 编写复杂,调试困难 易于编写和调试 2. 声明式宏 (macro_rules!) 这是 Rust 中最常见的宏类型,类似于 match 匹配。 让我们复刻一个 vec! 宏: 1macro_rules! my_vec { 2 // 匹配模式: ( $( $x:expr ),* ) 3 // $x:expr -> 捕获一个表达式,命名为 x 4 // $(...),* -> 重复匹配括号内的内容,以逗号分隔 5 ( $( $x:expr ),* $(,)? ) => { 6 { 7 let mut temp_vec = Vec::new(); 8 // $(...)* -> 对每一次匹配进行重复展开 9 $( 10 temp_vec.push($x); 11 )* 12 temp_vec 13 } 14 }; 15} 16 17let v = my_vec![1, 2, 3]; 18let v2 = my_vec![1, 2, 3,]; // 也支持末尾逗号 这里发生了什么? ...

2026-05-20 · 2 min · 225 words · 老墨

Rust 学习笔记 25:高级特性 (Advanced Features)

Rust 学习笔记 25:高级特性 (Advanced Features) “With great power comes great responsibility.” – Uncle Ben (actually unsafe block) Rust 通常强制执行内存安全规则,但有时我们需要绕过它们(比如操作硬件、与 C 交互)。这时,Unsafe Rust 就派上用场了。 此外,Rust 的 Trait 和类型系统还有很多高级用法,能让你的代码更具表现力。 1. Unsafe Rust unsafe 关键字给我们开启了 5 种超能力: 解引用裸指针 (Raw Pointers)。 调用不安全的函数/方法(包括 FFI)。 访问或修改可变静态变量 (Mutable Static Variables)。 实现不安全 Trait (unsafe trait)。 访问 union 的字段。 裸指针: 1let mut num = 5; 2let r1 = &num as *const i32; 3let r2 = &mut num as *mut i32; 4 5unsafe { 6 println!("r1 is: {}", *r1); 7} 编译器不会检查裸指针是否有效、是否为空、是否遵守所有权规则。你自己要负责。 ...

2026-04-15 · 2 min · 236 words · 老墨

Rust 学习笔记 24:模式匹配详情 (Pattern Matching)

Rust 学习笔记 24:模式匹配详情 (Pattern Matching) “Patterns are the ultimate way to deconstruct reality.” 我们在前面的章节已经频繁使用了 match 和 let。其实,模式 (Pattern) 在 Rust 中无处不在。 只要涉及到数据赋值或参数传递,几乎都有模式的身影。 1. 模式无处不在 除了 match 表达式,哪里还有模式? if let: 1if let Some(x) = option_value { ... } while let: 1while let Some(top) = stack.pop() { ... } for 循环: 1for (index, value) in v.iter().enumerate() { ... } let 语句: 1let (x, y, z) = (1, 2, 3); 函数参数: 1fn print_coordinates(&(x, y): &(i32, i32)) { ... } 2. Refutability (可反驳性) 模式分为两类: ...

2026-03-30 · 2 min · 316 words · 老墨

Rust 学习笔记 23:面向对象特性 (Object Oriented Features)

Rust 学习笔记 23:面向对象特性 (Object Oriented Features) “The problem with object-oriented languages is they’ve got all this implicit environment that they carry around with them. You wanted a banana but you got a gorilla holding the banana and the entire jungle.” – Joe Armstrong Rust 是面向对象语言吗?这取决于你对 OOP 的定义。 如果定义是"封装、继承、多态",那么 Rust: 封装:有。struct 和 impl,以及 pub 关键字。 继承:没有。Rust 没有任何继承机制(Struct 不能继承 Struct)。 多态:有。通过泛型(静态多态)和 Trait Objects(动态多态)。 1. Trait Objects (动态分发) 我们在泛型那一章学过用 Trait Bound 实现多态: 1fn draw<T: Draw>(item: T) { item.draw(); } 这种是静态分发 (Static Dispatch)。编译器会为每种具体的 T 生成一份代码。优点是快,缺点是 Vec<T> 里的所有元素必须是同一种类型。 ...

2026-01-20 · 2 min · 215 words · 老墨

Rust 学习笔记 22:共享状态并发 (Shared-State Concurrency)

Rust 学习笔记 22:共享状态并发 (Shared-State Concurrency) “Do not communicate by sharing memory; instead, share memory by communicating.” – Go Language Slogan 虽然 Go 倡导通过通信来共享内存(Channel),但 Rust 同样提供了强大的共享状态并发工具。而且得益于所有权系统,Rust 中的锁是线程安全的(Thread Safety),你很难写出有数据竞争的代码。 1. Mutex (互斥锁) Mutex (Mutual Exclusion) 让同一时刻只有一个线程访问数据。 Rust 的 Mutex 有两个特点: 必须要先获取锁,才能通过解引用访问内部数据。这保证了你如果不加锁,根本拿不到数据(编译器按头让你加锁)。 RAII:锁的释放在 Guard 离开作用域时自动发生,不需要手动 unlock。 1let m = Mutex::new(5); 2{ 3 let mut num = m.lock().unwrap(); // 获取锁,num 是 MutexGuard 4 *num = 6; // 修改数据 5} // 离开作用域,锁自动释放 2. 原子引用计数 Arc 要在多个线程间共享 Mutex,直接传是不行的(所有权规则)。 你可能会想到 Rc<T>,但 Rc 不是线程安全的。 我们需要 Arc<T> (Atomic Reference Counting)。 ...

2025-11-30 · 2 min · 223 words · 老墨

Rust 学习笔记 21:并发编程 (无畏并发)

Rust 学习笔记 21:并发编程 (无畏并发) “Concurrency is about dealing with lots of things at once. Parallelism is about doing lots of things at once.” – Rob Pike 在很多语言中,并发编程是噩梦。数据竞争 (Data Race)、死锁 (Deadlock)、竞态条件 (Race Condition) 让人防不胜防。 Rust 号称 “Fearless Concurrency”(无畏并发)。它利用所有权和类型系统,在编译期就把数据竞争扼杀在摇篮里。 1. 使用线程 Rust 默认使用 1:1 线程模型,即一个 Rust 线程对应一个操作系统线程。 1use std::thread; 2use std::time::Duration; 3 4fn main() { 5 thread::spawn(|| { 6 for i in 1..10 { 7 println!("hi number {} from the spawned thread!", i); 8 thread::sleep(Duration::from_millis(1)); 9 } 10 }); 11 12 for i in 1..5 { 13 println!("hi number {} from the main thread!", i); 14 thread::sleep(Duration::from_millis(1)); 15 } 16} 注意:当主线程结束时,所有派生线程都会被强制终止,无论它们是否执行完。 ...

2025-11-29 · 2 min · 221 words · 老墨

Rust 学习笔记 20:项目实战二:构建 grep 命令行工具 (minigrep)

Rust 学习笔记 20:项目实战二:minigrep “UNIX is basically a simple operating system, but you have to be a genius to understand the simplicity.” – Dennis Ritchie 到目前为止,我们已经学习了 Rust 的大部分核心特性。现在,让我们把它们串起来,复刻一个经典的命令行工具:grep。 我们的目标是创建一个 minigrep,它接受一个查询字符串和一个文件名,然后打印出文件中包含查询字符串的行。 1. 需求分析 用法: 1$ cargo run -- searchstring example-filename.txt 功能点: 读取命令行参数。 读取文件内容。 筛选包含关键词的行。 错误处理(用户没传参数?文件不存在?)。 关注点分离:main.rs 负责处理参数和系统调用,lib.rs 负责核心逻辑。 TDD:测试驱动开发。 环境变量:支持 IGNORE_CASE=1 进行大小写不敏感搜索。 2. 核心代码演进 我们将代码分为 main.rs 和 lib.rs。 2.1 参数解析与配置 在 src/lib.rs 中定义 Config 结构体: 1use std::env; 2 3pub struct Config { 4 pub query: String, 5 pub file_path: String, 6 pub ignore_case: bool, 7} 8 9impl Config { 10 pub fn build(mut args: impl Iterator<Item = String>) -> Result<Config, &'static str> { 11 args.next(); // 也就是程序名,通常忽略 12 13 let query = match args.next() { 14 Some(arg) => arg, 15 None => return Err("Didn't get a query string"), 16 }; 17 18 let file_path = match args.next() { 19 Some(arg) => arg, 20 None => return Err("Didn't get a file path"), 21 }; 22 23 let ignore_case = env::var("IGNORE_CASE").is_ok(); 24 25 Ok(Config { 26 query, 27 file_path, 28 ignore_case, 29 }) 30 } 31} 注意这里使用了 impl Iterator,这样我们可以直接消费 env::args(),更加高效。 ...

2025-11-04 · 2 min · 344 words · 老墨

Rust 学习笔记 19:智能指针 (Smart Pointers)

Rust 学习笔记 19:智能指针 (Smart Pointers) “Pointer, I choose you!” 在 C++ 中,为了管理内存,我们有 unique_ptr 和 shared_ptr。 Rust 也有类似这一套,而且它是内存安全模型的重要补充。 普通引用 &T 和 &mut T 只是单纯的借用,不拥有数据。 而智能指针通常拥有它们指向的数据。 1. Box 最简单的智能指针。它把数据分配在堆上,栈上只留一个指针。 1let b = Box::new(5); 2println!("b = {}", b); 用途: 当类型大小在编译期无法确定时(比如递归类型 Cons List)。 当你拥有大量数据,想转移所有权但不想拷贝数据时。 Trait Objects (Box<dyn Trait>)。 2. Deref 和 Drop Trait 智能指针之所以"智能",是因为它们实现了: Deref Trait:允许像普通引用一样使用解引用运算符 *。这就是为什么 *b 能取到 5。 Drop Trait:允许自定义在清理时运行的代码(析构函数)。 3. Rc (引用计数) Rc (Reference Counting) 允许数据有多个所有者。 比如图结构,一个节点可能被多条边指向。 1let a = Rc::new(5); 2let b = Rc::clone(&a); // 引用计数 +1 3let c = Rc::clone(&a); // 引用计数 +1 Rc::clone 不会深拷贝数据,只会增加引用计数。当所有 Rc 都离开作用域,计数归零,数据才被释放。 注意:Rc 只能用于单线程。多线程要用 Arc (Atomic Rc)。 ...

2025-08-28 · 1 min · 151 words · 老墨

Rust 学习笔记 18:闭包与迭代器 (Closures & Iterators)

Rust 学习笔记 18:闭包与迭代器 (Closures & Iterators) “Functional programming is like backwards coding, everything is immutable and stateless… wait, Rust allows mutability?” Rust 不仅仅是系统编程语言,它还拥有强大的函数式编程特性。其中最核心的两个就是:闭包和迭代器。 1. 闭包 (Closures) 闭包就是可以捕获其环境的匿名函数。 在 Go 中,我们经常写 func() { variableInOuterScope = 1 }。Rust 的闭包语法有点像 Ruby 的管道符 | |。 1let x = 4; 2let equal_to_x = |z| z == x; // 捕获了 x 3let y = 4; 4assert!(equal_to_x(y)); 类型推断: 闭包不需要像函数那样强制标注参数和返回值类型,编译器会自动推断。 捕获方式: 闭包可以通过三种方式捕获环境,对应三个 Trait: FnOnce:获取捕获变量的所有权(Move)。闭包只能被调用一次。 FnMut:获取可变借用。可以修改环境。 Fn:获取不可变借用。 2. 迭代器 (Iterators) 迭代器模式允许你对一个序列进行遍历。 在 Rust 中,迭代器是惰性 (Lazy) 的。如果你不调用 collect() 或 sum() 等消耗方法,迭代器本身什么都不做。 ...

2025-08-20 · 1 min · 184 words · 老墨

Rust 学习笔记 17:自动化测试 (Automated Testing)

Rust 学习笔记 17:自动化测试 (Automated Testing) “Program testing can be a very effective way to show the presence of bugs, but it is hopelessly inadequate for showing their absence.” – Edsger W. Dijkstra 在 Go 中,我们创建 _test.go 文件,写 func TestXxx(t *testing.T)。 在 Rust 中,测试是一等公民。你不需要特殊的目录结构(单元测试),也不需要额外的测试框架。 1. 单元测试 (Unit Tests) 按照惯例,Rust 的单元测试直接写在源代码文件中,通常放在底部的 tests 模块里。 1// src/lib.rs 2 3fn add(a: i32, b: i32) -> i32 { 4 a + b 5} 6 7#[cfg(test)] 8mod tests { 9 use super::*; // 引入父模块的所有内容 10 11 #[test] 12 fn it_works() { 13 assert_eq!(add(2, 2), 4); 14 } 15} #[cfg(test)]:告诉编译器,只有在运行 cargo test 时才编译这个模块。生成的二进制文件不会包含这些测试代码,零空间开销。 #[test]:标记一个函数为测试函数。 assert_eq! / assert!:断言宏。 2. 测试私有函数 Rust 的单元测试允许测试私有函数!这是很多语言做不到的(Java 需要反射或放宽可见性)。 因为测试模块就定义在源码文件内部,且是子模块,它天然拥有访问父模块私有内容的权限。 ...

2025-06-10 · 2 min · 226 words · 老墨

Rust 学习笔记 15:Traits (特质)

Rust 学习笔记 15:Traits (特质) “If it walks like a duck and quacks like a duck, it must be a Trait.” 在 Go 语言中,接口 (Interface) 是隐式实现的。只要你的方法签名对上了,你就实现了接口。 在 Rust 中,Traits (特质) 必须要显式实现 (impl Trait for Type)。 1. 定义与实现 Trait 定义一个 Summary Trait: 1pub trait Summary { 2 fn summarize(&self) -> String; 3} 为 NewsArticle 和 Tweet 实现它: 1impl Summary for Tweet { 2 fn summarize(&self) -> String { 3 format!("{}: {}", self.username, self.content) 4 } 5} 2. 默认实现 (Default Implementations) Trait 中可以提供默认实现,这有点像 Java 8 的 default method。 ...

2025-05-20 · 2 min · 286 words · 老墨

Rust 学习笔记 16:生命周期 (Lifetimes)

Rust 学习笔记 16:生命周期 (Lifetimes) “To live is to suffer, to survive is to find some meaning in the suffering… of the borrow checker.” 生命周期 (Lifetimes) 是 Rust 中最令人困惑的概念,没有之一。 在 Go 或 Java 中,GC 会自动管理对象的死活。你不需要关心引用活多久,反正 GC 兜底。 在 C++ 中,你需要手动管理,稍有不慎就是 Use-After-Free。 Rust 选择了第三条路:编译期验证引用有效性。这就是生命周期检查器 (Borrow Checker) 的工作。 1. 为什么需要生命周期? 主要为了避免悬垂引用 (Dangling References)。 1{ 2 let r; 3 { 4 let x = 5; 5 r = &x; 6 } // x 在这里销毁了 7 println!("r: {}", r); // r 指向了非法内存 8} 这段代码无法通过编译,因为 r 的生命周期比 x 长。 ...

2025-05-20 · 2 min · 227 words · 老墨

Rust 学习笔记 14:泛型 (Generics)

Rust 学习笔记 14:泛型 (Generics) “Abstraction without overhead.” 泛型是静态类型语言提高代码复用率的关键。 在 Go 中,我们习惯了 interface{} (或 any) 带来的运行时动态检查,或者复制粘贴代码。 Rust 的泛型则不同,它在编译期通过单态化 (Monomorphization) 展开代码,运行时没有性能损耗。 1. 泛型函数 假设我们要写一个找最大值的函数,既能找 i32 也能找 char。 1fn get_largest<T: PartialOrd + Copy>(list: &[T]) -> T { 2 let mut largest = list[0]; 3 for &item in list { 4 if item > largest { // 需要 T 实现 PartialOrd 5 largest = item; 6 } 7 } 8 largest 9} 这里 T 是类型参数。<T: PartialOrd + Copy> 叫做 Trait Bound(类似 Java 的 T extends ... 或 Go 的 T interface ...)。它告诉编译器:这个 T 不是任意类型,它必须能比较大小,而且能被 Copy(为了简单起见,这里假设是栈上数据)。 ...

2025-04-26 · 2 min · 220 words · 老墨

Rust 学习笔记 13:错误处理 (Error Handling)

Rust 学习笔记 13:错误处理 (Error Handling) “To err is human; to handle it safely is Rusty.” Go 开发者最熟悉的代码可能是 if err != nil。 Rust 也有类似的机制,但它利用强大的枚举类型 Result<T, E> 把错误处理提升到了一个新的高度。 Rust 将错误分为两类: 不可恢复错误:bug 导致的,比如数组越界。使用 panic!。 可恢复错误:文件未找到、网络超时等。使用 Result<T, E>。 1. Panic! (不可恢复错误) 当程序遇到无法处理的状况时(例如访问数组越界),Rust 会 panic。程序会打印错误信息,展开 (unwind) 栈,清理数据,然后退出。 1panic!("crash and burn"); 你也可以配置 Cargo.toml 让 panic 时直接 abort(不清理栈),以减小二进制体积。 2. Result (可恢复错误) 大部分时候我们不希望程序崩溃。 1enum Result<T, E> { 2 Ok(T), 3 Err(E), 4} 处理 Result 的标准姿势是 match: 1let f = File::open("hello.txt"); 2 3let f = match f { 4 Ok(file) => file, 5 Err(error) => panic!("Problem opening the file: {:?}", error), 6}; unwrap 和 expect: 如果你确定不会出错,或者为了省事(在 demo 代码中),可以使用 unwrap()。如果出错它会直接 panic。expect("msg") 则是带自定义消息的 unwrap。 ...

2025-03-11 · 2 min · 237 words · 老墨

Rust 学习笔记 12:常用集合 (Collections)

Rust 学习笔记 12:常用集合 (Collections) “Data structures are the backbone of any program.” Rust 标准库提供了一系列非常实用的集合数据结构。和数组/元组不同,它们的数据存储在堆 (Heap) 上,这意味着长度可以动态变化。 如果你有 Go 语言背景,可以这样类比: Vec<T> ≈ Slice []T String ≈ string (但在 Rust 中它是可变的) HashMap<K, V> ≈ Map map[K]V 1. Vector (动态数组) Vector 是最常用的集合。 1let mut v: Vec<i32> = Vec::new(); 2v.push(1); 3v.push(2); 4v.push(3); 或者使用 vec! 宏: 1let v = vec![1, 2, 3]; 访问元素: 有两种方式,一种是索引 &v[2](越界会 Panic),一种是 v.get(2)(返回 Option<&T>)。在 Rust 中,推荐尽可能用 get 来安全处理越界。 借用规则陷阱: 当你持有一个元素的引用时,不能往 Vector 里 push 新元素! 原因:push 可能会导致 vector 扩容(重新分配内存),如果你手里还攥着旧内存的引用,那就是悬垂指针了。Rust 编译器会死死盯着你。 ...

2025-02-18 · 2 min · 253 words · 老墨

Rust 学习笔记 11:包与模块 (Packages & Modules)

Rust 学习笔记 11:包与模块 (Packages & Modules) “Organization is what you do before you do it, so that when you do it, it’s not all mixed up.” – A. A. Milne 欢迎来到 2025 年! 在完成了前 10 章的基础语法学习后,我们现在的 Rust 代码全都写在一个 main.rs 里。这对于写个猜数字游戏还行,但要写大项目,文件组织是必修课。 Rust 的模块系统 (Module System) 有点复杂,甚至被称为"新手劝退三大难"之一(另外俩是所有权和生命周期)。 1. 家族谱系:Package, Crate, Module 首先要分清三个概念: Package (包):Cargo 的功能单元。包含 Cargo.toml。一个 Package 可以包含多个 Binary Crate,但只能有一个 Library Crate。 Crate (箱):编译单元。main.rs 是二进制 Crate 的根,lib.rs 是库 Crate 的根。 Module (模块):代码组织单元。用 mod 关键字定义。 层级关系:Package -> (contains) -> Crates -> (contains) -> Modules。 ...

2025-02-06 · 2 min · 243 words · 老墨

Rust 学习笔记 10:项目实战一:猜数字

Rust 学习笔记 10:项目实战一:猜数字 “Programming is not about typing, it’s about thinking.” 之前的 9 篇笔记,我们一直在抠语法细节。 今天是圣诞节(假设),我们来点轻松的:用 Rust 写一个猜数字游戏。 规则很简单: 程序生成一个 1~100 的随机数。 玩家输入猜测。 程序提示 “大了”、“小了” 或 “猜对了”。 猜对了游戏结束,否则继续。 1. 引入依赖 Crates Rust 的标准库很精简,没有内置随机数生成。我们需要引入第三方库 rand。 修改 Cargo.toml: 1[dependencies] 2rand = "0.9" 这和 Go 的 go get 不太一样,Rust 更像是 Node.js 的 npm,依赖管理非常现代化。 2. 获取用户输入 1use std::io; 2 3let mut guess = String::new(); // 创建可变空字符串 4 5io::stdin() 6 .read_line(&mut guess) // 传可变引用进去修改 7 .expect("Failed to read line"); // 处理 Result::Err 注意那个 read_line 返回的是 io::Result 枚举。如果读取失败,它会返回 Err,expect 方法会帮你 panic 并打印错误信息。在 Rust 中,忽略返回值是会被警告的。 ...

2024-12-25 · 2 min · 249 words · 老墨

Rust 学习笔记 09:枚举与模式匹配

Rust 学习笔记 09:枚举与模式匹配 “Null References: The Billion Dollar Mistake.” – Tony Hoare Go 语言没有枚举(只有 iota 常量),也没有代数数据类型,更没有模式匹配。所以这一章对于 Go 开发者来说,是全新的世界。 1. 它是枚举,但又不仅仅是枚举 在 C/Go 中,Enum 通常只是整数的别名。 但在 Rust 中,Enum 可以携带数据,而且每个变体携带的数据类型可以不同。 1enum Message { 2 Quit, // 没有数据 3 Move { x: i32, y: i32 }, // 包含匿名结构体 4 Write(String), // 包含 String 5 ChangeColor(i32, i32, i32), // 包含元组 6} 这一个 Enum 类型,就涵盖了消息系统的所有可能性。这比 Go 中定义一堆 struct 加上 interface 要简洁直观得多。 2. Option 枚举:告别 Null Rust 没有 null。 那我想要表达"变量可能为空"怎么办? Rust 标准库提供了一个枚举 Option<T>: ...

2024-11-30 · 2 min · 224 words · 老墨

Rust 学习笔记 08:结构体 (Structs)

Rust 学习笔记 08:结构体 (Structs) “Structure is the maker of light.” – Louis Kahn 在 Go 中,我们用 struct 来组织数据,用 func (s *Struct) Method() 来定义方法。 在 Rust 中,这种感觉非常相似,但细节决定成败。 1. 定义与实例化 定义一个 User 结构体: 1struct User { 2 active: bool, 3 username: String, 4 email: String, 5 sign_in_count: u64, 6} 这和 Go 几乎一模一样(除了字段名为 snake_case,Go 推荐 CamelCase)。 实例化: 1let user1 = User { 2 email: String::from("someone@example.com"), 3 username: String::from("someusername123"), 4 active: true, 5 sign_in_count: 1, 6}; 区别点:如果想修改 user1 的字段,必须把 user1 声明为 mut。Rust 不允许仅把某个字段标记为可变,可变性是针对整个实例的。 ...

2024-10-15 · 2 min · 342 words · 老墨

Rust 学习笔记 07:Slice 类型

Rust 学习笔记 07:Slice 类型 “Slice and dice your data safely.” 在 Go 语言中,Slice (切片) 是一个非常核心的概念,它底层是一个结构体 (ptr, len, cap)。 在 Rust 中,Slice 也是类似的,但它有一个本质的区别:Rust 的 Slice 是一种引用(Borrowed Type)。 它不拥有数据,它只是借用了数据的一部分。 1. 字符串切片 (String Slices) 假设我们有一个字符串 s: 1let s = String::from("hello world"); 我们可以创建一个切片,指向它的一部分: 1let hello = &s[0..5]; // 指向 0,1,2,3,4 2let world = &s[6..11]; 注意那个 & 符号,说明 hello 是一个引用。 它的类型是 &str(读作 “string slice”)。它内部包含两个字段: 指向数据的指针 (ptr) 切片的长度 (len) 这和 Go 的 Slice 结构几乎一样,只是少了容量 (cap),且必须依附于原字符串存在。 2. 切片与所有权 既然 Slice 是引用,它就受借用规则的约束。 ...

2024-09-08 · 2 min · 217 words · 老墨

Rust 学习笔记 06:引用与借用 (References & Borrowing) 下

Rust 学习笔记 06:引用与借用 (References & Borrowing) 下 “Borrow checkers are the strict librarians of the programming world.” 上一集我们讲了所有权 (Ownership):把东西给别人(Move),自己就没了。这虽然安全,但太麻烦了。 比如我想计算一个字符串长度,难道每次都要把所有权传进去,算完再传回来? 1// 痛苦的写法 2fn calculate_length(s: String) -> (String, usize) { 3 let length = s.len(); 4 (s, length) // 还要把 s 传回去,心累 5} Rust 说:别急,你可以 借 (Borrow) 啊。 1. 引用 (References) 引用允许你使用值但不获取其所有权。在 Rust 中,用 & 符号表示。 1fn main() { 2 let s1 = String::from("hello"); 3 4 // 传引用:&s1 5 let len = calculate_length(&s1); 6 7 // s1 依然有效!因为我们只是借出去了,没送出去。 8 println!("The length of '{}' is {}.", s1, len); 9} 10 11// 接收引用:&String 12fn calculate_length(s: &String) -> usize { 13 s.len() 14} 这叫 借用 (Borrowing)。就像你借了朋友的书,你看完了得还回去(离开作用域),但书还是朋友的。 ...

2024-08-25 · 2 min · 278 words · 老墨

Rust 学习笔记 05:所有权 (Ownership) 上

Rust 学习笔记 05:所有权 (Ownership) 上 “I thought I knew what ownership meant until I met the borrow checker.” – Anonymous Rustacean 终于来到了 Rust 的核心 —— 所有权 (Ownership)。 作为 Go 开发者,我们习惯了 GC(垃圾回收)帮我们打理一切。我们随手创建一个指针,传给函数,传给 Channel,从来不需要关心它什么时候被释放。因为有 GC 在兜底。 但在 Rust 里,没有 GC。但它也没有让我们像 C++ 那样手动 malloc/free。 那它是怎么管理内存的? 答案就是:所有权系统 + 编译器静态检查。 1. 核心原则 所有权规则非常霸道,但只有三条: Rust 中的每一个值都有一个被称为其 所有者 (owner) 的变量。 值在任一时刻有且只有一个所有者。 当所有者(变量)离开作用域,这个值将被丢弃 (Drop),内存被释放。 这听起来很像作用域管理,但最关键的是第二条:有且只有一个所有者。 2. 移动 (Move) 语义 在 Go 中,变量赋值默认是值拷贝(对于指针是拷贝地址)。 1// Go 代码 2s1 := "hello" 3s2 := s1 4// 现在 s1 和 s2 都指向同一个字符串,随便用 但在 Rust 中,对于复杂类型(如 String,在堆上分配),情况完全不同: ...

2024-07-20 · 2 min · 274 words · 老墨

Rust 学习笔记 04:控制流程

Rust 学习笔记 04:控制流程 “Controlling complexity is the essence of computer programming.” – Brian Kernighan 写控制流程(条件判断、循环)是程序员每天都在做的事。 对于 Go 开发者来说,我们习惯了 if err != nil 和万能的 for 循环。 但在 Rust 里,控制流程被注入了"表达式"的灵魂,变得更加灵活(也更骚)。 1. if 表达式 注意标题:是 if 表达式,不是 if 语句。这意味着 if 结构本身可以产生一个值。 基础用法 和 Go 一样,Rust 的 if 条件不需要括号 (),但执行体必须用大括号 {}。 1let number = 3; 2if number < 5 { 3 println!("condition was true"); 4} else { 5 println!("condition was false"); 6} 作为表达式赋值 这是 Go 做不到的。因为 if 是表达式,我们可以把它放在 let 语句的右边。这完全替代了 Java/C++ 中的三元运算符 ? :。 ...

2024-06-15 · 2 min · 307 words · 老墨

Rust 学习笔记 03:函数

Rust 学习笔记 03:函数 “Talk is cheap. Show me the code.” – Linus Torvalds 在这一课,我们来聊聊编程语言中最基础的构建块——函数。 如果你熟悉 Go,你可能会觉得:“函数嘛,不就是 func 换成了 fn,大括号里写逻辑吗?” 确实,表面上看区别不大。但 Rust 引入了一个对 Go 开发者来说比较新颖的概念:语句 (Statements) 与 表达式 (Expressions) 的严格区分。这直接改变了代码的书写习惯(和逼格)。 1. 定义函数 Rust 使用 snake_case (蛇形命名法) 来命名函数,所有字母小写,单词间用下划线分开。 1fn main() { 2 println!("Hello, world!"); 3 another_function(); 4} 5 6fn another_function() { 7 println!("Another function."); 8} Go 也是这么写的(除了大括号位置 Go 强制不换行,Rust 推荐不换行但没强制)。 2. 参数与类型 和 Go 一样,Rust 是强类型语言,函数参数必须声明类型。这点没得商量。 1fn print_labeled_measurement(value: i32, unit_label: &str) { 2 println!("The measurement is: {}{}", value, unit_label); 3} 3. 语句 vs 表达式 (The Twist) 这是本节的重点! ...

2024-05-18 · 2 min · 275 words · 老墨

Rust 学习笔记 02:变量与可变性

Rust 学习笔记 02:变量与可变性 “Stability is not immobility.” – Prince Metternich 习惯了 Go 语言的 var 和 :=,第一次写 Rust 时,最让人抓狂的报错一定是 cannot assign twice to immutable variable。 在 Go 里,变量生来就是可变的(除了 const),但在 Rust 里,变量生来就是不可变的。 这就像是:Go 是一个自由奔放的游乐场,你想去哪就去哪;而 Rust 是一个戒备森严的博物馆,除非你申请了"触摸许可"(mut),否则只能看,不能摸。 1. 默认不可变 (Immutability) 在 Rust 中,当你写下: 1let x = 5; 注意: Rust 必须在语句末尾加上 “;",与 Java 一样,但是 Go 却不需要,这需要 Go 开发者习惯很久。 这不仅是定义了一个变量,更是立下了一个誓言:“x 的值就是 5,永远不会变。” 如果你试图打破誓言: 1x = 6; // 编译报错! 编译器会无情地告诉你:cannot assign twice to immutable variable x。 ...

2024-04-12 · 2 min · 350 words · 老墨

Rust 学习笔记 01:简介与环境搭建

Rust 学习笔记 01:简介与环境搭建 “A language that doesn’t affect the way you think about programming, is not worth knowing.” – Alan Perlis 作为一名写了几年 Go 的程序员,习惯了 GC 的安逸,也忍受了 if err != nil 的繁琐。一直听说 Rust 有多强,但每次看到那陡峭的学习曲线和满屏的生命周期引用,都默默劝退。 2024 年了,是时候走出舒适区,挑战一下这个"编译器教你做人"的语言了。 这系列笔记不是官方教程的复读机,而是从一个 Go/Java 开发者的视角,记录学习过程中的困惑、对比和感悟。 1. 为什么要折腾自己? 如果 Go 是一把瑞士军刀,简单实用;那 Rust 就像是一把手术刀,精准锋利,但由于太锋利,很容易割到手。 对于 Go 开发者来说,Rust 的吸引力在于: 零成本抽象:不用担心封装会带来性能损耗。 没有 GC:从此告别 STW (Stop The World),虽然 Go 的 GC 已经很快了,但有些场景下,完全控制内存是刚需。 安全性:编译器会在编译阶段就拦下绝大多数内存错误和并发数据竞争问题。 2. 环境搭建 Rust 的安装体验和 Go 差不多,甚至更现代。 macOS/Linux 一行命令搞定: ...

2024-03-05 · 1 min · 203 words · 老墨