重新再补一下基础
一、基础
整数
- 无自动类型转换,不同类型变量之间赋值会报错
- 2个整数相除得到的还是整数,比如5/2得到的是2
变量
- 函数的参数必须显示声明类型
if语句
if
表达式中的条件必须是bool
类型,即布尔值 。- if/else是一个表达式,在rust中表达式可以返回一个值,但要求各个分支返回的类型必须一致
rust
let number = 3;
let message = if number < 5 {
"smaller than 5"
} else {
"greater than or equal to 5"
};
溢出和下溢
比如两个u8类型数据相加值为256,当发生溢出时,rust会有两种处理方式
- 拒绝执行
- wrap around 选择环绕,两个u8相加的值,溢出的值重新从0开始到255
- overflow-checks可以在配置文件中设置这两种方法的选择
- 如果需要根据上下文执行不同的运算效果,可以使用下面的方法
- wrapping_methods
- saturating_methods
rust
let x = 255u8;
let y = 1u8;
let sum = x.wrapping_add(y);
assert_eq!(sum, 0);
let x = 255u8;
let y = 1u8;
let sum = x.saturating_add(y);
assert_eq!(sum, 255);
as类型转换
需要注意的这个截断的概念
模块可见性
pub
:使实体公开 ,即可以从定义它的模块外部访问,可能从其他 crate 访问。pub(crate):
使实体在同一个 crate 中公开,但不在外部。pub(super):
在父模块中公开实体。pub(in path::to::module):
在指定模块中公开实体。
类型布局
这里包括数据对齐,初次了解这个概念,通过deepseek查找了一下答案。
在计算机中数据对齐的作用是什么?
提升内存访问速度(性能)
现代计算机的CPU通过数据总线从内存中读取数据。这个总线是有宽度的(例如32位、64位)。内存系统也通常被设计为在对齐的地址上访问时效率最高。
-
未对齐访问的代价:如果一个4字节的整数存储在地址0x0003(即没有在4字节边界上对齐),CPU需要执行两次内存访问:
- 第一次从地址0x0000读取4个字节(获取0x0000-0x0003,包含该整数的第一个字节)。
- 第二次从地址0x0004读取4个字节(获取0x0004-0x0007,包含该整数的后三个字节)。
- 然后CPU需要将这两次读取结果中的相关部分拼接起来,才能得到这个整数的值。
-
对齐访问的优势:如果同一个4字节整数存储在地址0x0004(在4字节边界上对齐),CPU只需一次内存访问即可读取整个整数。
显然,一次操作比两次操作外加拼接要快得多。对于高性能计算,这种差异是至关重要的。
rust
//在rust中String类型是由指针,长度,空间三个部分组成的
//每一个字段都是表示usize,所以在64位系统中,usize占用8个字节
//那么String占用内容的大小就是 3*8 = 24
fn string_size() {
assert_eq!(size_of::<String>(), 24);
}
更详细内容查看,类型布局
引用
这里容易误解的一个点是,并不是所有的指针都指向堆
Rust 中的大多数引用,在内存中表示为指向内存位置的指针。因此,它们的大小与指针的大小相同,即 usize
。
rust
assert_eq!(std::mem::size_of::<&String>(), 8);
assert_eq!(std::mem::size_of::<&mut String>(), 8);
什么是胖指针? 就是带有附加元数据的指针 比如Stirng类型它存储的结构是,有指针,长度,容量。比如&str,它的内存模式是一个指针和一个字符长度
二、Trait
标准库中常用trait
先附上一个关于trait的文档链接tour-of-rusts-standard-library-traits
在标准库中定义的一些关键trait
- Operator traits (e.g.
Add
,Sub
,PartialEq
, etc.)
Operator | Trait |
---|---|
+ |
Add |
- |
Sub |
* |
Mul |
/ |
Div |
% |
Rem |
== and != |
PartialEq |
< , > , <= , and >= |
PartialOrd |
算术运算符在std::ops模块,比较运算符在std::cmp模块
From
andInto
, for infallible conversionsClone
andCopy
, for copying valuesDeref
and deref coercion- 通过为类型
T
实现Deref<Target = U>
,您可以告诉编译器&T
和&U
在某种程度上是可以互换的。
- 通过为类型
Sized
, to mark types with a known sizeSized
, to mark types with a known size
宏
trait的作用
- 解锁内置行为,比如上面的运算符trait
- 扩展trait,向现有的类型添加行为
- 通用编程,比如泛型
rust
pub struct Ticket {
title: String,
description: String,
status: String,
}
// 存在一个疑问这里为什么不是&self.title.trim()
impl Ticket {
pub fn title(&self) -> &str {
self.title.trim()
}
pub fn description(&self) -> &str {
self.description.trim()
}
}
Sized
要点
- str也是动态大小类型
- Rust 有一个特定的 trait 来确定一个类型的大小是否在编译时可知,即Sized
- 对于动态大小类型,必须放在指针后面来使用
- 每次具有泛型类型参数时,编译器都会隐式假定它是
Sized
- 当函数绑定动态大小类型时,可以使用?Sized,表示可能是或可能不是Sized
rust
// 注意t的类型是&T因为其类型可能不是 `Sized` 的,所以需要将其置于某种指针之后,所以使用了引用
fn generic<T: ?Sized>(t: &T) {
// --snip--
}
Into和From
- From和Into是双重Trait,实现了From的任何类型,都实现了Into
- into()转换成什么类型,需要显示类型标注
rust
pub struct WrappingU32 {
value: u32,
}
impl From<u32> for WrappingU32{
fn from(value:u32)->Self{
WrappingU32{value}
}
}
fn example() {
let wrapping: WrappingU32 = 42.into();
let wrapping = WrappingU32::from(42);
}
泛型和关联类型
关联类型使得只有一个实现,在这里定义trait时,type Output是在实现trait时需要确定的类型,Exponent=Self默认值是Self,在使用时可以更改为需要实现的类型,使得与self关联一起
rust
pub trait Power<Exponent = Self> {
type Output;
fn power(&self, n: Exponent) -> Self::Output;
}
impl Power<u16> for u32 {
type Output = u32;
fn power(&self, n: u16) -> Self::Output {
self.pow(n.into())
}
}
impl Power<&u32> for u32 {
type Output = u32;
fn power(&self, n: &u32) -> Self::Output {
self.power(*n)
}
}
impl Power<u32> for u32 {
type Output = u32;
fn power(&self, n: u32) -> Self::Output {
self.pow(n)
}
}
Clone
它的方法 clone
采用对 self
的引用,并返回一个相同类型的新拥有的实例。
rust
pub trait Clone {
fn clone(&self) -> Self;
}
Copy
rust
pub trait Copy: Clone { }
如果类型实现了 Copy
,则无需调用 .clone()
来创建该类型的新实例:Rust 会隐式地为您完成此作。
- 无论
T
是什么,&mut T
从不实现Copy
类型必须满足一些要求才能被允许实现 Copy
- 除了它在内存中占用的std::mem::size_of字节之外,该类型不管理任何额外的资源(例如堆内存、文件句柄等)。
- 该类型不是可变引用 (
&mut T
)。
在实现Copy时,可以派生它,但必须也派生Clone,因为Copy是Clone的子trait,所以这个类型也必须的视线Clone功能,下面示例说明:
rust
#[derive(Copy, Clone)]
struct MyStruct {
field: u32,
}
Drop
rust
pub trait Drop {
fn drop(&mut self);
}
Drop与Copy
如果一个类型管理超出其在内存中占用的 std::mem::size_of
字节之外的其他资源,则它无法实现 Copy
。
编译器如何知道类型是否管理其他资源? 没错: Drop
trait实现!
如果您的类型具有显式的 Drop
实现,则编译器将假定您的类型附加了其他资源,并且不允许您实现 Copy
下面的实现将报错
rust
impl Drop for MyType {
fn drop(&mut self) {
todo();
}
}
Error
rust
pub trait Error: Debug + Display {}
TryFrom和TryInto
TryFrom
和 TryInto
都在 std::convert
模块中定义,就像 From
和 Into
一样。
如果为某个类型实现 TryFrom
,则可以自动获得 TryInto
。
rust
pub trait TryFrom<T>: Sized {
type Error;
fn try_from(value: T) -> Result<Self, Self::Error>;
}
pub trait TryInto<T>: Sized {
type Error;
fn try_into(self) -> Result<T, Self::Error>;
}
在这里还是有点困惑的,没有写出来,真的动手了,发现这个章节好几个写不出来,这里的思路挺好的,先实现&str转换到Status的,然后再实现String转换到Status的
rust
#[derive(Debug, PartialEq, Clone)]
enum Status {
ToDo,
InProgress,
Done,
}
#[derive(Debug,thiserror::Error)]
#[error("{invalid_status} is not a valid status")]
struct ParseStatusError{
invalid_status:String
}
impl TryFrom<String> for Status{
type Error=ParseStatusError;
fn try_from(value: String) -> Result<Self, Self::Error>{
value.as_str().try_into()
}
}
impl TryFrom<&str> for Status {
type Error = ParseStatusError;
fn try_from(value: &str) -> Result<Self, Self::Error> {
match value.to_lowercase().as_str() {
"todo" => {
Ok(Status::ToDo)
},
"inprogress" => Ok(Status::InProgress),
"done" => Ok(Status::Done),
_ => Err(ParseStatusError {
invalid_status: value.to_string(),
}),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::convert::TryFrom;
#[test]
fn test_try_from_string() {
let status = Status::try_from("ToDO".to_string()).unwrap();
assert_eq!(status, Status::ToDo);
let status = Status::try_from("inproGress".to_string()).unwrap();
assert_eq!(status, Status::InProgress);
let status = Status::try_from("Done".to_string()).unwrap();
assert_eq!(status, Status::Done);
}
#[test]
fn test_try_from_str() {
let status = Status::try_from("todo").unwrap();
assert_eq!(status, Status::ToDo);
let status = Status::try_from("inprogress").unwrap();
assert_eq!(status, Status::InProgress);
let status = Status::try_from("done").unwrap();
assert_eq!(status, Status::Done);
}
}
三、复杂类型
向量
对于vec!向量存储,可以通过Vec::with_capacity设置容量大小,如果超出内容,它将要求分配器提供新的(更大的)堆内存块,复制元素,并释放旧内存。此作可能成本高昂,因为它涉及新的内存分配和复制所有现有元素。因此对于你能预料的内存大小可以使用Vec::with_capacity进行设置,避免自动扩容,导致性能问题。
rust
let mut numbers = Vec::with_capacity(3);
numbers.push(1);
numbers.push(2);
numbers.push(3); // Max capacity reached
numbers.push(4); // What happens here?
Iterator trait
iterator trait的实现
rust
trait Iterator {
type Item;
fn next(&mut self) -> Option<Self::Item>;
}
for循环的本质
rust
let mut iter = IntoIterator::into_iter(v);
loop {
match iter.next() {
Some(n) => {
println!("{}", n);
}
None => break,
}
}
IntoIterator trait
并非所有类型都实现 Iterator
,但许多类型都可以转换为实现 Iterator 的类型, 这就是 IntoIterator
特征的用武之地:
rust
trait IntoIterator {
type Item;
type IntoIter: Iterator<Item = Self::Item>;
fn into_iter(self) -> Self::IntoIter;
}
组合器
map
将函数应用于迭代器的每个元素。filter
仅保留满足条件的元素。filter_map
将filter
和map
组合在一个步骤中。cloned
将引用的迭代器转换为值的迭代器,克隆每个元素。enumerate
返回一个新的迭代器,该迭代器产生(index, value)
对。skip
跳过迭代器的前n 个
元素。take
在n
个元素之后停止迭代器。chain
将两个迭代器合二为一。
collect
collect
使用迭代器并将其元素收集到您选择的集合中。
collect是泛型的,需要提供类型提示来帮助推断出正确的类型,比如下面,在squares_of_evens标注类型,或者使用turbofish语法来指定类型
rust
let numbers = vec![1, 2, 3, 4, 5];
let squares_of_evens: Vec<u32> = numbers.iter()
.filter(|&n| n % 2 == 0)
.map(|&n| n * n)
.collect();
let squares_of_evens = numbers.iter()
.filter(|&n| n % 2 == 0)
.map(|&n| n * n)
// Turbofish syntax: `<method_name>::<type>()`
// It's called turbofish because `::<>` looks like a fish
.collect::<Vec<u32>>();
Slice
一个示例
rust
let numbers = vec![1, 2, 3];
//需要注意:`iter` 不是 `Vec` 的方法!
//它是 `&[T]` 的方法,但你可以对 `Vec` 调用它
//多亏了解引用强制转换。
let sum: i32 = numbers.iter().sum();
Vec<T>
和 &[T]
的关系
Vec<T>
是一个可增长的堆分配数组&[T]
是一个切片引用(胖指针,包含数据指针和长度)Vec<T>
可以自动转换为&[T]
方法查找过程
当你调用 numbers.iter()
时,Rust 编译器会:
rust
let numbers = vec![1, 2, 3];
let sum: i32 = numbers.iter().sum();
numbers
是Vec<i32>
类型Vec<i32>
没有iter()
方法- 但
Vec<i32>
实现了Deref<Target = [i32]>
- 编译器自动将
&Vec<i32>
转换为&[i32]
&[i32]
有iter()
方法
要点:当您需要将对 Vec
的不可变引用传递给函数时,首选 &[T]
而不是 &Vec<T>
。
rust
let array = [1, 2, 3];
let slice: &[i32] = &array;
数组切片和 Vec
切片是同一类型:它们是指向连续元素序列的胖指针。在数组的情况下,指针指向堆栈而不是堆,但在使用切片时这并不重要
可变切片
rust
let mut numbers = vec![1, 2, 3];
let slice: &mut [i32] = &mut numbers;
slice[0] = 42; //可以通过切片修改元素
可变切片,Rust 不允许您在切片中添加或删除元素。您将只能修改/替换已经存在的元素。
rust
let mut numbers = Vec::with_capacity(2);
let mut slice: &mut [i32] = &mut numbers;
slice.push(1);
Index trait
真的没想到,index也是trait 详细的查看文档
IndexMut
只有当类型已经实现了 Index
时,才能实现 IndexMut
,因为它解锁了额外的功能
rust
// Slightly simplified
pub trait IndexMut<Idx>: Index<Idx>
{
// Required method
fn index_mut(&mut self, index: Idx) -> &mut Self::Output;
}
BTreeMap类型
BTreeMap
保证条目按其键排序。
rust
// `K` and `V` stand for the key and value types, respectively,
// just like in `HashMap`.
impl<K, V> BTreeMap<K, V> {
pub fn insert(&mut self, key: K, value: V) -> Option<V>
where
K: Ord,
{
// implementation
}
}
线程
在此示例中,第一个生成的线程将反过来生成一个子线程,该子线程每秒打印一条消息。然后第一个线程将完成并退出。当这种情况发生时, 只要整个进程正在运行,它的子线程就会继续运行 。在 Rust 的行话中,我们说子线程的寿命超过了它的父线程。
rust
use std::thread;
fn f() {
thread::spawn(|| {
thread::spawn(|| {
loop {
thread::sleep(std::time::Duration::from_secs(1));
println!("Hello from the detached thread!");
}
});
});
}
leaking data
这里没有太理解 Box::leak()
std::thread::scope
在 Rust 中,std::thread::scope
是一个用于创建作用域线程的函数,它在 Rust 1.63 版本中稳定。它的主要作用是允许生成能够在当前作用域内借用的线程,确保所有线程在作用域结束前完成执行。
作用
安全的数据借用
作用域线程可以安全地借用栈上的数据,而无需 'static
生命周期要求:
rust
use std::thread;
fn main() {
let numbers = vec![1, 2, 3, 4, 5];
thread::scope(|s| {
// 可以安全地借用 `numbers`,因为作用域确保线程在 numbers 被丢弃前结束
s.spawn(|| {
println!("Length: {}", numbers.len());
});
s.spawn(|| {
println!("Sum: {}", numbers.iter().sum::<i32>());
});
}); // 所有线程在这里保证已经结束
// numbers 仍然可用
println!("Numbers: {:?}", numbers);
}
自动线程连接
作用域内的所有线程会在作用域结束时自动 join,无需手动管理:
rust
use std::thread;
fn main() {
let mut results = Vec::new();
thread::scope(|s| {
for i in 0..5 {
results.push(s.spawn(move || {
i * 2
}));
}
// 不需要手动 join,自动等待所有线程完成
});
for handle in results {
println!("Result: {}", handle.join().unwrap());
}
}
异步
这也是个难点