我们对宏的理解可以定义为用于生成代码的代码。编译时生成代码所以不会损耗运行性能
调用方式类似于函数,接受一个参数,但是返回的不是值,而是一堆新的代码
宏分为两大类:声明式宏( *declarative macros* ) macro_rules! 和三种过程宏( *procedural macros* ):
自定义宏,#[derive]在之前多次见到的派生宏,可以为目标结构体或枚举派生指定的代码
类属性宏(Attribute-like macro),用于为目标添加自定义的属性
类函数宏(Function-like macro),看上去就像是函数调用
宏与函数Rust 的函数签名是固定的:定义了两个参数,就必须传入两个参数,多一个少一个都不行,而宏就可以拥有可变数量的参数
声明式宏声明式宏允许我们写出类似 match 的代码。match 表达式是一个控制结构,其接收一个表达式,然后将表达式的结果与多个模式进行匹配,一旦匹配了某个模式,则该模式相关联的代码将被执行
vec! 创建的动态数组支持任何元素类型,也并没有限制数组的长度,如果使用函数,我们是无法做到这一点的
12345678 ...
编程语言
未读并发和并行咖啡机就是我们计算机核心,只有一个核心的情况下还想同时处理多任务只能是两个队伍共用一个核心
并发(Concurrent) 是多个队列使用同一个咖啡机,然后两个队列轮换着使用(未必是 1:1 轮换,也可能是其它轮换规则),最终每个人都能接到咖啡,快速轮换处理不同的任务,线程负责管理任务队列
并行(Parallel) 是每个队列都拥有一个咖啡机,最终也是每个人都能接到咖啡,但是效率更高,因为同时可以有两个人在接咖啡
串行 只有一个队列且仅使用一台咖啡机,前面哪个人接咖啡时突然发呆了几分钟,后面的人就只能等他结束才能继续接。
并发和并行都是对“多任务”处理的描述,其中并发是轮流处理,而并行是同时处理。
使用多线程多线程编程的风险由于多线程的代码是同时运行的,因此我们无法保证线程间的执行顺序,这会导致一些问题:
竞态条件(race conditions),多个线程以非一致性的顺序同时访问数据资源
死锁(deadlocks),两个线程都想使用某个资源,但是又都在等待对方释放资源后才能使用,结果最终都无法继续执行
一些因为多线程导致的很隐晦的 BUG,难以复现和解决
12345 ...
编程语言
未读用于跟硬件沟通的编程逻辑无法通过编译器的检查,为了实现相应的程序功能,unsafe代码块会解除检查限制,允许我们编写不安全的代码实现自己的需求。需要注意的是unsafe模式可以绕过编译阶段的检查,但是执行的时候如果逻辑有错误仍然会panic
解引用裸指针,就如上例所示
调用一个 unsafe 或外部的函数
访问或修改一个可变的静态变量
实现一个 unsafe 特征
访问 union 中的字段
解引用裸指针裸指针(raw pointer,又称原生指针) 在功能上跟引用类似,同时它也需要显式地注明可变性。裸指针长这样: *const T 和 *mut T
裸指针:
可以绕过 Rust 的借用规则,可以同时拥有一个数据的可变、不可变指针,甚至还能拥有多个可变的指针
并不能保证指向合法的内存
可以是 null
没有实现任何自动的回收 (drop)
裸指针跟 C 指针是非常像的,使用它需要以牺牲安全性为前提,但我们获得了更好的性能,也可以跟其它语言或硬件打交道。
基于引用创建裸指针123456789fn main() { let mut num = 5; let r ...
智能指针(Smart Pointers) 往往都实现了 Deref 和 Drop 特征,智能指针(Smart Pointers) 是用于管理内存和资源的核心工具之一。它们不仅封装了内存地址(像普通指针一样),还通过 所有权(Ownership) 和 借用规则(Borrowing Rules) 提供额外的安全性和功能。
Box堆对象分配Rust中的堆栈具有垃圾回收机制的编程语言不需要考虑堆栈问题,,例如Python,Java,但是C,C++,Rust需要我们深入了解堆栈
栈内存从高位地址向下存储数据,例如0xFF存储第一个字节数据的话,那么0xFE就会存储第二个字节,当然这只是一个例子,实际内存地址根据计算机操作系统长度生成,例如32位操作系统就有32位长度的内存地址,并且根据存储数据的大小地址也需要对应相应大小的内存空间进行加减操作。操作系统对栈内存的大小都有限制
堆内存则是从低位地址向上增长,堆内存通常只受物理内存限制,而且通常是不连续的,因此从性能的角度看,栈往往比堆更高。Rust 堆上对象有一个特殊之处,它们都拥有一个所有者,因此受所有权规则的限制
BOX使用场景特意的将数据分配 ...
编程语言
未读闭包定义格式|参数|{语句}
closure,一种匿名函数,可以储存在变量里的类似函数的结构
123456fn main() { let x =1; let sum=|y|x+y;//y函数参数,x+y函数体 println!("{}",sum(2));}
闭包类型推导Rust是静态语言,所有的变量都有类型
可以显示的为闭包定义类型
1234567fn main() { let sum=|x:i32,y:i32|->i32{x+y}; println!("{}",sum(2,3)); }
省略的写法
1234fn add_one_v1 (x: u32) -> u32 { x + 1 }let add_one_v2 = |x: u32| -> u32 { x + 1 };let add_one_v3 = |x| { x + ...
综合所学基础构建一个简易的grep程序
grep程序可以过滤文件中的特定字符串并且打印相关行
接受命令行参数新建项目
1cargo new minigrep
minigrep需要接收两个参数,关键字和文件路径
个 Rust 标准库提供的函数 std::env::args用于获取命令行参数,该函数返回一个迭代器
我们目前只需要知道迭代器生成一系列的值,可以在迭代器上调用 collect 方法将其转换为一个集合,比如包含所有迭代器产生元素的vector。
1234567use std::env;fn main() { let args:Vec<String>=env::args().collect(); dbg!(args);}
根据截图可知想要查询的字符串为迭代器的第二个元素,想要查找的文件路径在迭代器的第三个元素,为两个元素赋变量名
读取文件在项目根目录创建一个文件poem.txt
12345678I'm nobody! Who are you?Are you nobody, too?Then there's a ...
生命周期是为了避免悬垂引用
了每一个引用&都有一个生命周期
悬垂引用悬垂引用指的是一个引用指向的内存已经被释放或者不再有效,编程者如果在这个时候还尝试访问这个失效的引用就会发生错误
r 引用的值在尝试使用之前就离开了作用域
借用检查器Rust 编译器有一个 借用检查器(borrow checker),它比较作用域来确保所有的借用都是有效的
r 的生命周期标记为 ‘a 并将 x 的生命周期标记为 ‘b
被引用者的生命周期要大于或者等于引用者的生命周期,或者直接将所有权交给引用者
函数中的泛型生命周期编写一个返回两个字符串 slice 中较长者的函数
我们定义这个函数的时候,并不知道传递给函数的具体值,所以也不知道到底是 if 还是else 会被执行
123&i32 // 引用&'a i32 // 带有显式生命周期的引用&'a mut i32 // 带有显式生命周期的可变引用
单个的生命周期注解本身没有多少意义,因为生命周期注解告诉 Rust 多个引用的泛型生命周期参数如何相互联系的
123456789101112131415161 ...
编程语言
未读编程语言都有高效处理重复概念的工具,比如打印信息的函数或者方法再接收参数的时候我们往往希望该函数可以接受不同类型的参数进行打印,而不是为每一种类型都编写一个打印函数,这就要求打印函数需要能够接受多种类型的参数
Option
HashMap<K, V>
Result<T, E>
代码去重泛型允许我们使用一个可以代表多种类型的占位符来替换特定类型,以此来减少代码冗余。
寻找列表中最大值的小程序
12345678910fn main() { let number_list = vec![34, 50, 25, 100, 65]; let mut largest = &number_list[0]; for number in &number_list { if number > largest { largest = number; } } println!("The largest number is {largest}"); }
如果我们需要 ...
分类Rust将错误分为可处理的和不可处理的
对于一个可恢复的错误,比如文件未找到的错误,我们很可能只想向用户报告问题并重试操作。
对于一个不可恢复的错误,比如试图访问一个超过数组末端的位置,因此我们要立即停止程序。
Rust 没有异常。相反,它有 Result<T, E> 类型,用于处理可恢复的错误,还有 panic! 宏,在程序遇到不可恢
复的错误时停止执行。
panic!主动调用panic宏
被动的出现一类panic
这种程序崩溃的情况往往是我们不希望出现的,在非必要的时候不要直接结束程序
Result枚举
1234enum Result<T, E> { Ok(T), Err(E),}
T 和 E 是泛型类型参数;第十章会详细介绍泛型。现在你需要知道的就是 T 代表成功时返回的Ok 成员中的数据的类型,而 E 代表失败时返回的 Err 成员中的错误的类型
我们打开一个文件的时候有可能会遇到使用者输入的文件名错误或者文件不存在等情况,这个时候我们往往希望用户可以重新操作确认一下自己的输入是否有误
当 File::open 成功时,g ...
概述储存在堆上,不必在编译时就已知,可以随着程序的运行增长或缩小
三种常见集合
• vector 允许我们一个挨着一个地储存一系列数量可变的值• 字符串(string)是字符的集合。我们之前见过 String 类型,不过在本章我们将深入了解。• 哈希 map(hash map)允许我们将值与一个特定的键(key)相关联。这是一个叫做 map的更通用的数据结构的特定实现。
Vector简单使用标准库提供,可以存储多个值,只能存储类型相同的值,在内存中连续存放
123456789101112131415fn main() { //let v1:Vec<i32> = Vec::new();//创建为初始化的vec需要指定变量的类型 let mut v= vec![0,1,2];//使用宏创建vec由于初始化的值,编译器会自动推断 v.push(3);//使用push添加元素 let th = &v[2];//使用索引获取值 println!("vec 3 number is {th}"); ...