Rust 基础概念

前言

Rust 是一门系统编程语言,它的目标是提供一种安全、并发、实用的编程语言。Rust 的设计借鉴了现有的一些编程语言,如 C++、Haskell、OCaml 等,但是它又有自己的特色,比如内存安全、线程安全等。本文旨在通过类比前端编程知识来解释 Rust 的语法

变量和数据类型

Rust 是一门静态类型语言,变量在使用前需声明,数据类型包括基本类型、复合类型和自定义类型。

基本类型

  • 整数:有符号和无符号,如 i32u64
  • 浮点数:单精度 f32 和双精度 f64
  • 布尔值:bool,取值为 truefalse
  • 字符:char,使用单引号表示。

整数

Rust 的整数类型包括有符号和无符号两种类型,分别有 8、16、32、64 和 arch(与机器架构相关)五种位数。

1let a: i32 = 42;
2let b: u64 = 0xff;

浮点数

Rust 的浮点数类型包括单精度(f32)和双精度(f64)两种。

1let a: f32 = 3.14;
2let b: f64 = 3.14159265358979323846;

布尔值

Rust 的布尔值类型为 bool,有两个值:truefalse

1let a: bool = true;
2let b: bool = false;

字符

Rust 的字符类型为 char,使用单引号表示。

1let a: char = 'A';
2let b: char = '字';

复合类型

  • 元组(tuple):包含多个类型的值的组合。
  • 数组(array):固定长度的相同类型元素集合。

元组

元组是一个可以包含多个类型的值的组合,类似 TS 的元组。

1let a: (i32, f64, bool) = (42, 3.14, true);

访问元组中的值可以使用模式匹配或者使用点号加索引。

1let (x, y, z) = a;
2println!("x: {}, y: {}, z: {}", x, y, z); // 输出:x: 42, y: 3.14, z: true
3
4let first = a.0;
5let second = a.1;
6let third = a.2;
7println!("first: {}, second: {}, third: {}", first, second, third); // 输出:first: 42, second: 3.14, third: true

数组

数组是具有固定长度的相同类型元素的集合,类似 TS 的数组。

1let a: [i32; 5] = [1, 2, 3, 4, 5];

访问数组中的元素可以使用索引。

1let first = a[0];
2let second = a[1];
3println!("first: {}, second: {}", first, second); // 输出:first: 1, second: 2

自定义类型

  • 结构体(struct):类似于 JavaScript 的对象。
  • 枚举(enum):表示多种类型。
  • Trait:定义共享行为。

结构体

Rust 的结构体(struct)类似于 JavaScript 的对象。结构体可以包含多个字段,每个字段需要指定类型。

1struct Point {
2    x: f64,
3    y: f64,
4}
5
6let p = Point { x: 1.0, y: 2.0 };
7println!("point: ({}, {})", p.x, p.y);

枚举

Rust 的枚举(enum)类似于 JavaScript 的类,但它可以表示多种类型。

1enum Message {
2    Quit,
3    Move { x: i32, y: i32 },
4    Write(String),
5    ChangeColor(i32, i32, i32),
6}
7
8let m = Message::Write(String::from("hello"));

Trait

Trait 是 Rust 中的一种特殊类型,用于定义共享行为。Trait 可以被其他类型实现,从而获得 Trait 中定义的方法。

1trait Printable {
2    fn print(&self);
3}
4
5struct Point {
6    x: i32,
7    y: i32,
8}
9
10impl Printable for Point {
11    fn print(&self) {
12        println!("({}, {})", self.x, self.y);
13    }
14}
15
16let p = Point { x: 1, y: 2 };
17p

控制流

Rust 支持常见的控制流结构,如 ifelsewhilefor 等。

if 和 else

Rust 的 if 和 else 语法与 JavaScript 类似。

1let a = 42;
2
3if a < 0 {
4    println!("a is negative");
5} else if a > 0 {
6    println!("a is positive");
7} else {
8    println!("a is zero");
9}

while

Rust 的 while 语法与 JavaScript 类似。

1let mut a = 5;
2
3while a > 0 {
4    println!("a is {}", a);
5    a -= 1;
6}

for

Rust 的 for 语法可以用于遍历集合,如数组和范围。

1let a = [1, 2, 3, 4, 5];
2
3for element in a.iter() {
4    println!("the value is: {}", element);
5}

使用范围:

1for number in (1..4).rev() {
2    println!("{}!", number);
3}
4println!("LIFTOFF!!!");

函数

Rust 的函数定义使用 fn 关键字,参数和返回值需指定类型。 返回值类型使用 -> 指定。

1fn add(a: i32, b: i32) -> i32 {
2    a + b
3}
4
5let result = add(1, 2);
6println!("1 + 2 = {}", result);

这只是 Rust 语言的基本概念和语法,更多的功能和特性需要在实际项目中不断学习和探索。希望本文能为前端开发者提供一个 Rust 入门的参考。

错误处理

Rust 提供了一种强大的错误处理机制,可以帮助你编写健壮的代码。Rust 有两种错误类型:可恢复错误(recoverable errors)和不可恢复错误(unrecoverable errors)。Rust 使用 Result 枚举处理可恢复错误;使用 panic! 宏处理不可恢复错误。

可恢复错误

可恢复错误是那些可以通过返回一个 Result 类型来处理的错误。Result 类型是一个枚举类型,有两个变体:OkErr。例如在这个示例中,尝试打开一个文件。如果文件打开成功,将得到一个 Ok 变体,其中包含一个 File 对象。

1use std::fs::File;
2
3fn main() {
4    let f = File::open("hello.txt");
5
6    match f {
7        Ok(file) => println!("File opened: {:?}", file),
8        Err(error) => println!("Failed to open file: {:?}", error),
9    }
10}

Result

Result 是一个枚举类型,用于表示可能出错的操作的结果。它有两个变体:OkErr

1fn divide(a: i32, b: i32) -> Result<i32, String> {
2    if b == 0 {
3        Err("division by zero".to_string())
4    } else {
5        Ok(a / b)
6    }
7}
8
9let a = divide(4, 2);
10let b = divide(4, 0);

可以使用模式匹配来处理 Result 类型的值。

1match a {
2    Ok(value) => println!("a: {}", value),
3    Err(error) => println!("a: error: {}", error),
4}
5
6match b {
7    Ok(value) => println!("b: {}", value),
8    Err(error) => println!("b: error: {}", error),
9}

不可恢复错误

不可恢复错误是那些无法通过返回一个 Result 类型来处理的错误。这类错误通常表示程序中的严重问题,需要立即终止程序的执行。在 Rust 中,可以使用 panic! 宏来触发不可恢复错误。例如在这个示例中,使用 panic! 宏来触发一个不可恢复错误。当程序执行到这一行时,它将立即终止,并显示一个错误消息。

1fn main() {
2    panic!("crash and burn");
3}

panic!

panic! 是一个宏,用于在遇到不可恢复错误时终止程序。例如

1fn main() {
2    panic!("This is an unrecoverable error.");
3}

模块系统

Rust 的模块系统用于组织和管理代码。模块系统包括以下几个部分:模块(module)、包(package)、库(library)和二进制(binary)。

模块

模块是 Rust 中的命名空间,用于将相关的代码组织在一起。可以使用 mod 关键字定义模块。

1mod math {
2    pub fn add(a: i32, b: i32) -> i32 {
3        a + b
4    }
5
6    pub fn subtract
7}

在一个模块中,可以使用 pub 关键字将函数、结构体、枚举等声明为公共的,这样它们就可以在其他模块中被访问。

1mod math {
2    pub fn add(a: i32, b: i32) -> i32 {
3        a + b
4    }
5
6    pub fn subtract(a: i32, b: i32) -> i32 {
7        a - b
8    }
9}
10
11fn main() {
12    let sum = math::add(1, 2);
13    let difference = math::subtract(3, 1);
14    println!("sum: {}, difference: {}", sum, difference);
15}

包和库

包(package)是一个包含一个或多个库(library)的项目。库是一组相关的模块。在 Rust 中,可以使用 Cargo 工具来创建和管理包和库。

要创建一个新的包,可以使用 cargo new 命令

这将创建一个名为 my_package 的新包,其中包含一个名为 my_package 的库。库的源代码位于 src/lib.rs 文件中。

要在包中添加一个新的库,可以在 src 目录下创建一个新的子目录,并在其中创建一个 lib.rs 文件。

二进制

二进制(binary)是一个可执行程序。在 Rust 中,可以使用 Cargo 工具来创建和管理二进制。

要创建一个新的二进制,可以使用 cargo new --bin 命令:

cargo new 这将创建一个名为 my_binary 的新二进制,其中包含一个名为 my_binary 的可执行程序。程序的源代码位于 src/main.rs 文件中。

要在包中添加一个新的二进制,可以在 src/bin 目录下创建一个新的 .rs 文件。

所有权与借用

所有权(ownership)

所有权是 Rust 中的一个核心概念,用于管理内存。在 Rust 中,每个值都有一个唯一的所有者,当所有者离开作用域时,值将被自动回收。这种机制可以避免内存泄漏和悬垂指针等问题。

1{
2    let s = String::from("hello");
3} // s 离开作用域,内存被回收

当一个值被赋值给另一个变量时,原变量将失去所有权,值的所有权将转移给新变量。

1let s1 = String::from("hello");
2let s2 = s1; // s1 失去所有权,s2 获得所有权

借用(borrowing)

借用是 Rust 中的另一个核心概念,用于在不转移所有权的情况下访问值。借用有两种形式:可变借用(mutable borrowing)和不可变借用(immutable borrowing)。

不可变借用

不可变借用是对值的只读访问。可以使用 & 符号创建不可变引用。

1fn print_length(s: &String) {
2    println!("length: {}", s.len());
3}
4
5let s = String::from("hello");
6print_length(&s); // 不可变借用 s

可变借用

可变借用是对值的可写访问。可以使用 &mut 符号创建可变引用。

1fn append_world(s: &mut String) {
2    s.push_str(" world");
3}
4
5let mut s = String::from("hello");
6append_world(&mut s); // 可变借用 s
7println!("{}", s); // 输出:hello world

注意:在同一作用域中

并发编程

Rust 提供了一些原生的并发编程特性,如线程、通道和互斥锁。这些特性可以编写高效且安全的并发代码。

线程

线程是操作系统中的并发执行单元。在 Rust 中,可以使用 std::thread 模块创建和管理线程。

1use std::thread;
2use std::time::Duration;
3
4fn main() {
5    let handle = thread::spawn(|| {
6        for i in 1..10 {
7            println!("thread: {}", i);
8            thread::sleep(Duration::from_millis(1));
9        }
10    });
11
12    for i in 1..5 {
13        println!("main: {}", i);
14        thread::sleep(Duration::from_millis(1));
15    }
16
17    handle.join().unwrap();
18}

通道

通道(channel)是一种用于在线程之间传递消息的同步原语。在 Rust 中,可以使用 std::sync::mpsc 模块创建和管理通道。

1use std::sync::mpsc;
2use std::thread;
3
4fn main() {
5    let (tx, rx) = mpsc::channel();
6
7    let handle = thread::spawn(move || {
8        let val = String::from("hi");
9        tx.send(val).unwrap();
10    });
11
12    let received = rx.recv().unwrap();
13    println!("Got: {}", received);
14
15    handle.join().unwrap();
16}

互斥锁

互斥锁(mutex)是一种用于在线程之间同步访问共享资源的同步原语。在 Rust 中,可以使用 std::sync::Mutex 类型创建和管理互斥锁。

1use std::sync::{Arc, Mutex};
2use std::thread;
3
4fn main() {
5    let counter = Arc::new(Mutex::new(0));
6    let mut handles = vec![];
7
8    for _ in 0..10 {
9        let counter = Arc::clone(&counter);
10        let handle = thread::spawn(move || {
11            let mut num = counter.lock().unwrap();
12            *num += 1;
13        });
14        handles.push(handle);
15    }
16
17    for handle in handles {
18        handle.join().unwrap();
19    }
20
21    println!("Result: {}", *counter.lock().unwrap());
22}

在这个示例中,使用 Arc(原子引用计数)和 Mutex(互斥锁)来同步访问一个共享的 counter 变量。创建了 10 个线程,每个线程都尝试对 counter 进行加 1 操作。通过使用互斥锁,确保了在任何时刻只有一个线程可以访问 counter。