声明:
- 本blog偏向学习/速查手册,需要阅读者会使用Linux,并具备编程基础(最好C++)
- 本blog不会事无巨细介绍方方面面,而是专注于快速掌握rust,并理解rust的语言特性
- 本blog不追求教科书般的严谨表达,而是专注于更加容易理解与记忆的表达
- 作者只是rust的初学者,内容中如有错误,欢迎大家批评指正
准备环境
# rust快速安装与执行
curl --proto '=https' --tlsv1.2 https://sh.rustup.rs -sSf | sh
cargo new hello_cargo
cd hello_cargo
cargo run
基础语法
赋值
let a = 12; // rust会自动推导类型,一旦类型确定,就不可再变更
let i:i32 = 12; // (不可变变量)赋值语句
let mut m:i32 = 12; // (可变变量)赋值语句
数据类型
基本类型
// 整型(默认i32)
let i:i8 = -1; // 有符号类型,从i8到i128
let u:u8 = 0; // 无符号类型,从u8到u128
let s:isize; // 按CPU位长确定类型(32 or 64),分为有符号isize和无符号usize
// 浮点型(默认f64)
let f:f32 = 1.0; // 只有f32和f64两种
// 布尔型(全小写)
let b:bool = false; // true or false
// 字符型(单引号)
let c:char = 'a'; // 固定4字节Unicode编码,可通过std::mem::size_of::<char>()验证
类型转换
// rust禁止隐式类型转换
let i:i32 = 2;
let f:f32 = 3.0;
let r = i * f; // 此处会报错
// 通过as进行显式类型转换
let i:i32 = 2;
let f:f32 = 3.0;
let x:f32 = i as f32;
let r:f32 = f * x;
复合类型
// 元组:不可变、多类型
// 在语法层面,“tup”和“(x, y)”是可以等价替换的
let tup = (32, 6.4); // 自动推导类型
let tup:(i32, f64) = (32, 6.4); // 严格定义类型
let (x, y) = tup;
let (x, y):(i32, f64) = (1, 2.0);
let (x, y) = (y, x);
let first = tup.0;
let second = tup.1;
// 数组:可变,单类型
let arr = [1,2,3,4,5]; // 自动推导类型
let arr:[i8;5] = [1,2,3,4,5]; // 严格定义类型和长度
let arr = [3;5]; // 批量初始化
let first = arr[0];
let second = arr[1];
let mut arr = [1,2,3]; // 数组默认不可变,需要使用mut定义,才是“传统意义“上的数组
arr[0] = 4;
// 切片(slice):部分引用,无所有权
let arr = [0,1,2,3,4,5,6,7,8,9];
let f5 = &arr[0..5]; // 切片必须也只能引用
切片引用
// 读引用
let a = [0,1,2,3,4,5,6,7,8,9];
let b = &a[0..5];
println!("{:?}", b);
// 写引用
let mut a = [0,1,2,3,4,5,6,7,8,9];
let b = &mut a[0..5];
b[0] = 10;
println!("{:?}", b);
控制流
分支
// 标准的分支判断用法
let mut a = 0;
if a == 0 { // 判断条件可以不加括号
a = 0;
} else if (a > 0) { // 也可以加括号,但有warning
a = 1;
} else {
a = -1;
}
// 单行分支判断模拟三元运算符
let x = if a==0 {0} else {1}; // 大括号中不写分号,解释详见“语言特性”-“表达式和语句”部分
循环
常规语法
// for循环
for i in (1..10).rev() { // exp..exp是范围表达式,类型为std::ops::Range,详见官方Reference
println!("for loop: {}", i);
}
for e in [1,3,5,7,9] {
println!("for loop: {}", e);
}
// while循环
let mut c = 0;
while c < 10 {
c += 1;
if c % 2 == 0 {
println!("while loop continue: {}", c);
continue;
}
println!("while loop: {}", c);
}
// loop循环(死循环)
let mut c = 0;
loop {
if c > 10 {
break;
}
println!("infinite loop");
c += 1;
}
特性用法
break返回循环结果
// break返回循环结果(类似return语句)
let mut c = 0;
let r = loop {
if c > 10 {
break c;
}
c += 1;
}; // 这里必须加分号,解释详见“语言特性”-“表达式和语句”部分
println!("result: {}", r) // r输出为11
根据label跳出循环
// 按label退出循环(类似goto语句)
let mut c = 0;
'outer: loop { // 指定外层循环label
'inner: loop { // 指定内存循环label
if c/10 != c%10 {
break; // 默认情况下,break跳出内层循环
}
if c == 99 {
break 'outer; // 指定label后,break跳出外层循环
}
c += 1;
println!("inner loop: {c}");
}
c += 10;
println!("outer loop: {c}");
}
println!("result: {c}");
match
let c = 0;
let r = match c { // match也是一种表达式,也有返回值
0 => "0", // 任意表达式都可以作为match的取值
_ => "_", // 通配匹配。match要求,匹配必须覆盖全集
};
println!("{:?}", &r);
// if let 就是 match 的语法糖,上下两套代码,完全等价
let c = 1;
let r = if let 0 = c {
"0"
} else { // 如果没有else,默认为空,视同于match中的“_ => ()”
"_" // 对应match中通配符“_”的部分
};
println!("{:?}", &r);
// match与Option
let c = Some(0);
let r:Option<i32> = match c {
Some(i) => Some(i+1), // Some(T)是Option的一个泛型枚举值
None => None,
};
println!("{:?}", &r);
// match与Result
let c = Ok(0);
let r:Result<i32, String> = match c {
Ok(i) => Ok(i+1),
Err(e) => Err(e),
};
println!("{:?}", &r);
函数
fn add(x:i32, y:i32) -> i32 { // 明确定义函数的参数类型和返回值类型
x + y // 用结尾表达式描述函数的返回值
}
fn print_type_of<T>(_:&T) { // 泛型用法,暂不深究
println!("{}", std::any::type_name::<T>())
}
fn main() { // 空参数和空返回值,等同于:fn main() -> () {...}
let r = add(1, 2);
println!("result: {}", r);
print_type_of(&r);
}
结构体
普通结构体
#[derive(Debug)] // 该语句使println可以打印结构体
struct User {
username: String,
email: String,
}
fn main() {
// 用法1
let username = String::from("someone");
let user1 = User {
email:username.clone()+"@example.com", // struct的成员变量,不接受引用
username, // 语法糖:同名变量可以简写
};
println!("{:?}", user1);
// 用法2
let user2 = User {
username:user1.username, // 成员变量通过“点”运算符来取值
email:user1.email, // 成员变量的赋值,会导致所有权转移
};
println!("{:?}", user2);
// 用法3
let user3 = User {
username:String::from("another"),
..user2 // 省略补充用法:将未赋值的成员变量,使用该对象依次填充
};
println!("{:?}", user3);
}
元组结构体
#[derive(Debug)] // 该语句使println可以打印结构体
struct Point(i32, i32, i32);
struct Empty; // 空结构体
fn main() {
let p = Point(1, 2, 3); // 定义一个元组结构的变量
println!("{:?}", p);
let mut p = Point(1, 2, 3);
p.0 = 4; // 元组结构体,只能用下标方式访问
println!("{:?}", p);
let e = Empty; // 空结构体一般用于只有成员函数,没有成员变量的场景
}
定义类/对象方法
struct Rectangle {
width: u32,
height: u32,
}
// rust没有构造函数
impl Rectangle {
fn set(&mut self, width:u32, height:u32) { // &mut self参数表示可写成员函数
self.width = width;
self.height = height;
}
fn area(&self) -> u32 { // &self参数,表示只读成员函数
self.width * self.height
}
fn square(size: u32) -> Rectangle { // 无self,表示关联函数(也就是静态函数或者类函数)
Rectangle {width: size, height: size}
}
}
// rust可以自定义析构函数
impl Drop for Rectangle {
fn drop(&mut self) {
println!("drop Rectangle");
}
}
fn main() {
let mut rect = Rectangle{width:10, height:20};
rect.set(20, 20);
println!("{:?}", rect.area());
println!("{:?}", Rectangle::square(10).area());
}
枚举
#[derive(Debug)]
enum IpAddrKind {V4, V6} // 定义枚举
fn main() {
let four = IpAddrKind::V4; // 创建枚举实例
let six = IpAddrKind::V6;
dbg!(&six); // dbg!会获取并返回参数的所有权,所以这里需要引用
}
接口与泛型
接口
此处没有实现泛型接口,更多内容详见:
- 【官方文档】:Traits: Defining Shared Behavior - The Rust Programming Language
- 【中文翻译】:trait:定义共享的行为 - Rust 程序设计语言 中文版
// 定义一个接口
trait Summary {
// 没有默认实现的方法
fn summarize_author(&self) -> String;
// 有默认实现的方法
fn summarize(&self) -> String {
format!("(Summarize: {}...)", self.summarize_author())
}
}
trait Display {
fn display(&self) -> String;
}
// 定义一个“类”
struct Tweet {
username: String,
content: String,
}
// 定义“类”的“接口”实现
impl Summary/*接口*/ for Tweet/*类*/ {
fn summarize_author(&self) -> String {
format!("@{}({})", self.username, self.content.len())
}
}
impl Display for Tweet {
fn display(&self) -> String {
format!("Display: @{}({})", self.username, self.content.len())
}
}
// 以接口作为参数,从而实现多态
fn notify(item: &impl Summary) {
println!("Breaking news! {}", item.summarize());
}
// 实现多个接口
fn multi_notify(item: &(impl Summary + Display)) { // 由于优先级问题,所以这里多接口实现类的引用,需要加括号
println!("Breaking news! {}", item.summarize());
println!("Breaking news! {}", item.display());
}
fn main() {
let t = Tweet {
username: String::from("horse_ebooks"),
content: String::from("of course, as you probably already know, people"),
};
notify(&t);
multi_notify(&t);
}
泛型
闭包与迭代器
闭包
闭包有三种类型:
- FnOnce:捕获(非传参)变量的所有权转移,通常配合move关键字进行定义
- FnMut:以“可变引用”的方式捕获(非传参)变量
- Fn:以“不可变引用”的方式捕获(非传参)变量
// Fn(只读引用)类型闭包
fn closure_read(c:impl Fn() -> usize) -> usize {
c()
}
// FnMut(可写引用)类型闭包
fn closure_mut(mut c:impl FnMut()) {
c();
}
// FnOnce(所有权转移)类型闭包
fn closure_once(c:impl FnOnce()->String) -> impl FnOnce()->String {
let mut s = c();
move || -> String {
s.push_str("_fn");
s
}
}
fn main() {
// FnOnce(所有权转移)类型闭包
let mut s = String::from("hello");
let c = move || -> String {
s.push_str("_main");
s
};
let c = closure_once(c);
let s = c();
println!("{}", s);
// FnMut(可写引用)类型闭包
let mut s = String::from("hello");
let c = || {
s.push_str("_mut");
};
closure_mut(c);
println!("{}", s);
// Fn(只读引用)类型闭包
let c = || {
s.len()
};
let r = closure_read(c);
println!("{}", r);
}
迭代器
fn main() {
// 只读引用迭代器
let v = vec![1,2,3];
let v_iter = v.iter();
for v in v_iter {
println!("iter: {}", v);
}
// 读写引用迭代器
let mut v = vec![1,2,3];
let v_iter = v.iter_mut();
for v in v_iter {
*v += 1;
}
println!("{:?}", v);
// 所有权迭代器
let v = vec![1,2,3]; // 此处vec并不需要mut,因为后续vec元素的所有权转移,而非原地修改
let mut v2:Vec<i32> = Vec::new();
let v_iter = v.into_iter(); // 此时v的所有权转移,并被包裹在了类型为IntoIter的v_iter变量中
for v in v_iter { // IntoIter类型的特点为,对集合元素的使用,也会转移元素的所有权
v2.push(v+1); // 此处,每个元素的所有权都被转移到了新的集合v2中
}
println!("{:?}", v2);
// 迭代器的map用法
let v = vec![1,2,3];
let v2:Vec<_> = v.iter().map(|x| x + 2).collect(); // 此处v.iter是只读引用,所以整体视同于复制
println!("iter: {:?}", v);
println!("iter: {:?}", v2);
// 迭代器的filter用法
let v = vec![1,2,3];
let v2:Vec<_> = v.into_iter().filter(|x| x % 2 > 0).collect(); // 此处v.into_iter转移了所有权,所以整体视同于移动
println!("iter: {:?}", v2);
}
智能指针
fn main() {
// Box智能指针(拥有所有权)
let mut a = Box::new(5);
*a += 1;
println!("{}", a);
// Rc智能指针(只读引用)
use std::rc::Rc;
let a = Rc::new(5); // Rc是不可变引用
let a = Rc::clone(&a);
println!("{}", Rc::strong_count(&a)); // 打印指针的引用计数
//
// RcCell智能指针(读写引用)
use std::cell::RefCell;
let a = RefCell::new(5); // RefCell不在编译期检查是否可变
println!("{:?}", a.borrow()); // RefCell的只读borrow用法
let mut w = a.borrow_mut(); // RefCell的读写borrow用法
*w += 1;
println!("{:?}", a); // 由于w始终没有销毁,所以a的值,无法读取,只记录borrow状态
std::mem::drop(w); // 强制回收
println!("{:?}", a); // 在没有任何其他borrow时,a才能被正常使用
}
持有者数量的区别:
Rc<T>
允许相同数据有多个所有者Box<T>
和RefCell<T>
单一所有者
编译期检查的区别:
Box<T>
允许在编译时执行不可变或可变借用检查Rc<T>
仅允许在编译时执行不可变借用检查RefCell<T>
允许在运行时执行不可变或可变借用检查。
- 可读可写的区别:
- Box<T>是可写的
- Rc<T>是只读的
RefCell<T>
可以在自身是不可变的情况下修改其内部的值
是否可以用于多线程:
Box<T>允许
Rc<T>
,RefCell<T>
只能用于单线程场景
无畏并发
线程与channel
use std::thread;
use std::sync::mpsc;
use std::time::Duration;
fn main() {
let v = vec![0,1,2,3,4,5,6,7,8,9];
let (tx, rx) = mpsc::channel(); // 如果对tx、rx进行clone,即可进行“多写”或“多读”模式
let handle = thread::spawn(move || { // 传递给线程的闭包,必须通过move进行所有权转移,不能引用
for i in 0..10 {
println!("hi number {}[{}] from the spawned thread!", i, v[i]);
thread::sleep(Duration::from_millis(10));
}
tx.send(v).unwrap(); // send会导致所有权转移
});
for i in 1..5 {
println!("hi number {} from the main thread!", i);
thread::sleep(Duration::from_millis(10));
}
handle.join().unwrap();
let received = rx.recv().unwrap(); // 还有try_recv的非阻塞方法
println!("receive: {:?}", received);
}
线程与Mutex
Arc与Mutex的关系很像Rc与RefCell的关系,都是使用不可变的指针获取其内部的可变引用
use std::thread;
use std::sync::{Mutex, Arc};
fn main() {
let counter = Arc::new(Mutex::new(0)); // Arc类似Rc,简单理解为线程安全的Rc
let mut handles = vec![];
for _ in 0..10 {
let counter = Arc::clone(&counter); // 将内部变量counter复制10次,分别move给10个线程
let handle = thread::spawn(move || {
let mut num = counter.lock().unwrap(); // counter自身指向不可变,但指向的内容可变
*num += 1;
});
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
println!("Result: {}", *counter.lock().unwrap());
}
代码管理
内部模块
多文件模型
src/ # 模块名就是文件名(不包含扩展名rs)
├── main.rs # 主函数文件,文件名必须为main.rs
├── modbardir # 一级模块(目录)
│ ├── mod.rs # 一级模块(mod.rs为特殊文件,用于定义其所在的目录模块的内容)
│ ├── modbar.rs # 二级模块(文件)
│ └── modbar2dir # 二级模块(目录)
│ ├── mod.rs # 二级模块(mod.rs为特殊文件,用于定义其所在的目录模块的内容)
│ └── modbar2.rs # 三级模块(文件)
└── modfoo.rs # 零级模块(与主函数同级)
main.rs
// 模块的引入,都是以当前文件的地址作为起始地址
mod modfoo; // mod关键字用于引入模块(文件)
mod modbardir; // mod关键字用于引入模块(目录)
use crate::modfoo::foo; // 绝对引用
use modbardir::modbar::bar; // 相对引用
use modbardir::modbar::bar as barfn; // 相对引用(通过as重命名)
use modbardir::ref_bar; // 相对引用(super间接引用)
use modbardir::modbar2::bar2; // 相对引用(pub use间接引用)
fn main() {
crate::modfoo::foo(); // 绝对引用(crate为关键字)
modfoo::foo(); // 相对引用(以当前路径为基础)
foo(); // 相对引用(通过use声明)
crate::modbardir::modbar::bar(); // 绝对引用
modbardir::modbar::bar(); // 相对引用(以当前路径为基础)
bar(); // 相对引用(通过use声明)
barfn(); // 相对引用(通过as重命名)
ref_bar(); // 相对引用(super间接引用)
crate::modbardir::modbar2dir::modbar2::bar2(); // 绝对引用
modbardir::modbar2dir::modbar2::bar2(); // 相对引用(以当前路径为基础)
modbardir::modbar2::bar2(); // 相对引用(pub use间接引用)
bar2(); // 相对引用(pub use间接引用)
}
modfoo.rs
// 任何元素(不限于函数)如果想被外部使用,都必须通过pub声明
pub fn foo() {
println!("mod: foo");
}
// 所有未声明为pub的都是私有的,私有的都无法访问
fn _private_foo() {
println!("mod: private_foo");
}
modbardir/mod.rs
// mod.rs文件用于定义目录模块的内容
pub mod modbar; // 只有通过pub声明的子模块,才能被外部引用
pub mod modbar2dir;
pub use modbar2dir::modbar2; // 通过pub user声明的多级子模块,可作为直接子模块使用
// super关键字,用于指定父模块
pub fn ref_bar() {
use super::modbardir::modbar::bar;
bar();
}
modbardir/modbar.rs
pub fn bar() {
println!("mod: bar");
}
modbardir/modbar2dir/mod.rs
pub mod modbar2;
modbardir/modbar2dir/modbar2.rs
pub fn bar2() {
println!("mod: bar2")
}
单文件模型
// 单文件模型与多文件模型,表达的是相同的模块组织形式
// 通过单文件形式,可以更容易了解模块组织形式
mod modfoo {
pub fn foo() {
println!("mod: foo");
}
fn _private_foo() {
println!("mod: private_foo");
}
}
mod modbardir {
pub use modbar2dir::modbar2;
pub fn ref_bar() {
use super::modbardir::modbar::bar;
bar();
}
pub mod modbar {
pub fn bar() {
println!("mod: bar");
}
}
pub mod modbar2dir {
pub mod modbar2 {
pub fn bar2() {
println!("mod: bar2")
}
}
}
}
use crate::modfoo::foo; // 绝对引用
use modbardir::modbar::bar; // 相对引用
use modbardir::modbar::bar as barfn; // 相对引用(通过as重命名)
use modbardir::ref_bar; // 相对引用(super间接引用)
use modbardir::modbar2::bar2; // 相对引用(pub use间接引用)
fn main() {
crate::modfoo::foo(); // 绝对引用(crate为关键字)
modfoo::foo(); // 相对引用(以当前路径为基础)
foo(); // 相对引用(通过use声明)
crate::modbardir::modbar::bar(); // 绝对引用
modbardir::modbar::bar(); // 相对引用(以当前路径为基础)
bar(); // 相对引用(通过use声明)
barfn(); // 相对引用(通过as重命名)
ref_bar(); // 相对引用(super间接引用)
crate::modbardir::modbar2dir::modbar2::bar2(); // 绝对引用
modbardir::modbar2dir::modbar2::bar2(); // 相对引用(以当前路径为基础)
modbardir::modbar2::bar2(); // 相对引用(pub use间接引用)
bar2(); // 相对引用(pub use间接引用)
}
外部依赖
前置操作
# 在你的项目文件夹(此例中,为hello文件夹)所在的目录,执行一下命令,以创建一个本地库
cargo new --lib diy_lib
cd diy_lib
cargo build
依赖引入(Cargo.toml文件)
[package]
name = "hello"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
clap = "2.27.1" # 来自 crates.io
rand = { git = "https://github.com/rust-lang-nursery/rand" } # 来自网上的仓库
diy_lib = { path = "../diy_lib" } # 来自本地文件系统的路径
// 此处只展示本地外部依赖的用法,其他类型的外部依赖,用法相同
extern crate diy_lib; 通过extern crate来指定外部依赖,此时diy_lib可以作为顶级mod来使用
fn main() {
let r = diy_lib::add(1, 1);
println!("{r}")
}
语言特性
不可变变量的本质
不可变变量在栈上的本质就是“两个变量”,当第二个a生效时,第一个a无法再被访问到而已。
// 打印变量地址后发现,两个a并不在同一个地址空间
let a = 1;
println!("{:p}", &a);
let a = 2;
println!("{:p}", &a);
表达式和语句
基本概念
- 带分号的是语句,不带分号的是表达式
- 语句没有返回值,表达式有返回值
- 代码块也是表达式
- 代码块、函数体、分支语句、循环语句,都可以看作是抽象的“代码块”
- 代码块具有默认返回值——空值:即一对小括号
// 任何不以分号结尾的,都是表达式,任何表达式代表某个“值”
let a = 0; // 任何能表示一个值的代码,都是表达式
let a = {0}; // 任何表达式,都可以用大括号括起来
let a = {}; // 代码块也是表达式,默认返回空值
let a = {0;}; // "0;"是语句,当代码块没有结尾表达式时,默认返回空值
// 任何一个代码块,最多只能有1个表达式,来代表这个代码块所表达的“值”
let a = {
let i = 0; // “let i = 0;”是一条语句,它并不影响该代码块所表达的值
1 // “1”是表达式,即为该代码块所表达的值
}; // let a = {...};为一条完整的语句,所以大括号的后面要加分号
// if语句也是表达式,也具有返回值,这就是if实现三目运算符的原理
let x = if a==0 {0} else {1};
break & return
这两个关键字的特殊之处在于:分别用于解决“循环表达式”和“函数表达式”的返回值问题
- break关键字用于循环语句,是为循环语句定义返回值的唯一方法
- return关键字用于函数,是让函数提前返回的唯一方法,一般情况下,结尾表达式可以取代return语句
所有权
最好能有的预备知识:
- C++的指针与引用
- C++的const指针与指向const的指针
- C++的析构函数
- C++的unique_ptr智能指针
堆栈与作用域
- 堆:通过引用计数来决定内存的释放
- 栈:通过函数退出来决定内存的释放
- 作用域:作用域是“大括号”决定的,“大括号”的范围即是变量的生命周期
赋值与引用
rust和其他语言最大的不同就是所有的“赋值”过程,默认情况下,都被看作是“移动”而非“复制”。
// 栈上复制
let a = 0; // 定义变量
let b = a; // 复制变量,当移动看待
let c = &b; // 引用变量
println!("{}, {}, {}", a, b, c); // 由于b是a的复制,所以可以正常输出
// 堆上移动
let s1 = String::from("hello"); // 定义对象
let s2 = s1; // 移动对象
let s3 = &s2; // 引用对象
println!("{}, {}", s2, s3); // 此处无法打印s1
有些地方会区分基础数据类型是复制,对象类型是引用,但作者认为,既然我们已经选择了rust,那么思想上遵守更严格的统一“约定”,会更加符合rust的思维。
移动的本质
println!("{}", std::mem::size_of::<String>()); // 无论字符串是什么,String都只包含24Byte的meta信息
let s1 = String::from("hello"); // 栈上的s1只记录meta信息,真正的字符串内容记录在堆上
let s2 = s1; // s2先将s1的meta信息复制过来,然后再把s1的meta信息清空,从而完成移动过程
let s3 = s2.clone(); // s3只移动了s2的拷贝,所以s2本身依然可以使用
println!("{}, {}", s2, s3); // 此处无法打印s1
传参导致的移动
参数移动,是学习rust过程中,非常“反直觉”的一种现象,需要特别注意!
fn foo(s:String) -> String {
println!("{}", s);
return s; // 如果不return,那么s会在函数结束后销毁
}
fn main() {
let s = String::from("hello");
let s = foo(s); // s在传参的时候,发生了移动,如果不重新赋值,那么s将会失效
println!("{}", s);
}
引用的本质
“引用”和“借用”,是一个意思,这只是一种人为规定,需要了解这个规定,是因为编译器报错会使用“borrow”这个词
// 理解此代码需要读者熟练掌握C语言指针的概念
// 如果不理解指针,可忽略这段代码,直接看结论
let a = 0; // 定义一个整型a
let b = &a; // b保存a的地址
let c = &&a; // c保存a地址的地址(此处隐含了一个匿名变量&a)
println!("a的值:\t\t{:?}", &a); // 正常输出a的值0
println!("a的地址:\t{:p}", &a); // 输出变量a的地址
println!("b的真值:\t{:p}", b); // 通过指定类型,打印b真正的值,即a的地址
println!("b的值:\t{:?}", b); // 虽然b是a的引用(就是指针),但rust会自动解引用,打印a的值
println!("*b的真值:\t{:?}", *b); // 对b显式解引用,通常用于基础数据类型的修改
println!("b的地址:\t{:p}", &b); // 打印b的地址,以此确认,b就是一个指针,有自己的地址空间
println!("c的真值:\t{:p}", c); // 通过指定类型,打印c1真正的值,即a的引用(就是指针)的地址
println!("c的值:\t\t{:?}", c); // 无论多少级引用,rust都会一直解引用,一直到真正的值为止
println!("c的地址:\t{:p}", &c); // 打印c的地址,进一步证明,多级引用,会产生多个地址空间
// 通过使用裸指针,理解引用的本质
let rp = &a as *const i32; // 通过裸指针的形式,获取a的地址,可以发现和b的真值相同,进一步确认,引用的本质就是指针
println!("rp的真值:\t{:p}", rp); // 以指针形式,打印rp的真值,作为参照
println!("rp的值:\t\t{:?}", rp); // 裸指针不会自动“解引用”,所以并不会打印a的值,依旧打印a的地址
let srp = unsafe{*rp}; // 裸指针无法直接取值,必须通过unsafe的表达式才行
println!("srp的值:\t{:?}", srp); // 裸指针只有通过显示取值,才能得到真正a的值
// 语法糖:自动解引用
let mut s = String::from("hello");
let ms = &mut &mut s; // 这里了两个&mut,主要为了验证多级解引用效果
ms.push_str(" world"); // 下标访问、成员方法访问,无需显式解引用,rust会自动解引用
println!("s的值:\t{:?}", s);
结论:
- 引用就是指针,只不过rust为这种指针赋予了特殊能力:自动解引用 & 安全性检查
- 基础类型不会自动解引用,必须手动解引用才能修改值,否则会被认为是修改引用目标
- 下标访问、成员方法访问,无需显式解引用,rust会自动解引用
- 多级引用就是多级指针,rust会自动解多级引用,直到所有引用全部解完为止
理解可变引用
如果你理解C++中的“const指针”与“指向const的指针”的区别,那么你已经学会了可变引用
// 基础变量的可变引用
let a = 0; // a是不可变变量
let mut ma = 0; // mut_a是可变变量
let b = &a; // b不可再引用新的对象,且引用的a不许修改
let b = &mut ma; // b不可再引用新的对象,但引用的a可以修改
*b = 1; // 对基础变量的修改,必须显式解引用
let mut b = &a; // b可以重新指定引用对象,但引用的a不许修改
let mut b = &mut ma; // b可以重新指定引用对象,且引用的a可以修改
let mut mc = 0;
b = &mut mc; // 直接赋值表示修改b引用的对象,而不是修改引用的值
引用的读写规范
官方文档使用的名词为:“引用”和“可变引用”,而作者自己更喜欢叫“读引用”和“写引用”,此小节会使用作者喜欢的叫法进行总结。
// 读引用
let mut s = String::from("hello");
let r1 = &s;
let r2 = &s;
let r3 = &s;
println!("{s}, {r1}, {r2}, {r3}");
// 写引用
let w = &mut s;
w.push_str(" world!");
println!("{s}");
规则:
- 一写多读原则(核心原则)
- 任何一个引用,从定义开始,到使用结束,作为其合法性检查范围
- 在任意检查范围内,可以同时使用多个读引用,或者使用一个写引用
- 变更失效原则
- 原始变量产生变更,变更前定义的所有引用,无论读写,全部失效
- 写引用产生变更,变更前定义的所有相同指向的读引用,全部失效
- 用时判断原则
- 任何引用,如果不被使用,则不会进行检查
- 间接使用,也是使用(如函数参数,宏参数等)
- 互斥替换原则
- 读引用与写引用是互斥的,任意检查范围内,只能存在一种
- 对一个变量的多次写引用,后者会替代前者,前者会失效
高级特征
代码示例
三大常用集合
更多用法与细节,详见std官方手册:std - Rust
String
特别注意:rust的String类型,不支持“下标”与“遍历”两种用法
fn main() {
// 字符串初始化
let _s = String::new();
let _s = "initial contents".to_string();
let _s = String::from("initial contents");
// 字符串追加
let mut s = String::from("foo");
s.push_str("bar");
s.push('x');
println!("{}", s);
// 字符串拼接
let s = String::from("foo") + &String::from("bar"); // 前不引用后引用的原因为,add的定义形式为:fn add(self, s: &str) -> String
println!("{}", s);
let s = format!("{}-{}-{}", "foo", "x", "bar");
println!("{}", s);
// 使用安全但低效的方式,修改字符串
let s = String::from("foo");
let mut v = s.into_bytes();
v[0] = 'z' as u8;
println!("{}", String::from_utf8(v).unwrap());
// 使用高效但unsafe的方式,修改字符串
let mut s = String::from("foo");
unsafe {
let b = s.as_bytes_mut();
b[0] = 'z' as u8;
}
println!("{}", s);
}
Vector
fn main() {
// vector初始化
let _v: Vec<i32> = Vec::new();
let mut v = vec![1, 2, 3];
// vector追加
v.push(4);
// vector修改
v[0] = 5;
println!("{:?}", v);
println!("{:?}, {:?}", v[0], v.get(0)); // 下标返回&T类型,而get()方法返回的是Option<&T>类型
// vector遍历
for e in &mut v {
*e += 1;
}
println!("{:?}", v);
}
HashMap
use std::collections::HashMap;
fn main() {
// 初始化空map,并通过insert插入数据
let mut m = HashMap::new();
m.insert(String::from("a"), 1); // 通过insert插入新值
println!("{:?}", m);
m.insert(String::from("a"), 2); // insert即能插入新值,也能覆盖老值
println!("{:?}", m);
m.entry(String::from("a")).or_insert(3); // 使用entry返回一个枚举值,or_insert只有不存在key时才插入,并且会返回当前插入值的可变引用
println!("{:?}", m);
println!("{:?}", m.get("a")); // 返回Option<T&>类型
// 通过key和value的数据,直接对map进行初始化
let keys = vec![String::from("a"), String::from("b")];
let values = vec![1, 2];
let m: HashMap<_, _> = keys.iter().zip(values.iter()).collect();
println!("{:?}", m);
}
Result语法糖
use std::io;
use std::io::Read;
use std::fs::File;
// 与open_with_match等效
fn open_with_delivery() -> Result<String, io::Error> {
let mut s = String::new();
// “?”是专门为Result类型设计的match语法糖,会将Result原样向上传递
// “?”的另一个好处,是支持链式调用
File::open("src/main.rs")?.read_to_string(&mut s)?;
Ok(s)
}
fn open_with_match() -> Result<String, io::Error> {
let f = File::open("src/main.rs");
let mut f = match f {
Ok(file) => file,
Err(e) => return Err(e),
};
let mut s = String::new();
match f.read_to_string(&mut s) {
Ok(_) => Ok(s),
Err(e) => Err(e),
}
}
fn open_or_panic() -> File {
let _f = File::open("hello.txt").unwrap(); // unwrap返回File或者调用panic!宏
let f = File::open("hello.txt").expect("my diy error message"); // except还能自定义错误信息
f
}
fn main() {
let r = open_with_delivery();
println!("{:?}", r);
let r = open_with_match();
println!("{:?}", r);
let r = open_or_panic();
println!("{:?}", r);
}
参考信息
- 【官方Book】:The Rust Programming Language - The Rust Programming Language
- 【中文Book】:Rust 程序设计语言 - Rust 程序设计语言 中文版
- 【官方Reference】:Introduction - The Rust Reference
- 【中文Reference】:简介 - Rust 参考手册 中文版
- 【官方Example】:Introduction - Rust By Example
- 【中文Example】:简介 - 通过例子学 Rust 中文版
- 【Rust标准库(std)手册】:std - Rust
- 【Rust语言圣经】:关于本书 - Rust语言圣经(Rust Course)
- rust得到变量的类型和占用空间的大小 | 软sim卡
- How do I print in Rust the type of a variable? - Stack Overflow
- 2020-09-26:请问rust中的&和c++中的&有哪些区别? - 知乎
- 理解可变引用的排他性 - Rust入门秘籍
- Rust 入门指南(modules 和工程结构) - 知乎
- Rust中字符串与字节集合的转换 - | Cloud Strife |
- Rust学习笔记-闭包 - 知乎
- Rust中的Iterator和IntoIterator介绍及应用_pilaf1990的博客-CSDN博客