Rust学习笔记以及我的一点想法
2025年6月13日 06:53

小马过河,不试一下怎么知道
学习rust就像小马过河一样,有人觉得太难了,有人觉得一点也不难。到底难不难只有自己试了才知道,因为每个人的水平、理解力、学习环境和心态都是不一样的。
就目前来说,我确实感觉到难,但是并没有网上说的那么难,因为所有难点都是可以理解的,不至于什么都看不懂,我认为rust和其它语言一样,都是熟能生巧的,都是时间问题。
Rust数据类型
Rust的数据类型分为基础类型和高级类型(复合类型)。
基础类型:各种数值(i32,u8,f64...等等)、字符char、布尔bool、数组arr、切片slice等等;
高级类型(复合类型):String、Vec、元组、结构体、枚举等等;
类型使用规则:能使用基础的类型就绝对不要用高级的类型(避免麻烦,减少出错)。
所有权问题
基础类型都是静态的、都存储在栈上、都可以直接拷贝使用、不存在所有权转移的问题,我们称该特征为Copy特征;
高级类型都是动态的、都存储在堆上、会转移所有权,默认只能通过引用(&String,&Vec![])的方式使用,如果要复制完全一样的值,需要使用clone()方法。
Rust语言和其它语言的区别,就在于这些高级类型的借用上,Rust语言会转移所有权、但是其他的语言不会,所以Rust比别的语言更安全。
借用和引用
引用可以理解为C语言的指针,只不过换了个说法。
与其说是引用,我感觉用索引这个词更合适。即每个值都有它的内存地址索引,就类似数据表一样。
内存中存储的值都是以:(内存地址(索引):值)成对的方式出现。
当你声明x=5时,意思是将数值5绑定到变量x上。如果要获取数值5的内存地址可以通过以下方法:&x;
同样的,当我们将内存地址绑定到y上,那通过内存地址获取到值的方式是*y。
x=5
// 将数值5赋值给x;
// &x为数值5的内存地址,即引用;
y = &x
//将数值5的内存地址(引用)赋值给y;
*y = 5
// 通过索引的内存地址查找数值的方法即*y;
let ref r = 5
// 这里的r就是5的引用,即内存地址。
// 通过r获取值的方式是*r
可变引用只能同时存在1个,可以使用花括号修改作用域来实现使用多个可变引用;
可变引用不可以和不可变引用同时存在;
一个值可以有多个不可变引用或者1个可变引用。
引用和借用规则:能不使用引用和借用就不使用(减少出错)
Rust函数
Rust函数要求参数必须标注类型,有输出的话输出也要标注类型,没有的不用标;
数据类型转换
基础类型用as,as不检查溢出,不支持自定义类型;
From/Into 安全转换。
let my_name ="Pascal";
// my_name的数据类型为字符切片;
// 将my_name转换成String字符串类型有多种方式。
let my_name =String::from("Pascal");
let my_name ="Pascal".tostring();
let my_name ="Pascal".into();
let my_name ="Pascal".to_owned();
以上这四种都能将my_name转换成String字符串类型
say_hello(&s);
say_hello(&s[..]);
say_hello(s.as_str());
以上三种方式都可以将String类型转换成&str类型
字符串可以切片,不能索引,但是切片仍然容易报错。
push_str()追加字符串字面量,也可以追加字符char,但是push()只能追加字符char,因此,以后只用push_str()方法就行了。同理还有insert_str()和insert()
替换方法有三个,replace(),replacen()和replacerange(),前两个是返回一个新的字符串,而不是操作原来的字符串,不用加mut,第三个是操作原字符串,需要加mut
复合类型
rust的整个逻辑更像是个套娃。基础数据类型加mut变成可变的,再加点东西套娃成高级类型,高级类型套娃成复合类型,复合类型继续套娃泛型等等。
枚举
rust中很多数据处理函数返回的值的类型是option类型的,不能直接用。需要强制进行错误处理。
模式匹配
在python中匹配顶多算是一个不常用的函数,但是在rust中,模式匹配被用出花来,到处都贯穿着模式匹配思想。建议多多练习,灵活运用,融会贯通。
泛型
编程最基本的元素:数据、类型、函数。数据是一个值,而类型是给值进行的归类。函数是获取某一种类型值的方式,流程则控制着数据的走向。
数据通常以指针和值成对的方式存储。指针存放在栈上,而值却不一定。基础类型的值存储在栈上,复合类型的通常存储在堆上(也不一定)。
最小单位的值默认都是静态的、不可变的,静态的意思是长度大小都是固定的,不可变的意思是无法通过外部改变其内容的。这一类的值,我们统称为基础类型。
多个基础的类型值组合到一起就是复合类型。复合类型就有动态和静态之分。
基础类型和静态复合类型都存储在栈上,可以直接使用值,不会产生所有权问题,我们称之为Copy特征。
动态的意思是长度是不固定的。这类值通常存储在堆上,只能通过&方式引用,无法获取所有权,一旦获取所有权,则之前的数据就会销毁。当然我们也可以通过clone()的方式解决该问题,不过因为数据的不固定性,通过clone()方式进行复制的效率极低,性能也不强。因此我们通常使用&引用的方式进行调用。
不管是基础类型还是复合类型,都是默认不可变的,如果要变成可变的,就要进行一次包装。这个包装就是mut。你也可以想象它就是数据上面添加了一个开关。我更喜欢比喻它是一次套娃。
除了基础类型和复合类型,还有一类更复杂的类型,他就是泛型。泛型是根据一类规则提炼出来的。而这些规则,同样可以使用函数进行表达。
这里我们要注意:函数不仅能处理数据,也能处理类型。泛型函数往往就是表达某一类数据的形式。并且泛型和基础类型和复合类型一样,在任何表达某一类数据的地方都能使用。例如:结构体、枚举、方法、数组等等。因此它为rust编程提供了极大的灵活性。
泛型不一定能正常运行,有时候需要加上约束特征。
T 就是泛型参数,实际上在 Rust 中,泛型参数的名称你可以任意起,但是出于惯例,我们都用 T (T 是 type 的首字母)来作为首选,这个名称越短越好,除非需要表达含义,否则一个字母是最完美的。
这里的思想和python有很大的区别。python是希望尽量语义化,方便理解。而rust中泛型却希望越简洁越好,不在乎语义,这点不太苟同。
编译器无法推断你想要的泛型参数,就需要加上fn::
泛型是一种不具体的类型,因此可以用于多个场景,例如函数、枚举、结构体、方法等等。
根据不同的泛型可以实现不同的方法。
泛型是通过具体的类型不断提炼出来的,这也是写泛型的一个路径或步骤。
struct A; // 创建一个单元结构体A
struct S(A); // 创建一个元组结构体S,里面包含唯一的元组成员,类型为A
struct SGen<T>(T); // 创建一个元组结构体SGen,里面包含唯一的元组成员,类型为泛型T
fn reg_fn(_s: S) {} //创建reg_fn函数,有一个参数_s,类型为S
fn gen_spec_t(_s: SGen<A>) {}//创建一个gen_spec_t函数,有一个参数_s,类型为SGen<A>
fn gen_spec_i32(_s: SGen<i32>) {}//创建一个gen_spec_i32函数,有一个参数_s,类型为SGen<i32>
fn generic<T>(_s: SGen<T>) {}//创建一个fn generic函数,有一个参数_s,类型为SGen<T>
fn main() {
// 使用非泛型函数
reg_fn(S(A));// 参数为S(A)。A为单元结构体,类型就是唯一值。因此S(A)在此处为结构体S(A)的实例。
gen_spec_t(SGen(A)); // 参数为SGen(A)。A为单元结构体,类型就是唯一值。因此SGen(A)在此处为结构体SGen<A>(A)的实例。
gen_spec_i32(SGen(15)); // 参数为SGen(15)。SGen(15)的结构体应为SGen<i32>(i32)
// 显式地指定类型参数 `char`
generic::<char>(SGen('d'));//参数为SGen('d')。SGen('d')的结构体应为SGen<char>(char)。
// 隐式地指定类型参数 `char`.
generic(SGen('a'));//参数为SGen('a')。SGen('a')的结构体应为SGen<char>(char)。
}
特征与特征对象
到了这里你会发现难度陡升,一些概念甚至很难理解。必须要反复入门。
特征是实现了某一行为特性的类型,但是这些类型并不一定是同一个类型,而是一类类型。我们可以将具有这一类特征的类型的集合,称为特征对象。说白了他就是特征的进一步抽象。
当我们不需要关注具体类型的时候我们可以将输出类型用impl sometrait来代替,但是这里有一个需要注意的点,impl sometrait只能代替单个类型进行输出。那如果有多个类型呢?使用特征对象:dyn sometrait。
特征对象的关键词为dyn。特征对象一般只能使用引用的方式调用,有两种方式,一个是普通的&引用,一个是Box<>智能指针引用。它两是一回事,写法不同。对于Box<>后续会学习,现在暂且按下不表。
特征对象仅适用于不知道输出类型的场景。对于知道输出类型的场景使用泛型。
Rust经典名言:没有什么是加一层解决不了的,如果不行那就再加一层。充分说明rust是一门套壳语言,一个概念套另一个概念。
其他语言都是针对数据编程函数,而rust多了个针对类型的编程语言,双管齐下,极大的提高了灵活性,同时也增加了学习难度。
生命周期
生命周期并没有想象中的那么难。在rust编程中用到的概率不超过10%,而且主要是用来取悦编译器,消除错误,通过编译用的。
生命周期也可以理解为rust编程的另一层套壳。只有在输出为引用的时候才需要考虑。
生命周期遵循以下规则:
1. 每个引用参数都有独立的生命周期;
2. 若只有一个引用输入参数,则输入输出的生命周期相同;
3. 若有多个引用输入参数,若其中有一个为&self或者&mut self,则输出的生命周期和self相同。
生命周期默认是可以消除的。消除遵循三条原则(以下表述不准确且难懂,文字需要再推敲一下):
- 函数:只有一个引用输入参数;
- 方法:可以有多个引用输入参数,但其中一个必须为&self或者&mut self。
不满足以上2条的都要标注。
关于变量可变性的思考
不添加mut,变量就不能变了吗?
请看以下代码:
// 代码一
fn main() {
let mut x = 5;
println!("The value of x is: {x}");
x = 6;
println!("The value of x is: {x}");
}
// 代码二
fn main() {
let x = 5;
println!("The value of x is: {x}");
let x = 6;
println!("The value of x is: {x}");
}
我们可以通过变量遮蔽的方式,让不可变的变量变得“可变”。
mut:仅修改变量内存中的值,不修改变量类型,性能高,官方推荐;
变量遮蔽:在内存中创建新的值和类型,将原先的隐藏掉,更灵活,更简便,不易出错,特定场景下使用。
专业办理低费率POS机,使用稳定,不乱涨价,不乱扣费,微信联系salesleads
版权声明:本站文章大部分为原创文章,如需转载请提前联系站长获得授权;本站部分内容源自网络,本站承诺绝不用于商业用途,如有冒犯请联系站长删除,谢谢。站长微信:salesleads 本站公众号:企泰7TEC,敬请关注!本文链接:https://7tec.cn/detail/636