Rust文档精简版
没学过本语言的建议看官方文档,作者不建议看本文档进行学习,本文档适用于复习,本文为官方文档的精简版并加以改进。
rust安装: https://www.rust-lang.org/zh-CN/tools/install
一、初识cargo
Cargo 是 Rust 的构建系统和包管理器。
创建Rust 工程目录
cargo new xxx
Cargo 除了创建工程以外还具备构建(build)工程、运行(run)工程等一系列功能,构建和运行分别对应以下命令:
cargo build
cargo run
Cargo 还具有获取包、打包、高级构建等功能。
- cargo clippy: 类似eslint,lint工具检查代码可以优化的地方
- cargo check:检查代码。
- cargo fmt: 类似go fmt,代码格式化
- cargo tree: 查看第三方库的版本和依赖关系
- cargo bench: 运行benchmark(基准测试,性能测试)
- cargo udeps(第三方): 检查项目中未使用的依赖
- cargo build --release:编译时会进行优化,代码会运行的更快,但编译时间更长(正式发布)
二、Rust输出
Rust 输出文字的方式主要有两种:println!() 和 print!()。前者会在输出的最后附加输出一个换行符。当用这两个"函数"输出信息的时候,第一个参数是格式字符串,后面是一串可变参数,对应着格式字符串中的"占位符"
fn main() {
let a = 12;
println!("a is {}", a);
}
//输出a is 12
使用 rustc
编译rs文件
rustc main.rs
执行main
./main
println!(宏)
在 {} 之间可以放一个数字,它将把之后的可变参数当作一个数组来访问,下标从 0 开始。
println!("a is {0}, a again is {0}", a);
格式字符串
fn main() {
println!("{{内容}}");
}
//输出{内容}
案例1:输出
use std::io; //prelude
fn main() {
println!("猜数");
println!("猜一个数");
//定义mut可变
let mut guess = String::new();
//最好加expect来判断
io::stdin().read_line(&mut guess).expect("无法读取行");
println!("你猜的数是{}",guess)
}
案例2:随机数
use rand::Rng; //trait
fn main() {
let secret_number = rand::thread_rng().gen_range(1..101);
println!("神秘数字是{}",secret_number);
}
案例3:比较大小
use std::io; //prelude
use rand::Rng; //trait
use std::cmp::Ordering;
fn main() {
println!("猜数");
let secret_number = rand::thread_rng().gen_range(1..101);
println!("神秘数字是{}",secret_number);
loop {
println!("猜一个数");
let mut guess = String::new();
io::stdin().read_line(&mut guess).expect("无法读取行");
let guess:u32 = match guess.trim().parse(){
Ok(num) => num,
Err(_) => continue,
};
println!("你猜的数是{}",guess);
match guess.cmp(&secret_number){
Ordering::Less => println!("猜的数比神秘数小"),
Ordering::Greater => println!("猜的数比神秘数大"),
Ordering::Equal =>{
println!("你赢了");
break;
},
}
}
}
三、基础语法
1、变量
声明了 a 为无符号 64 位整型变量
如果没有声明类型,a 将自动被判断为有符号 32 位整型变量
let a: u64 = 123;
声明a为可变变量。
let mut a = 123;
2、常量const
不可使用mut,
const MAX_POINTS:u32 = 100_00
3、shadowing隐藏
可以使用相同名字声明新变量,新变量会shadow之前声明的同名变量。
let spaces = " "
let spaces = space.len();
4、注释
// 单行
/*
多行
*/
5、函数
Rust 中的函数定义以 fn
开始,后跟着函数名和一对圆括号。大括号告诉编译器函数体在哪里开始和结束。
风格:下划线命名法
fn main() {
println!("Hello, world!");
another_function();
}
fn another_function() {
println!("Another function.");
}
参数的使用
fn main() {
another_function(5);
}
fn another_function(x: i32) {
println!("The value of x is: {}", x);
}
6、控制流
1、if
表达式
注意:代码中if后面的条件必须是 bool
值。如果条件不是 bool
值,会报错。
fn main() {
let number = 3;
if number < 5 {
println!("condition was true");
} else {
println!("condition was false");
}
}
2、 else if
处理多重条件
fn main() {
let number = 6;
if number % 4 == 0 {
println!("number is divisible by 4");
} else if number % 3 == 0 {
println!("number is divisible by 3");
} else if number % 2 == 0 {
println!("number is divisible by 2");
} else {
println!("number is not divisible by 4, 3, or 2");
}
}
3、循环
Rust 有三种循环:loop
、while
和 for
。
1、loop循环
使用 loop
重复执行代码,相当于python等语言中的while true
fn main() {
loop {
//重复执行此大括号内的代码
//可适当添加if控制结束
println!("again!");
}
}
2、while循环
fn main() {
let mut number = 3;
while number != 0 {
println!("{}!", number);
number -= 1;
}
println!("LIFTOFF!!!");
}
3、for循环
for
循环的安全性和简洁性使得它成为 Rust 中使用最多的循环结构。
fn main() {
let a = [10, 20, 30, 40, 50];
for element in a {
println!("the value is: {}", element);
}
}
四、数据类型
1、标量
1、整数类型
长度 | 有符号类型 | 无符号类型 |
---|---|---|
8 位 | i8 | u8 |
16 位 | i16 | u16 |
32 位 | i32 | u32 |
64 位 | i64 | u64 |
128 位 | i128 | u128 |
arch | isize | usize |
数字字面量 | 示例 |
---|---|
十进制 | 98_222 |
十六进制 | 0xff |
八进制 | 0o77 |
二进制 | 0b1111_0000 |
字节 (仅限于 u8 ) | b'A' |
比方说有一个 u8
,它可以存放从 0 到 255 的值。那么当你将其修改为范围之外的值,比如 256,则会发生整型溢出
要显式处理溢出的可能性,可以使用标准库针对原始数字类型提供的以下一系列方法:
- 使用
wrapping_*
方法在所有模式下进行包裹,例如wrapping_add
- 如果使用
checked_*
方法时发生溢出,则返回None
值 - 使用
overflowing_*
方法返回该值和一个指示是否存在溢出的布尔值 - 使用
saturating_*
方法使值达到最小值或最大值
2、浮点类型
f32
类型是单精度浮点型,f64
为双精度浮点型。
3、数字运算
基本数学运算:加法、减法、乘法、除法和取模运算
// addition
let sum = 5 + 10;
// subtraction
let difference = 95.5 - 4.3;
// multiplication
let product = 4 * 30;
// division
let quotient = 56.7 / 32.2;
let floored = 2 / 3; // Results in 0
// remainder
let remainder = 43 % 5;
4、布尔类型
true和false
let t = true;
let f: bool = false;
5、字符类型
char
(字符)类型是该语言最基本的字母类型,大小为 4 个字节
一些合法值:标音字母,中文/日文/韩文的文字,emoji,还有零宽空格(zero width space)
2、复合类型
1、元组类型
元组中的每个位置都有一个类型,并且元组中不同值的类型不要求是相同的。
fn main() {
let tup: (i32, f64, u8) = (500, 6.4, 1);
}
想从元组中获取个别值,我们可以使用模式匹配来解构(destructuring)元组的一个值
fn main() {
let tup = (500, 6.4, 1);
let (x, y, z) = tup;
println!("The value of y is: {}", y);
}
使用一个句点(.
)连上要访问的值的索引来直接访问元组元素。
fn main() {
let x: (i32, f64, u8) = (500, 6.4, 1);
let five_hundred = x.0;
let six_point_four = x.1;
let one = x.2;
}
没有任何值的元组 ()
是一种特殊的类型,该类型被称为单元类型(unit type),只有一个值,该值被称为单元值(unit value)
2、数组类型
数组的每个元素必须具有相同的类型。Rust 中的数组具有固定长度。
fn main() {
let a = [1, 2, 3, 4, 5];
}
明确元素数量不需要改变时,数组会特别有用。但它们不像 vector (Rust 中动态数组)类型那么灵活。vector 类型类似于标准库中提供的集合类型,其大小允许增长或缩小。
let a: [i32; 5] = [1, 2, 3, 4, 5];
这里,i32
是每个元素的类型。分号之后,数字 5
表明该数组包含 5 个元素。
let a = [3; 5];
变量名为 a
的数组将包含 5
个元素,这些元素的值初始化为 3
。这种写法与 let a = [3, 3, 3, 3, 3];
效果相同。
访问数组元素
fn main() {
let a = [1, 2, 3, 4, 5];
let first = a[0];
let second = a[1];
}
五、所有权
1、stack与heap栈与堆
栈以放入值的顺序存储值并以相反顺序取出值。这也被称作 后进先出(last in, first out)
所有权解决问题:
- 跟踪代码的哪些部分正在使用heap的哪些数据
- 最小化Heap上的重复数据量
- 清理heap上未使用的数据以避免空间不足
2、克隆
需要复制String中堆上的数据需要使用clone方法
let s1 = String::from("hello");
let s2 = s1.clone();
println!("s1 = {}, s2 = {}", s1, s2);
而在栈上的数据不需要使用clone,
Rust 有一个叫做 Copy
trait 的特殊标注,可以用在类似整型这样的存储在栈上的类型上。
如果一个类型实现了 Copy
trait,那么一个旧的变量在将其赋值给其他变量后仍然可用。
let x = 5;
let y = x;
println!("x = {}, y = {}", x, y);
作为一个通用的规则,任何一组简单标量值的组合都可以实现 Copy
,任何不需要分配内存或某种形式资源的类型都可以实现 Copy
。如下是一些 Copy
的类型:
- 所有整数类型,比如
u32
。 - 布尔类型,
bool
,它的值是true
和false
。 - 所有浮点数类型,比如
f64
。 - 字符类型,
char
。 - 元组,当且仅当其包含的类型也都实现
Copy
的时候。比如,(i32, i32)
实现了Copy
,但(i32, String)
就没有。
3、所有权规则
首先,让我们看一下所有权的规则。当我们通过举例说明时,请谨记这些规则:
- Rust 中的每一个值都有一个被称为其 所有者(owner)的变量。
- 值在任一时刻有且只有一个所有者。
- 当所有者(变量)离开作用域,这个值将被丢弃。
返回值与作用域
函数在返回值的过程中也会发生所有权的转移
当一个包含heap数据的变量离开作用域,它的值就会被drop函数清理,除非数据所有权移动到另一个变量上。
4、引用和借用
fn main(){
let s1 = String::from("hello");
let len = calculate_length(&s1);
println!("The length of '{}' is {}.",s1,len);
}
fn calculate_length(s:&String) -> usize {
s.len()
}
数据竞争
- 两个或更多指针同时访问同一数据。
- 至少有一个指针被用来写入数据。
- 没有同步数据访问的机制。
引用规则
- 在任意给定时间,要么 只能有一个可变引用,要么 只能有多个不可变引用。
- 引用必须总是有效的。
5、切片
let s = String::from("hello world");
let hello = &s[0..5];
let world = &s[6..11];
参数s类型需改为字符串
let my_string = String::from("hello world");
// `first_word` 接受 `String` 的切片,无论是部分还是全部
let word = first_word(&my_string[0..6]);
let word = first_word(&my_string[..]);
// `first_word` 也接受 `String` 的引用,
// 这等同于 `String` 的全部切片
let word = first_word(&my_string);
let my_string_literal = "hello world";
// `first_word` 接受字符串字面量的切片,无论是部分还是全部
let word = first_word(&my_string_literal[0..6]);
let word = first_word(&my_string_literal[..]);
// 因为字符串字面值**就是**字符串 slice,
// 这样写也可以,即不使用 slice 语法!
let word = first_word(my_string_literal);
六、Struct结构体
1、普通结构体
struct User {
active: bool,
username: String,
email: String,
sign_in_count: u64,
}
使用user1中的一个值创建一个新的User实例
struct User {
active: bool,
username: String,
email: String,
sign_in_count: u64,
}
fn main() {
// --snip--
let user1 = User {
email: String::from("someone@example.com"),
username: String::from("someusername123"),
active: true,
sign_in_count: 1,
};
let user2 = User {
email: String::from("another@example.com"),
..user1
};
}
2、元组结构体
struct Color(i32, i32, i32);
struct Point(i32, i32, i32);
fn main() {
let black = Color(0, 0, 0);
let origin = Point(0, 0, 0);
}
3、struct方法
// fn main() {
// let w = 50;
// let h = 50;
// println!("{}",area(w, h));
// }
// fn area(width:u32,length:u32) -> u32 {
// width*length
// }
//改进
// fn main() {
// let rect = (30,50);
// println!("{}",area(rect));
// }
// fn area(dim:(u32,u32))->u32 {
// dim.0*dim.1
// }
//最终改进
#[derive(Debug)]
struct Rectangle {
width:u32,
length:u32,
}
fn main(){
let rect = Rectangle{
width:30,
length:50,
};
println!("{}",area(&rect));
println!("{:#?}",rect)
}
//计算长方形面积,借用Rectangle的实例
fn area(rect: &Rectangle) -> u32 {
rect.width * rect.length
}
七、枚举和模式匹配
枚举是一个很多语言都有的功能,不过不同语言中其功能各不相同。Rust 的枚举与 F#、OCaml 和 Haskell 这样的函数式编程语言中的 代数数据类型(algebraic data types)最为相似。
1、定义枚举
#![allow(unused)]
fn main() {
enum IpAddrKind {
V4,
V6,
}
}
//可以像这样创建 IpAddrKind 两个不同成员的实例:
#![allow(unused)]
fn main() {
enum IpAddrKind {
V4,
V6,
}
let four = IpAddrKind::V4;
let six = IpAddrKind::V6;
}
枚举与结构体结合例子:
#![allow(unused)]
fn main() {
enum IpAddr {
V4(String),
V6(String),
}
let home = IpAddr::V4(String::from("127.0.0.1"));
let loopback = IpAddr::V6(String::from("::1"));
}
2、 option枚举
#![allow(unused)]
fn main() {
enum Option<T> {
Some(T),
None,
}
}
Option<T>
枚举是如此有用以至于它甚至被包含在了 prelude 之中,你不需要将其显式引入作用域。另外,它的成员也是如此,可以不需要 Option::
前缀来直接使用 Some
和 None
。即便如此 Option<T>
也仍是常规的枚举,Some(T)
和 None
仍是 Option<T>
的成员。
3、match控制流运算符
接下来是 match
的分支。一个分支有两个部分:一个模式和一些代码。
enum Coin {
Penny,
Nickel,
Dime,
Quarter,
}
fn value_in_cents(coin: Coin) -> u8 {
match coin {
//Coin::Penny是模式,1是代码,=>将其分开
Coin::Penny => 1,
Coin::Nickel => 5,
Coin::Dime => 10,
Coin::Quarter => 25,
}
}
编写一个函数,如果其中含有一个值,将其加一。如果其中没有值,函数应该返回 None 值,而不尝试执行任何操作
fn main() {
fn plus_one(x: Option<i32>) -> Option<i32> {
match x {
None => None,
Some(i) => Some(i + 1),
}
}
let five = Some(5);
let six = plus_one(five);
let none = plus_one(None);
}
将 match
与枚举相结合在很多场景中都是有用的。
Rust 代码中很多这样的模式:match
一个枚举,绑定其中的值到一个变量,接着根据其值执行代码。
Rust 中的匹配是穷举式的(exhaustive):必须穷举到最后的可能性来使代码有效。
案例:
fn main() {
let dice_roll = 3;
match dice_roll {
3 => add_fancy_hat(),
7 => remove_fancy_hat(),
other => move_player(other),
}
fn add_fancy_hat() {
println!("你戴上了帽子,并没有走动")
}
fn remove_fancy_hat() {
println!("你摘下了帽子,并没有走动")
}
fn move_player(num_spaces: u8) {
println!("你走了{}步",num_spaces);
}
}
改变游戏规则:当你掷出的值不是 3 或 7 的时候,你必须再次掷出。
使用 _
来替代变量 other
fn main() {
let dice_roll = 9;
match dice_roll {
3 => add_fancy_hat(),
7 => remove_fancy_hat(),
_ => reroll(),
}
fn add_fancy_hat() {
println!("你戴上了帽子,并没有走动")
}
fn remove_fancy_hat() {
println!("你摘下了帽子,并没有走动")
}
fn reroll() {
println!("重新")
}
}
改变游戏规则:如果你掷出 3 或 7 以外的值,你的回合将无事发生
fn main() {
let dice_roll = 9;
match dice_roll {
3 => add_fancy_hat(),
7 => remove_fancy_hat(),
_ => (),
}
fn add_fancy_hat() {
println!("你戴上了帽子,并没有走动")
}
fn remove_fancy_hat() {
println!("你摘下了帽子,并没有走动")
}
}
4、if let简单控制流语句
处理只匹配一个模式的值而忽略其他模式的情况使用if let
if let
获取通过等号分隔的一个模式和一个表达式。它的工作方式与 match
相同,这里的表达式对应 match
而模式则对应第一个分支。
#![allow(unused)]
fn main() {
let some_u8_value = Some(0u8);
if let Some(3) = some_u8_value {
println!("three");
}
}
可以在 if let
中包含一个 else
。else
块中的代码与 match
表达式中的 _
分支块中的代码相同
let mut count = 0;
match coin {
Coin::Quarter(state) => println!("State quarter from {:?}!", state),
_ => count += 1,
}
//等同于
let mut count = 0;
if let Coin::Quarter(state) = coin {
println!("State quarter from {:?}!", state);
} else {
count += 1;
}