Skip to content
Published at:

02. Tour of Rust — Rust 速览

本章快速浏览 Rust 的语法和核心概念。我们不会深入每个特性(后续章节会逐一展开),而是通过一系列可运行的示例建立整体印象。如果你是 Rust 新手,本章将帮助你理解各个语法元素的「形状」;如果你已有 Rust 经验,可以跳过本章。

创建第一个程序

bash
cargo new hello
cd hello

cargo new 会生成如下文件结构:

hello/
├── Cargo.toml          # 项目元数据和依赖
└── src/
    └── main.rs         # 入口文件

编辑 src/main.rs

rust
fn main() {
    println!("Hello, world!");
}
  • fn 声明一个函数。main 是程序的入口点(就像 C/C++ 中的 main 函数)。
  • println! 后面的 ! 表示这是一个宏(macro)。Rust 宏是可以生成代码的代码。println! 在编译时展开为格式化输出的代码。
  • 字符串字面量使用双引号。
  • 大多数语句以分号结尾。

运行程序:

bash
cargo run

变量与绑定

Rust 中的变量默认是**不可变(immutable)**的:

rust
fn main() {
    let answer = 42;
    // answer = 43;  // 编译错误:不可变变量不能重新赋值

    let mut guess = String::new();
    guess = String::from("hello");  // OK:mut 允许修改
}

let 不仅声明变量——它创建一个绑定(binding),将名称与值关联起来。Rust 的类型推断能力强大,大多数时候不需要显式标注类型:

rust
let x = 42;              // 推断为 i32
let y = 3.14;            // 推断为 f64
let s = "hello";         // 推断为 &str
let v = vec![1, 2, 3];   // 推断为 Vec<i32>

如果需要显式标注类型,语法是 let name: Type = value;

rust
let x: i32 = 42;
let s: &str = "hello";
let v: Vec<i32> = vec![1, 2, 3];

变量可以使用 let **遮蔽(shadow)**同名变量,甚至可以改变类型:

rust
let input = "42";           // &str
let input = input.trim();   // &str,遮蔽
let input: i32 = input.parse().unwrap();  // i32,遮蔽且改变类型

这与 mut 不同:mut 改变同一个绑定的值,遮蔽创建一个新的绑定。

函数

rust
fn gcd(mut n: u64, mut m: u64) -> u64 {
    assert!(n != 0 && m != 0);
    while m != 0 {
        if m < n {
            let t = m;
            m = n;
            n = t;
        }
        m = m % n;
    }
    n
}

要点解读:

  • 函数参数必须标注类型(Rust 不会推断参数类型)。
  • 返回类型用 -> 标注。
  • 函数体是表达式,最后一个表达式(不加分号)就是返回值。这里 n 不带分号,所以是返回值。
  • return 关键字存在,但 Rust 社区倾向于使用隐式返回——这让函数成为「一个计算并返回某物的表达式」。
  • assert! 宏在条件为 false 时 panic。

表达式 vs 语句:在 Rust 中,几乎所有东西都是表达式,都有值。ifloopblock 都有值:

rust
let status = if cpu_temp <= 70.0 {
    "OK"
} else if cpu_temp <= 85.0 {
    "Warning"
} else {
    "Critical"
};
// 注意:每个分支的类型必须一致

基本类型速览

rust
// 整数:有符号和无符号,位宽固定
let a: i8 = -128;        // -128 到 127
let b: u8 = 255;         // 0 到 255
let c: i32 = -1_000_000; // 下划线增强可读性
let d: usize = 42;       // 指针大小(64位平台 = u64)

// 浮点数
let pi: f64 = 3.14159265358979;
let e: f32 = 2.71828;

// 布尔值
let is_ok: bool = true;

// 字符:32 位 Unicode 标量值
let heart_eyed_cat = '😻';  // char 可以存表情符号
let z = 'ℤ';

// 元组(tuple)
let t: (i32, f64, char) = (42, 3.14, 'π');
let (x, y, z) = t;       // 解构
let first = t.0;          // 索引访问
let unit = ();             // 单元类型,不占空间

// 数组:固定长度,在栈上
let arr: [i32; 3] = [1, 2, 3];
let zeros = [0; 1000];    // 1000 个零

// 向量:动态长度,在堆上
let mut v: Vec<i32> = vec![1, 2, 3];
v.push(4);
v.pop();

// 字符串切片 &str:固定、不可变
let greeting: &str = "Hello, 世界";

// 拥有所有权的字符串 String:动态、可变
let mut s: String = String::from("Hello");
s.push_str(", world!");

// 切片 [T]:对数组或向量的某一连续区域的引用
let slice: &[i32] = &v[1..3];  // [2, 3]

控制流

rust
// if 表达式
let num = 42;
let size = if num < 100 { "small" } else { "large" };

// loop:无限循环(但有 break)
let mut i = 0;
loop {
    i += 1;
    if i >= 10 { break; }
}

// while
let mut n = 5;
while n > 0 {
    println!("{}", n);
    n -= 1;
}

// for:遍历集合
for i in 0..5 {
    println!("{}", i);  // 0, 1, 2, 3, 4
}

let v = vec!["a", "b", "c"];
for item in &v {
    println!("{}", item);
}

// match:模式匹配
let code = 200;
match code {
    200 => println!("OK"),
    301 | 302 => println!("Redirect"),
    404 => println!("Not Found"),
    500..=599 => println!("Server Error"),
    _ => println!("Unknown"),
}

match 是 Rust 中最强大的控制流结构之一。编译器会检查是否覆盖了所有可能的情况(穷尽性检查)。

错误处理:Result 与 Option

Rust 没有异常。函数通过返回类型显式表达可失败性:

rust
// Option<T>:可能没有值
fn divide(numerator: f64, denominator: f64) -> Option<f64> {
    if denominator == 0.0 {
        None
    } else {
        Some(numerator / denominator)
    }
}

match divide(10.0, 0.0) {
    Some(result) => println!("Result: {}", result),
    None => println!("Cannot divide by zero"),
}

// Result<T, E>:可能失败
use std::fs::File;

match File::open("hello.txt") {
    Ok(file) => println!("File opened: {:?}", file),
    Err(error) => println!("Error: {}", error),
}

? 运算符是简洁的错误传播语法:

rust
fn read_username() -> Result<String, std::io::Error> {
    let mut s = String::new();
    // ? 在错误时立即返回 Err,否则提取 Ok 中的值
    std::fs::File::open("username.txt")?.read_to_string(&mut s)?;
    Ok(s)
}

结构体与方法

rust
#[derive(Debug)]  // 自动生成格式化输出
struct Person {
    name: String,
    age: u8,
}

impl Person {
    // 关联函数(构造函数通常叫 new)
    fn new(name: String, age: u8) -> Self {
        Person { name, age }
    }

    // 方法(第一个参数是 &self)
    fn greet(&self) -> String {
        format!("Hi, I'm {} and I'm {} years old.", self.name, self.age)
    }

    // 可变方法
    fn have_birthday(&mut self) {
        self.age += 1;
    }
}

fn main() {
    let mut alice = Person::new(String::from("Alice"), 30);
    println!("{}", alice.greet());
    alice.have_birthday();
    println!("After birthday: {:?}", alice);
}

注意:&selfself: &Self 的语法糖,表示借用(不可变引用)。&mut self 是可变借用。没有 & 前缀的 self 表示获取所有权——调用后原变量不可用。

泛型与 Trait

rust
// 泛型函数
fn largest<T: PartialOrd + Copy>(list: &[T]) -> T {
    let mut largest = list[0];
    for &item in list {
        if item > largest {
            largest = item;
        }
    }
    largest
}

println!("{}", largest(&[3, 7, 1, 9, 2]));  // 9
println!("{}", largest(&['a', 'z', 'm']));   // z

T: PartialOrd + CopyTrait 约束(Trait bound):要求类型 T 同时实现了 PartialOrd(可比较)和 Copy(可以按位复制)。

定义 Trait:

rust
trait Summary {
    fn summarize(&self) -> String;

    // Trait 可以提供默认实现
    fn summarize_short(&self) -> String {
        String::from("(Read more...)")
    }
}

struct Article {
    title: String,
    body: String,
}

impl Summary for Article {
    fn summarize(&self) -> String {
        format!("{}——{}", self.title, &self.body[..50.min(self.body.len())])
    }
}

文件 I/O 实战

让我们写一个完整的程序:统计文件中每个单词的出现频率。

rust
use std::collections::HashMap;
use std::env;
use std::fs;
use std::io;

fn main() -> io::Result<()> {
    // 从命令行参数获取文件名,默认为 "input.txt"
    let args: Vec<String> = env::args().collect();
    let filename = args.get(1).map(String::as_str).unwrap_or("input.txt");

    // 读取整个文件内容
    let contents = match fs::read_to_string(filename) {
        Ok(s) => s,
        Err(e) => {
            eprintln!("Error reading {}: {}", filename, e);
            return Err(e);
        }
    };

    // 统计词频
    let mut word_count = HashMap::new();
    for word in contents.split_whitespace() {
        let word = word.trim_matches(|c: char| !c.is_alphabetic());
        if !word.is_empty() {
            *word_count.entry(word.to_lowercase()).or_insert(0) += 1;
        }
    }

    // 按频率降序排列
    let mut entries: Vec<_> = word_count.into_iter().collect();
    entries.sort_by(|a, b| b.1.cmp(&a.1));

    // 输出前 10 个
    for (word, count) in entries.iter().take(10) {
        println!("{:20} => {}", word, count);
    }

    Ok(())
}

这个示例展示了 Rust 中常见的惯用写法:

  • HashMap::entry().or_insert() 模式:查找或插入
  • Vec::sort_by 与闭包
  • 迭代器链:iter().take(10)
  • match 进行错误分支
  • 字符串方法链:trim_matches(|c: char| !c.is_alphabetic())

模块系统

rust
// 声明模块
mod math {
    // 默认私有
    fn private_helper() -> i32 { 42 }

    // pub 导出
    pub fn add(a: i32, b: i32) -> i32 {
        a + b + private_helper()
    }
}

fn main() {
    let sum = math::add(2, 3);  // 可以访问 pub 函数
    // math::private_helper();  // 错误:私有函数
}

更大的项目通常将模块放在单独的文件或目录中,但基本原理相同:模块递归地定义可见性边界。

一个完整示例:并发 Web 服务器

以下是 Rust 并发编程的一个缩影——使用标准库的线程和通道构建简单的 TCP 服务器:

rust
use std::io::{Read, Write};
use std::net::{TcpListener, TcpStream};
use std::thread;

fn handle_client(mut stream: TcpStream) {
    let mut buffer = [0; 512];
    stream.read(&mut buffer).unwrap();

    let response = "HTTP/1.1 200 OK\r\n\r\nHello from Rust!";
    stream.write(response.as_bytes()).unwrap();
    stream.flush().unwrap();
}

fn main() -> std::io::Result<()> {
    let listener = TcpListener::bind("127.0.0.1:7878")?;
    println!("Listening on port 7878...");

    // 为每个连接创建一个线程
    for stream in listener.incoming() {
        match stream {
            Ok(stream) => {
                thread::spawn(|| handle_client(stream));
            }
            Err(e) => eprintln!("Connection failed: {}", e),
        }
    }
    Ok(())
}

这个服务器虽然简单,但体现了 Rust 的几个关键理念:

  • ? 操作符传播错误
  • match 穷尽地处理结果
  • thread::spawn + 闭包的并发模型
  • 所有资源(socket、线程)在作用域结束自动清理

小结

本章快速浏览了 Rust 的核心语法元素。关键要点:

  • 变量默认不可变mut 允许修改;let 支持遮蔽。
  • 函数参数必须标注类型,返回类型用 ->;函数体是表达式,函数末尾的无分号表达式即为返回值。
  • 控制流都是表达式ifloopmatch 都有值,可以用于赋值。
  • 类型系统丰富且安全:整数位宽固定、Option<T> 代替空指针、Result<T, E> 代替异常。
  • 结构体和方法通过 impl 块组织,&self/&mut self/self 区分借用级别。
  • 泛型和 Trait 提供静态多态,Trait 约束(T: Trait)限制泛型参数的行为。
  • 模块系统控制可见性,pub 导出,默认私有。
  • ? 运算符是错误传播的惯用语法,减少样板代码。

下一章,我们将深入 Rust 的基本类型系统——这是理解 Rust 内存模型和安全保证的基础。