Rust语言教程:从基础到高级编程全面指南

Rust 语言教程:从基础到高级编程全面指南

Rust 是一门系统级编程语言,以其内存安全、并发性能和零成本抽象而著称。它被设计用来构建高性能、可靠的软件,例如操作系统、游戏引擎、WebAssembly 模块以及各种需要高性能和安全性的应用。本教程将带领你从 Rust 的基础知识逐步深入到高级编程概念,让你全面掌握这门强大的语言。

第一部分:Rust 基础

1.1 安装与配置

首先,你需要安装 Rust 编译器和包管理器 Cargo。访问 Rust 官方网站 https://www.rust-lang.org/ ,按照说明下载并安装适合你操作系统的 Rust 工具链。

安装完成后,在终端或命令行中输入以下命令来验证安装是否成功:

bash
rustc --version
cargo --version

如果显示出版本号,则表示安装成功。

1.2 Hello, World!

按照惯例,我们从一个简单的 "Hello, World!" 程序开始:

rust
fn main() {
println!("Hello, World!");
}

将这段代码保存为 main.rs 文件。然后,在终端中进入该文件所在的目录,使用 Cargo 构建并运行程序:

bash
cargo new hello_world //创建一个新的cargo 项目
cd hello_world //进入项目目录
cargo run //编译运行

你会在终端看到输出 "Hello, World!"。

代码解释:

  • fn main():定义了程序的入口点,main 函数是每个 Rust 可执行程序必须有的。
  • println!("Hello, World!");:这是一个宏(macro),用于将文本打印到控制台。! 表明它是一个宏调用。

1.3 变量与数据类型

Rust 是静态类型语言,这意味着在编译时必须知道所有变量的类型。

1.3.1 变量声明

使用 let 关键字声明变量。Rust 具有类型推断能力,通常不需要显式指定类型:

rust
let x = 5; // Rust 推断 x 为 i32 类型
let y: f64 = 3.14; // 显式指定 y 为 f64 类型

1.3.2 可变性

默认情况下,Rust 中的变量是不可变的(immutable)。要声明可变变量,使用 mut 关键字:

rust
let mut z = 10;
z = 20; // 可以修改 z 的值

1.3.3 基础数据类型

Rust 有以下几种基础数据类型:

  • 整数类型: i8, i16, i32, i64, i128, isize(有符号整数)和 u8, u16, u32, u64, u128, usize(无符号整数)。isizeusize 的大小取决于你的计算机架构(32 位或 64 位)。
  • 浮点数类型: f32(单精度)和 f64(双精度)。
  • 布尔类型: bool,值为 truefalse
  • 字符类型: char,表示一个 Unicode 标量值(4 个字节)。
  • 字符串类型: &str 字符串切片,String可变字符串。

rust
let a: i32 = -10;
let b: u64 = 100;
let c: f32 = 2.5;
let d: bool = true;
let e: char = '🚀';
let str_slice: &str = "hello"; //字符串切片
let mut my_string: String = String::from("Hello"); //可变字符串
my_string.push_str(", world!");

1.4 运算符

Rust 支持常见的算术、比较、逻辑和位运算符:

```rust
let sum = 5 + 10;
let difference = 10 - 5;
let product = 4 * 30;
let quotient = 56.7 / 32.2;
let remainder = 43 % 5;

let and = true && false;
let or = true || false;
let not = !true;

let greater = 10 > 5;
let less_equal = 5 <= 5;
```

1.5 控制流

1.5.1 if 表达式

```rust
let number = 7;

if number < 5 {
println!("condition was true");
} else if number == 5 {
println!("number is 5");
} else {
println!("condition was false");
}
```

if 表达式可以有返回值:

rust
let condition = true;
let number = if condition { 5 } else { 6 };

1.5.2 loop 循环

loop 关键字创建一个无限循环,通常与 break 结合使用:

```rust
let mut counter = 0;

loop {
counter += 1;

if counter == 10 {
    break; // 退出循环
}

}
```

loop 循环也可以有返回值:

```rust
let result = loop {
counter += 1;
if counter == 10 {
break counter * 2; //返回 counter*2
}
};

```

1.5.3 while 循环

```rust
let mut number = 3;

while number != 0 {
println!("{}", number);
number -= 1;
}
```

1.5.4 for 循环

for 循环用于遍历一个迭代器(例如范围、数组、向量):

```rust
for number in (1..4).rev() { //迭代1到4,不包含4
println!("{}", number);
}

let a = [10, 20, 30, 40, 50];

for element in a.iter() {
println!("the value is: {}", element);
}
```

1.6 函数

使用 fn 关键字定义函数:

```rust
fn greet(name: &str) {
println!("Hello, {}!", name);
}

fn add(x: i32, y: i32) -> i32 {
x + y // 表达式作为返回值,不需要 return 关键字
}

fn main(){
greet("Alice");
let sum = add(5,3);
println!("{}",sum);
}

```

  • fn greet(name: &str):定义了一个名为 greet 的函数,它接受一个字符串切片(&str)类型的参数 name
  • fn add(x: i32, y: i32) -> i32:定义了一个名为 add 的函数,它接受两个 i32 类型的参数 xy,并返回一个 i32 类型的值。
  • 在 Rust 中,函数的最后一个表达式的值会被隐式返回,因此不需要 return 关键字(当然,你也可以使用 return 来提前返回)。

1.7 注释

Rust 支持两种注释方式:

  • 单行注释://
  • 多行注释:/* ... */
  • 文档注释:/// (用于函数,模块等的文档)

```rust
// 这是单行注释

/
这是
多行注释
/

/// 计算两个数的和。文档注释
///
/// # Examples
///
/// /// let result = add(2, 3);
/// assert_eq!(result, 5);
///

fn add(a: i32, b: i32) -> i32 {
a + b
}

```

第二部分:Rust 核心概念

2.1 所有权(Ownership)

所有权是 Rust 最核心、最独特的概念,它保证了 Rust 程序的内存安全,避免了悬垂指针、数据竞争等问题。理解所有权是掌握 Rust 的关键。

所有权规则:

  1. 每个值都有一个被称为其所有者的变量。
  2. 在同一时间,只能有一个所有者。
  3. 当所有者超出作用域时,该值将被丢弃(释放内存)。

rust
{
let s = String::from("hello"); // s 进入作用域
// ... 使用 s ...
} // s 超出作用域,s 拥有的内存被释放(调用 drop 函数)

2.1.1 移动(Move)

当将一个变量赋值给另一个变量时,会发生所有权的转移(移动):

```rust
let s1 = String::from("hello");
let s2 = s1; // s1 的所有权移动到 s2

// println!("{}", s1); // 错误!s1 不再有效
println!("{}", s2);
```

这里,s1 的值被移动到 s2s1 不再有效。这是因为 String 类型的数据存储在堆上,如果两个变量都指向同一块内存,会导致 double free 的问题。

2.1.2 克隆(Clone)

如果你需要复制堆上的数据,可以使用 clone 方法:

```rust
let s1 = String::from("hello");
let s2 = s1.clone(); // 深拷贝 s1 的数据

println!("s1 = {}, s2 = {}", s1, s2); // s1 和 s2 都有效
```

2.1.3 拷贝(Copy)

对于存储在栈上的数据类型(例如整数、浮点数、布尔值、字符、元组),赋值操作会进行拷贝:

```rust
let x = 5;
let y = x; // 拷贝 x 的值

println!("x = {}, y = {}", x, y); // x 和 y 都有效
```

2.2 借用(Borrowing)

借用允许你引用一个值而不获取其所有权。借用使用 & 符号表示。

2.2.1 不可变借用

```rust
fn calculate_length(s: &String) -> usize { // s 是对 String 的引用
s.len()
} // s 在这里超出作用域,但它并不拥有 String,所以 String 不会被丢弃

fn main(){
let s1 = String::from("hello");
let len = calculate_length(&s1); // 传递 s1 的引用
println!("The length of '{}' is {}.", s1, len); //s1仍然有效
}

```

2.2.2 可变借用

使用 &mut 创建可变借用,允许你修改引用的值:

```rust
fn change(some_string: &mut String) {
some_string.push_str(", world");
}

fn main() {
let mut s = String::from("hello");
change(&mut s);
println!("{}",s);
}

```

借用规则:

  1. 在同一时间,你可以有多个不可变借用,或者只有一个可变借用。
  2. 引用必须始终有效(不能引用已经释放的内存)。

这些规则由 Rust 的借用检查器(borrow checker)在编译时强制执行,确保了内存安全。

2.3 生命周期(Lifetimes)

生命周期是 Rust 用来确保引用始终有效的机制。它们是泛型的一种,用于描述引用的有效范围。

基本语法:

rust
&'a i32 // 具有生命周期 'a 的 i32 引用
&'a mut i32 // 具有生命周期 'a 的可变 i32 引用

生命周期注解通常用于函数签名中,以指定输入引用和输出引用之间的关系:

rust
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() {
x
} else {
y
}
}

这个函数的签名表示:xy 这两个字符串切片具有相同的生命周期 'a,并且返回的字符串切片也具有相同的生命周期 'a。这意味着返回的引用不会比 xy 中的任何一个活得更久。

生命周期省略规则

在很多情况下,你可以省略生命周期注解,Rust 编译器会根据一些规则自动推断:

  1. 每个引用参数都有一个自己的生命周期参数。
  2. 如果只有一个输入生命周期参数,那么该生命周期会被赋给所有输出生命周期参数。
  3. 如果有多个输入生命周期参数,但其中一个是 &self&mut self,那么 self 的生命周期会被赋给所有输出生命周期参数。

第三部分:Rust 高级特性

3.1 结构体(Structs)

结构体用于创建自定义数据类型:

```rust
struct Point {
x: i32,
y: i32,
}

fn main() {
let origin = Point { x: 0, y: 0 };
println!("The origin is at ({}, {})", origin.x, origin.y);

let mut p = Point{x:1,y:2}; //可变结构体
p.x = 10;

}

```

3.2 枚举(Enums)

枚举用于定义一个类型,该类型可以是几个不同值中的一个:

```rust
enum Message {
Quit,
Move { x: i32, y: i32 }, // 包含命名字段
Write(String), // 包含一个 String
ChangeColor(i32, i32, i32), // 包含三个 i32
}

fn main(){
let m1 = Message::Quit;
let m2 = Message::Move{x:10,y:20};
}

```

3.2.1 Option 枚举

Option 是一个标准库中定义的枚举,用于表示一个值可能存在或不存在的情况:

rust
enum Option<T> {
Some(T),
None,
}

它用于处理可能失败的操作,避免了空指针异常。

```rust
fn divide(numerator: f64, denominator: f64) -> Option {
if denominator == 0.0 {
None
} else {
Some(numerator / denominator)
}
}

fn main(){
let result = divide(10.0,2.0);

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

}
```

3.3 模式匹配(Pattern Matching)

match 表达式允许你将一个值与一系列模式进行比较,并执行匹配的模式对应的代码:

```rust
let number = 1;

match number {
1 => println!("One"),
2 => println!("Two"),
3 => println!("Three"),
_ => println!("Something else"), // _ 匹配所有其他情况
}
```

match 还可以用于解构枚举、结构体等:

```rust
enum Message {
Quit,
Move { x: i32, y: i32 },
Write(String),
ChangeColor(i32, i32, i32),
}
fn main(){
let msg = Message::Move { x: 10, y: 20 };

match msg {
    Message::Quit => println!("Quit"),
    Message::Move { x, y } => println!("Move to x: {}, y: {}", x, y),
    Message::Write(text) => println!("Text message: {}", text),
    Message::ChangeColor(r, g, b) => println!("Change color to r: {}, g: {}, b: {}", r, g, b),
}

}

```

3.4 泛型(Generics)

泛型允许你编写可以处理多种类型的代码,而无需为每种类型编写单独的代码:

```rust
fn largest(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![34, 50, 25, 100, 65];
let result = largest(&number_list);
println!("The largest number is {}", result);

let char_list = vec!['y', 'm', 'a', 'q'];
let result = largest(&char_list);
println!("The largest char is {}", result);

}

```

  • fn largest<T: PartialOrd + Copy>(list: &[T]) -> T:定义了一个泛型函数 largest,它接受一个类型为 T 的切片,并返回一个类型为 T 的值。
  • T: PartialOrd + Copy:这是一个 trait bound(特征约束),它指定了类型 T 必须实现 PartialOrd(用于比较)和 Copy(用于拷贝)这两个 trait。

3.5 特征(Traits)

特征定义了共享行为。它们类似于其他语言中的接口(interfaces):

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

struct NewsArticle {
headline: String,
location: String,
author: String,
content: String,
}

impl Summary for NewsArticle {
fn summarize(&self) -> String {
format!("{}, by {} ({})", self.headline, self.author, self.location)
}
}

fn main() {
let article = NewsArticle {
headline: String::from("Penguins win the Stanley Cup Championship!"),
location: String::from("Pittsburgh, PA, USA"),
author: String::from("Iceburgh"),
content: String::from(
"The Pittsburgh Penguins once again are the best \
hockey team in the NHL.",
),
};

println!("New article available! {}", article.summarize());

}

```

  • trait Summary:定义了一个名为 Summary 的 trait,它有一个 summarize 方法。
  • impl Summary for NewsArticle:为 NewsArticle 结构体实现了 Summary trait。

3.6 错误处理

Rust 使用 Result 枚举来处理可能失败的操作:

rust
enum Result<T, E> {
Ok(T),
Err(E),
}

  • Ok(T):表示操作成功,并包含结果值 T
  • Err(E):表示操作失败,并包含错误值 E

```rust
use std::fs::File;
use std::io::Read;

fn read_username_from_file() -> Result {
let mut f = File::open("username.txt")?;
let mut s = String::new();
f.read_to_string(&mut s)?;
Ok(s)
}

fn main(){
let result = read_username_from_file();
match result {
Ok(username) => println!("Username: {}", username),
Err(error) => println!("Error: {}", error),
}
}

```

? 运算符用于简化错误传播。如果 File::openread_to_string 返回 Err? 会将错误返回给调用者。

3.7 并发(Concurrency)

Rust 的所有权和类型系统使其能够安全、高效地处理并发。

3.7.1 线程(Threads)

```rust
use std::thread;
use std::time::Duration;

fn main() {
let handle = thread::spawn(|| {
for i in 1..10 {
println!("hi number {} from the spawned thread!", i);
thread::sleep(Duration::from_millis(1));
}
});

for i in 1..5 {
    println!("hi number {} from the main thread!", i);
    thread::sleep(Duration::from_millis(1));
}

handle.join().unwrap(); // 等待 spawned 线程结束

}
```

3.7.2 消息传递(Message Passing)

```rust
use std::sync::mpsc;
use std::thread;

fn main() {
let (tx, rx) = mpsc::channel(); // 创建一个通道

thread::spawn(move || {
    let val = String::from("hi");
    tx.send(val).unwrap(); // 发送消息
});

let received = rx.recv().unwrap(); // 接收消息
println!("Got: {}", received);

}
```

  • mpsc::channel():创建一个通道,返回一个发送者(tx)和一个接收者(rx)。
  • tx.send(val):发送消息到通道。
  • rx.recv():从通道接收消息。

3.7.3 共享状态(Shared State)

```rust
use std::sync::{Arc, Mutex};
use std::thread;

fn main() {
let counter = Arc::new(Mutex::new(0)); // 使用 Arc 和 Mutex 实现共享可变状态
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());

}
```

  • Arc:原子引用计数指针,允许多个线程拥有相同的数据。
  • Mutex:互斥锁,用于保护共享数据,确保在同一时间只有一个线程可以访问数据。

总结

本教程涵盖了 Rust 语言的广泛主题,从基础语法到高级概念。通过学习本教程,你应该已经掌握了 Rust 的核心概念,包括所有权、借用、生命周期、结构体、枚举、模式匹配、泛型、特征、错误处理和并发。

要成为一名熟练的 Rust 开发者,还需要不断练习和探索。阅读 Rust 官方文档(https://doc.rust-lang.org/book/),参与 Rust 社区,并尝试编写自己的项目,这些都是提高 Rust 技能的好方法。祝你在 Rust 编程之旅中取得成功!

THE END