文章目录
1. rust的官网
https://www.rust-lang.org/zh-CN/,这个是rust的官方网站。
2. 安装
安装命令:
curl --proto '=https' --tlsv1.2 https://sh.rustup.rs -sSf | sh
配置环境的命令:
source "$HOME/.cargo/env"
版本更新:
rustup update
3. cargo命令使用方法
cargo new 创建项目。
cargo build 构建项目。
cargo run 一步构建并运行项目。
cargo check 在不生成二进制文件的情况下构建项目来检查错误
4. 整形溢出问题
在debug模式编译时,溢出会panic;如果在release模式下,rust不会panic,不过会回绕到最小值导致意外。建议使用下面的方法:
- 用 wrapping_* 方法进行 wrapping,如 wrapping_add
- 用checked_* 方法出现溢出,则返回 None 值
- 用overflowing_* 方法返回值和一个布尔值,表示是否出现溢出
- 用 saturating_* 方法在值的最小值或最大值处进行饱和处理
5. 所有权的copy和move
Rust语言中的所有权(ownership)是一种内存管理的方式,其基本规则是每个值都有一个所有者(owner),同时每个值在任意时刻只能有一个可变的所有者以及任意数量的不可变的借用者(borrower)。
当一个值被复制给另一个变量时,如果这个值的类型实现了Copy trait,那么它会发生值的复制而不是move。Copy trait告诉编译器这个类型可以通过简单的位拷贝方式进行复制,因此在复制时不会产生所有权的转移。除了实现了Copy trait的类型以外,所有的类型在赋值或传递参数等操作时,都会发生所有权的转移,这个过程称为move。当一个值所有权被转移到其他变量时,原来变量的所有权被清空,这个变量就失效了。
也就是说,有些类型实现了Copy trait,他们在进行赋值和函数传参的时候,会copy一份数据,不会发生所有权的移动。但是,其他数据类型在进行赋值和函数传参的时候,会发生所有权的移动,比如:String。String类型的变量在赋值给其他数据或者作为函数的传入参数后,这个变量就丢失所有权并且不再生效了。
以下类型实现了 Copy trait的,这些类型有个共同点就是数据都是在栈上的:
- 所有整数类型,比如 u32 。
- 布尔类型, bool ,它的值是 true 和 false 。
- 所有浮点数类型,比如 f64 。
- 字符类型, char 。
- 元组,当且仅当其包含的类型也都实现 Copy 的时候。比如, (i32, i32) 实现了 Copy ,但 (i32, String) 就没有
6. 调试变量
#[derive(Debug)]
是 Rust 的一个特性,用于自动实现 Debug trait 的行为。Debug trait 是一个用于打印调试信息的 trait,通常用于开发和调试阶段。Rust 通过实现 Debug trait,可以使用 println!("{:?}", your_var);
这类的语句打印出变量的值,以方便调试。
#[derive(Debug)]
可以自动为一个结构体或者枚举类型实现 Debug trait。这意味着,在使用 println!("{:?}", your_var);
这类的语句时,可以直接打印结构体或者枚举类型的值,而不必手动实现 Debug trait 的行为。#[derive(Debug)]
会自动生成实现 Debug trait 的代码,包括结构体或枚举类型的字段名和值。
例如,以下是一个使用 #[derive(Debug)]
的例子:
#[derive(Debug)]
struct Person {
name: String,
age: u32,
}
fn main() {
let person = Person { name: String::from("Alice"), age: 30 };
println!("{:?}", person);
}
在上面的例子中,#[derive(Debug)]
自动为结构体 Person 实现了 Debug trait,包括它的字段名和值。在!(“{:?}”, person);` 这个语句中,我们可以直接打印出 person 的值,而不必手动实现 Debug trait。
7. 哈希map
哈希 map的更新方法
//导入HashMap模块
use std::collections::HashMap;
//新建一个HashMap的可变变量
let mut scores = HashMap::new();
//直接覆盖插入键值对
scores.insert(String::from("Blue"), 10);
//只在键没有对应值时插入键值对
scores.entry(String::from("Blue")).or_insert(50);
//找到一个键对应的值并根据旧的值更新它
let word = String::from("Blue");
if let Some(count) = scores.get_mut(&word) {
*count += 1;
}
8. Trait Bound 语法
多个 Trait Bound:我们也可以限制一个类型参数同时实现多个 trait。例如,下面的例子中限制了泛型类型参数必须同时实现 std::fmt::Debug
和 std::fmt::Display
trait:
fn print<T: std::fmt::Debug + std::fmt::Display>(value: T) {
println!("Debug: {:?}", value);
println!("Display: {}", value);
}
还可以通过 where 从句进行指定,这种写法更加灵活,适合于写比较复杂的 Trait Bound。例如:
fn foo<T, U>(x: T, y: U)
where
T: MyTrait + std::fmt::Debug,
U: AnotherTrait,
{
// 在此处编写代码
}
9. 生命周期注解语法
在早期版本(pre-1.0)的 Rust 中,每一个引用都必须有明确的生命周期。后来改进了,采用三条规则来判断引用何时不需要明确的注解, 简称生命周期省略规则:
-
编译器为每一个引用参数都分配一个生命周期参数。换句话说就是,函数有一个引用参数的就有一个生命周期参数: fn foo<'a>(x: &'a i32) ,有两个引用参数的函数就有两个不同的生命周期参数, fn foo<'a, 'b>(x: &'a i32, y: &'b i32) ,依此类推。
-
如果只有一个输入生命周期参数,那么它被赋予所有输出生命周期参数: fn foo<'a>(x: &'a i32) -> &'a i32 。
-
如果方法有多个输入生命周期参数并且其中一个参数是 &self 或 &mut self ,说明是个对象的方法 (method),那么所有输出生命周期参数被赋予 self 的生命周期。这使得方法更容易读写,因为只需更少的符号。
如果编译器使用所有已知的生命周期省略规则,仍不能计算出签名中所有引用的生命周期,它不会猜测剩余引用的生命周期应该是什么。编译器会在可以通过增加生命周期注解来解决错误问题的地方给出一个错误提示,而不是进行推断或猜测。
生命周期注解并不改变任何引用的生命周期的长短。相反它们描述了多个引用生命周期相互的关系,而不影响其生命周期。未来可能会有更多的规则来避免程序员重复地编写生命周期注解。
10. 接受命令行参数
例子:
use std::env;
fn main() {
let args: Vec<String> = env::args().collect();
if args.len() != 2 {};
let args1 = &args[1];
let args2 = &args[2];
println!("args1 {}", args1 );
println!("args2 {}", args2 );
}
11. 闭包
闭包不太熟
12. 迭代器的方法
let v1 = vec![1, 2, 3];
//创建迭代器, iter 方法生成一个不可变引用的迭代器
let v1_iter = v1.iter();
//创建迭代器, 获取 v1 所有权并返回拥有所有权的迭代器
let v1_iter = v1.into_iter();
在一个 for 循环中使用迭代器
消费迭代器的方法:next方法、sum方法、
产生迭代器的方法:map方法、filter方法
迭代器,作为一个高级的抽象,被编译成了与手写的底层代码大体一致性能的代码。迭代器是 Rust 的 零成本抽象(zero-cost abstractions)之一,它意味着抽象并不会引入运行时开销。
13. 智能指针
在 Rust 标准库中,常用的智能指针有以下几种:
Box<T>
:Box 是一种智能指针类型,它允许在堆上分配内存。使用 Box 可以将值包装在堆上,并且允许值在超出其生命周期的范围时保持有效。Box 通常用于创建递归数据结构、解除类型擦除和实现可选值的包装。Rc<T>
:Rc 是一个引用计数智能指针类型,它允许使用多个所有权所有者来共享值。Rc 可以用于将数据结构进行共享,以便多个部分可以共享相同的数据。Weak<T>
:用于实现弱引用(weak reference)。一般使用 Weak 来避免强引用循环和内存泄漏。Arc<T>
:Arc 是一个原子引用计数智能指针,类似于 Rc,但可以安全地在多个线程中共享值。由于 Arc 是线程安全的,所以可以用于并发编程。RefCell<T>
:它们允许值被可变地引用,即使它们也被不可变地引用。RefCell<T>
用于包装不可复制类型。Mutex<T>
和RwLock<T>
:它们都是线程安全的智能指针,可以安全地将数据结构共享给多个线程。Mutex<T>
只允许一个线程同时访问其内部值,而RwLock<T>
允许多个线程同时访问其内部值,但要求读者和写者互斥地访问值。Pin<T>
:它是一个指针封装,用于解决由于内存重分配或移动而导致的悬挂指针问题。Pin<T>
具有一个不变性:它可以确保其持有的值不会被移动而导致悬挂指针问题。
结合 Rc<T>
和 RefCell<T>
来拥有多个可变数据所有者:Rc<RefCell<T>>
。Rc<T>
允许对相同数据有多个所有者,不过只能提供数据的不可变访问。如果有一个储存了 RefCell<T>
的 Rc<T>
的话,就可以得到有多个所有者 并且 可以修改的值了!
#[derive(Debug)]
enum List {
Cons(Rc<RefCell<i32>>, Rc<List>),
Nil,
}
use crate::List::{Cons, Nil};
use std::cell::RefCell;
use std::rc::Rc;
fn main() {
let value = Rc::new(RefCell::new(5));
let a = Rc::new(Cons(Rc::clone(&value), Rc::new(Nil)));
let b = Cons(Rc::new(RefCell::new(3)), Rc::clone(&a));
let c = Cons(Rc::new(RefCell::new(4)), Rc::clone(&a));
*value.borrow_mut() += 10;
println!("a after = {:?}", a);
println!("b after = {:?}", b);
println!("c after = {:?}", c);
}
这是非常巧妙的!通过使用 RefCell<T>
,我们可以拥有一个表面上不可变的 List ,不过可以使用 RefCell<T>
中提供内部可变性的方法来在需要时修改数据。
14. 多线程
use std::sync::{Arc, Mutex};
use std::thread;
fn main() {
// 创建一个互斥锁
let counter = Arc::new(Mutex::new(0));
// 将互斥锁的所有权传递给子线程
let counter_clone = counter.clone();
thread::spawn(move || {
// 使用互斥锁更新计数器的值
let mut val = counter_clone.lock().unwrap();
*val += 1;
}).join().unwrap();
// 在主线程中使用互斥锁访问计数器的值
let val = counter.lock().unwrap();
println!("Counter: {}", *val);
}
15. 不安全超能力
rust可以使用 unsafe 关键字来切换到不安全 Rust,接着可以开启一个新的存放不安全代码的块。这里有五类可以在不安全 Rust 中进行而不能用于安全 Rust 的操作,分别是:
- 解引用裸指针:Rust 的裸指针类型(raw pointer)允许开发人员自由地操纵内存地址。它们可以用于在代码中实现指针算法,以及访问硬件或其他操作系统接口。
- 调用不安全的函数或方法:Rust 允许使用 unsafe 关键字来标记函数,以表示这些函数具有不安全的特征。使用 unsafe 函数可以直接访问底层操作系统的 API,执行与操作系统密切相关的操作。
- 访问或修改可变静态变量:常量与不可变静态变量的一个微妙的区别是静态变量中的值有一个固定的内存地址。使用这个值总是会访问相同的地址。另一方面,常量则允许在任何被用到的时候复制其数据。另一个区别在于静态变量可以是可变的。访问和修改可变静态变量都是 不安全 的。
- 实现不安全 trait:当 trait 中至少有一个方法中包含编译器无法验证的不变式(invariant)时 trait 是不安全的。可以在 trait 之前增加 unsafe 关键字将 trait 声明为 unsafe ,同时 trait 的实现也必须标记为 unsafe 。
- 访问 union 的字段:联合体主要用于和 C 代码中的联合体交互。访问联合体的字段是不安全的,因为 Rust 无法保证当前存储在联合体实例中数据的类型。
16. 重载运算符
use std::ops::Add;
#[derive(Debug, Copy, Clone, PartialEq)]
struct Point {
x: i32,
y: i32,
}
impl Add for Point {
type Output = Point;
fn add(self, other: Point) -> Point {
Point {
x: self.x + other.x,
y: self.y + other.y,
}
}
}
fn main() {
assert_eq!(
Point { x: 1, y: 0 } + Point { x: 2, y: 3 },
Point { x: 3, y: 3 }
);
}
17. 结构体的方法
struct Point {
x: i32,
y: i32,
}
impl fmt::Display for Point
和 impl Point
的区别:
impl fmt::Display for Point
是针对 Rust 中内置的std::fmt
库中的Display
Trait 来实现Point
结构体的打印,即对结构体实现了fmt::Display
Trait,使得可以使用println!
等宏函数以预定义的格式来打印Point
类型的对象。这个实现在结构体内部并不会增加任何函数或方法;impl Point
通常是在结构体的实现块中为其添加方法和函数,以扩展结构体的功能。通过这种方式,可以为结构体添加自定义方法、实现其他 Trait 等等
18. 宏
宏(Macro)指的是 Rust 中一系列的功能:使用 macro_rules! 的 声明(Declarative)宏,和三种 过程(Procedural)宏:
- 自定义 #[derive] 宏在结构体和枚举上指定通过 derive 属性添加的代码
- 类属性(Attribute-like)宏定义可用于任意项的自定义属性
- 类函数宏看起来像函数不过作用于作为参数传递的 token
19.变量类型
在 Rust 语言中,变量类型是严格定义的,并且 Rust 是一种静态类型语言,这意味着每个变量在编译时都必须有一个明确的类型。Rust 提供了一系列内置类型,以及允许用户定义自己的类型。以下是 Rust 中的一些基本和常见的变量类型:
-
原生类型(Primitive Types):
bool
:布尔类型,可以是true
或false
。char
:字符类型,表示 Unicode 标量值。i8
,i16
,i32
,i64
,i128
:有符号整数类型。u8
,u16
,u32
,u64
,u128
:无符号整数类型。isize
和usize
:依赖于目标平台的整数类型,通常对应于指针大小。f32
和f64
:浮点数类型,f32
是单精度浮点数,f64
是双精度浮点数。!
:表示“never”类型,表示永远不会发生的值,通常用于错误处理。
-
复合类型(Composite Types):
Tuple
:元组类型,可以包含多个不同类型的值。Array
:固定大小的数组类型。Slice
:动态大小的数组视图。String
和&str
:字符串类型,String
是可变的字符串,&str
是不可变的字符串引用。
-
用户定义类型(User-Defined Types):
Struct
:结构体,可以包含多个字段。Enum
:枚举,可以表示一组固定的值。Union
:联合体,类似于 C/C++ 中的联合体,但 Rust 中的联合体是安全的。Function
:函数类型。Closure
:闭包,匿名函数。
-
指针类型(Pointer Types):
&T
:引用类型,指向某个类型的值。*const T
和*mut T
:裸指针,分别表示不可变和可变的指针。
-
Trait Types:
Trait
:特质类型,用于定义一组方法,可以被任何类型实现。impl Trait
:实现特定特质的类型,但不指定具体类型。
-
其他类型:
Box<T>
:堆分配的类型,用于在堆上分配内存。Option<T>
和Result<T, E>
:分别表示可能包含值或错误的类型。
在 Rust 中,变量的类型是通过类型注解(如 let variable: Type = value;
)来声明的。Rust 的类型系统非常严格,这有助于在编译时捕获错误,提高代码的安全性。