概述
Rust 是一门快速、高并发、安全且具有授权性的语言,由 Mozilla 开发。
作为一门静态强类型语言,它拥有静态类型检查和禁止自动类型转换的特性,可以安全地重构代码,在编译时捕获大多数错误。作为一门多范式的语言,不仅可以编写操作系统、游戏引擎和许多性能关键型应用程序,还可以编写高性能 Web 程序、网络服务、类型安全的 ORM 库,还可以将程序编译为 WebAssembly 在浏览器中运行,或者为嵌入式平台构建安全性优先的实时应用程序。
安装
Rust 工具链由编译器 rustc 和软件包管理器 Cargo 组成,使用 rustup 来安装它们:
curl https://sh.rustup.rs -sSf | sh
默认情况下会安装稳定版的 rustc 和 Cargo 到 ~/.cargo 目录下,并更新 PATH 变量。
要将工具链更新到最新版本:
rustup update
要将 rustup 更新到最新版本:
rustup self update
程序结构
Rust 程序会被组织成模块的形式,可执行程序的根模块通常是 main.rs,库文件的根模块则是 lib.rs。
use std::env;
fn main() {
let name = env::args().skip(1).next();
match name {
Some(n) => println!("Hi there! {}", n),
None => panic!("Didn't receive any name?")
}
}
use
从std
库中导入了一个模块env
。args
是env
模块的的一个函数,返回传递给程序的参数的迭代器。由于第 1 个参数是函数名,所以用skip()
跳过它。由于迭代器是惰性的,并且不会预先计算,所以调用next()
,返回一个名为Option
的枚举类型。Option
的值可能是Some
也可能是None
,因为用户可能没有提供参数。match
表达式将检查Option
的值,并执行对应的分支。
基本数据类型
bool
类型用true
表示真,false
表示假。char
表示单个字符。- 整数,根据位宽和是否有符号分为
i8
、i16
、i32
、i64
、i128
、u8
、u16
、u32
、u64
、u128
、isize
和usize
。 - 浮点数根据位宽分为
f32
和f64
。 [T; N]
表示固定数组的大小,T
表示元素的数据类型,N
表示元素数量,并且是编译器非负常数。[T]
表示动态大小的连续序列的视图,T
表示元素的数据类型。str
表示字符串切片,主要用作引用,即&str
。(T1, T2, ...)
表示有限序列,TN
可以是不同的数据类型。fn(T1)->T2
表示接收参数T1
并返回T2
类型的函数,如果有多个参数,用,
隔开。
声明变量
用let
声明一个变量,但这个变量是不可变的,初始化后便不能再为其赋值。变量名后面的:
用于标注该变量的数据类型,如果省略它的话,编译器会尝试根据初始化的值为这个变量推断一个合适的数据类型。
let a = 42;
let pi: f32 = 3.14;
用let mut
声明一个可变变量,可以用=
为这个变量再次赋值。
let mut a = 42;
a = 13;
函数
用fn
定义一个函数,()
中的是函数可以接收的参数,->
后面的是函数的返回值类型。
fn add(a: i64, b: i64) {
a + b
}
调用函数:
let a = 17;
let b = 3;
let result = add(a, b); // result: 20
如果希望修改函数的参数,可以在参数名前面加上mut
:
fn increase_by(mut val: u32, how_much: u32) {
val += how_much;
println!("You made {} points", val);
}
函数体最后一行的表达式就是函数的返回值(注意没有以;
结尾),也可以用return
关键字提前返回。
闭包
Rust 的闭包本质上是一个匿名函数,它可以捕获声明它的环境中的变量。闭包的主体可以是单一表达式或{}
中的语句块。
let doubler = |x| x * 2;
let value = 5;
let twice = doubler(value);
println!("{} doubled is {}", value, twice);
let big_closure = |b, c| {
let z = b + c;
z * twice
};
let some_number = big_closure(1, 2);
println!("Result from closure: {}", some_number);
闭包主要用作高阶函数的参数,高阶函数是一个以另一个函数或闭包作为参数的函数。
字符串
字符串在 Rust 中有两种形式:&str
类型或String
类型。String
类型是分配在堆上的,&str
则是指向现有字符串的指针,这些字符串既可以在堆上也可以在栈上,也可以是已编译对象代码的数据段中的字符串。
let question = "How are you?";
let person: String = "Bob".to_string();
let namaste = String::from("春日一番");
println!("{}! {} {}", namaste, question, person);
上述代码的question
是&Str
类型,person
和namaste
是String
类型的。Rust 的字符串均为 UTF-8 编码。
&
是一个运算符,用于创建指向任何类型的指针。
条件和判断
if
Rust 的if
是一个表达式而非语句,意味着它总是会返回一个值(既可以是empty
类型的()
,也可以是实际的值),即花括号最后一行的表达式,因此if
和else
应该具有相同类型的返回值。
let result = if 1 == 2 {
"Wait, what?"
} else {
"Rust make sense"
};
println!("You know what? {}.", result);
如果if
有一个返回值,则else
分支是不可省略的,否则当条件为false
时结果将是()
,编译器会报错。要省略else
分支,则if
分支不能有返回值,可以在最后一行的表达式后面加上;
告诉编译器丢弃这个值。
match
match
会根据给定的值来决定执行哪个分支,相较if
适合处理更加复杂的多值多条件判断。
fn req_status() -> u32 {
200
}
fn main() {
let status = req_status();
match status {
200 => println!("Success"),
404 => println!("Not Found"),
other => {
println!("Request failed with code: {}", other);
}
}
}
match
在{}
中的分支被称为匹配臂,每个匹配臂必须返回相同类型的值。=>
左侧是待选的值,右侧是表达式,如果是单行表达式还需要用,
分隔,other
表示上面给出的待选值均不匹配的情况。
和if
一样,match
表达式也可以将返回值赋给一个变量。
循环
loop
loop
表示无限循环,用break
语句可以跳出循环,如果使用名称标记循坏代码块还可以用break
直接跳出指定的循环体。
fn silly_sub(a: i32, b: i32) -> i32 {
let mut result = 0;
'increment: loop {
if result == a {
let mut dec = b;
loop {
if dec == 0 {
break 'increment;
} else {
result -= 1;
dec -= 1;
}
}
} else {
result += 1;
}
}
result
}
fn main() {
let a = 10;
let b = 4;
let result = silly_sub(a, b);
println!("{} minus {} is {}", a, b, result);
}
while
while
会在条件成立时一直循环。
let mut x = 100;
while x > 0 {
println!("{} more runs to go", x);
x -= 1;
}
for
for
只适用于可以转换为迭代器的类型。
for i in 0..10 {
print!("{},", i);
}
0..10
表示从0
到9
的整数序列,0..=10
表示从0
到10
的整数序列。
自定义数据类型
struct
struct
用于声明结构体,结构体的声明形式有 3 种:单元结构体、元组结构体和类 C 语言的结构体。
单元结构体只需要给出名称即可,运行时不占用任何空间,也不关联任何数据,通常用于表述错误或状态。
struct Dummy;
fn main() {
let value = Dummy;
}
元组结构体的成员没有命名,而是根据在定义中的位置来访问。Rust 中通过索引访问成员使用.
而不是[]
。
struct Color(u8, u8, u8);
fn main() {
let white = Color(255, 255, 255);
let red = white.0;
let green = white.1;
let blue = white.2;
println!("White: R:{}, G:{}, B:{}", red, green, blue);
}
还可以将结构体变量直接解构赋值给多个变量。如果希望丢弃某个值,可以用_
替代对应的变量。
let orange = Color(255, 165, 0);
let Color(red, green, blue) = orange;
println!("Orange: R:{}, G:{}, B:{}", red, green, blue);
类 C 语言的结构体在声明时需要给出成员的数据类型,成员之间用,
隔开,{}
后面不需要加;
。
struct Player {
name: String,
iq: u8,
friends: u8,
score: u16
}
fn bump_player_score(mut player: Player, score: u16) {
player.score += score;
println!("Updated player stats:");
println!("Name: {}", player.name);
println!("IQ: {}", player.iq);
println!("Friends: {}", player.friends);
println!("Score: {}", player.score);
}
fn main() {
let name = "Alice".to_string();
let player = Player {
name,
iq: 171,
friends: 134,
score: 1129 };
bump_player_score(player, 120);
}
在结构体前面加上mut
表示该结构体的所有成员都是可修改的。
enum
enum
用于声明枚举。
#[derive(Debug)]
enum Direction {
N,
E,
S,
W
}
enum PlayerAction {
Move {
direction: Direction,
speed: u8
},
Wait,
Attack(Direction)
}
fn main() {
let simulated_player_action = PlayerAction::Move {
direction: Direction::N,
speed: 2,
};
match simulated_player_action {
PlayerAction::Wait => println!("Player wants to wait"),
PlayerAction::Move { direction, speed } => {
println!("Player wants to move in direction {:?} with speed {}", direction, speed)
}
PlayerAction::Attack(direction) => {
println!("Player wants to attack direction {:?}", direction)
}
};
}
类型上的函数和方法
impl
块可以为自定义的数据类型或包装器类型提供行为的实现。
struct Player {
name: String,
iq: u8,
friends: u8
}
impl Player {
fn with_name(name: &str) -> Player {
Player {
name: name.to_string(),
iq: 100,
friends: 100
}
}
fn get_friends(&self) -> u8 {
self.friends
}
fn set_friends(&mut self, count: u8) {
self.friends = count;
}
}
fn main() {
let mut player = Player::with_name("Dave");
player.set_friends(23);
println!("{}'s friends count: {}", player.name, player.get_friends());
let _ = Player::get_friends(&player);
}
with_name
被称为关联方法,它没有使用self
作为首个参数,类似于其它编程语言中的静态方法。通常是在类型自身上用::
调用而不需要通过类的实例。
let player = Player::with_name("Dave");
get_friends
和set_friends
被称为实例方法,只能在已创建的实例上用.
调用。
player.get_friends();
集合
数组
数组[T;N]
具有固定的长度,可以存储相同类型的元素,T
是元素的数据类型,N
是一个表示长度的usize
类型字面量。
let numbers: [u8;10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
println!("Number: {}", numbers[5]); // Number: 5
访问数组中的成员使用[index]
。
元组
元组是异构集合,可以存储不同类型的元素,通常作为函数的返回值。
let num_and_str: (u8, &str) = (42, "hello");
let (n, s) = num_and_str;
println!("Num: {}, Str: {}", n, s);
向量
向量是一个动态长度的存储相同类型元素的集合,分配在堆上,既可以用Vec::new()
创建,也可以用vec![]
创建。
let mut numbers_vec: Vec<u8> = Vec::new();
numbers_vec.push(1);
numbers_vec.push(2);
let mut vec_with_macro = vec![1];
vec_with_macro.push(2);
let _ = vec_with_macro.pop();
let message = if numbers_vec == vec_with_macro {
"They are equal"
} else {
"Nah! They look different to me"
};
println!("{} {:?} {:?}", message, numbers_vec, vec_with_macro);
push()
将元素追加到向量的末尾,pop()
移除并返回末尾的元素。
哈希表
哈希表来自std::collections
模块,用于存储键值对,使用HashMap::new
创建。
use std::collections::HashMap;
fn main() {
let mut fruits = HashMap::new();
fruits.insert("apple", 3);
fruits.insert("mango", 6);
fruits.insert("orange", 2);
fruits.insert("avocado", 7);
for (k, v) in &fruits {
println!("I got {} {}", v, k);
}
fruits.remove("orange");
let old_avocado = fruits["avocado"];
fruits.insert("avocado", old_avocado + 5);
println!("\nI now have {} avocados", fruits["avocado"]);
}
切片
切片是指向现有集合类型的其它变量所拥有的连续区间,用&[T]
表示。
let mut numbers: [u8; 4] = [1, 2, 3, 4];
{
let all: &[u8] = &numbers[..];
println!("All of them: {:?}", all);
}
{
let first_two: &mut [u8] = &mut numbers[0..2];
first_two[0] = 100;
first_two[1] = 99;
println!("Look ma! I can modify through slices: {:?}", numbers);
}