摸个笔记
首先讨论的第一点,就是对于传统的开发者来说明:为什么要去用 这样手动设定的方式设定变量的可行性。
Course给出的解释是——苦一苦开发,乐一乐运维。
好吧,让我们理解,程序员的懒惰和贪婪是无止境的,我们想要一种类型自行推断,但是性能提高、安全非常的方式。在未解决前,先权衡吧。
[[Rust 命名规范]]
变量命名
在命名方面,和其它语言没有区别,不过当给变量命名时,需要遵循 Rust 命名规范
大致化用 驼峰命名法 和 蛇形命名法。
Rust 语言有一些关键字(keywords),和其他语言一样,这些关键字都是被保留给 Rust 语言使用的,因此,它们不能被用作变量或函数的名称。在 附录 A 中可找到关键字列表。
变量绑定
- 在rust 中,我们分为两部分去探讨变量和值。
- 值是rust 中存储在内存空间中的具体数字,
- 变量,不再是内存地址的代称,而是一个独立内容。它绑定对应的内存空间。因此,在Rust中就存在绑定和解绑的两部分。
- 这样就有一个问题,在解绑和绑定新内存空间之后,原来的内存对象怎么处理它?
- 以及另一个新问题:内存对象绑定到新的变量对象时,原有的变量对象怎么了?
2.概念 :所有权
在Rust 中我们把变量赋值,改称为变量绑定。 而在绑定这个概念被提出之后,就对应的引入了所有权的概念。 即 —— 内存对象是哪个变量所有的。
3.更近一步的补充需要再学习。
变量可变性
在这一节中,我们聊一下 为什么 Rust 喜欢不可变的变量。
Rust 的关注点:高并发。以及高并发天然带来的 资源使用安全问题。
在多线程编程中,我们常常需要对一些共用资源做出操作,而在大型项目中,多人编程你不能保证他人会按照你的预期操作你所使用的资源, 这种悲观保护情绪,在开发中给我们带来了很多额外的容错流程代码。
Rust想做的事情,就是通过折磨程序员,来剔除懒狗和low的小工。默认不可变的变量要求你在使用时对他投注更大的精力去关注。
而当你需要修改变量时,额外的mut 关键字 提醒了每一个使用者应当注意安全使用。
选择可变还是不可变,更多的还是取决于你的使用场景,例如不可变可以带来安全性,但是丧失了灵活性和性能(如果你要改变,就要重新创建一个新的变量,这里涉及到内存对象的再分配)。而可变变量最大的好处就是使用上的灵活性和性能上的提升。
例如,在使用大型数据结构或者热点代码路径(被大量频繁调用)的情形下,在同一内存位置更新实例可能比复制并返回新分配的实例要更快。使用较小的数据结构时,通常创建新的实例并以更具函数式的风格来编写程序,可能会更容易理解,所以值得以较低的性能开销来确保代码清晰。
使用下划线开头 忽略未使用的变量 和 函数
正如我们在 介绍 cargo 时所说的,Rust 有着一个优秀又严格的 编译器 加管理器,而因为严格,Rust对未使用的函数和变量都会检测挑出来。告警 告诉你: 这些内容都是未使用的,请注意。
而我们在编码过程中的时候,常常处于调试和测试的过程中,而这时,我们看到满屏的黄标是很影响心情的。
Rust 提供了编码规范解决这个问题, _当我们使用下划线这个关键词的时候,Rust 认为,对应的内容(变量和函数)是内部使用的,不需要向外界提供,因此不再告警未使用了。
比如下面的代码:
![[Pasted image 20230615165557.png]]
这里上面的编译运行就是告警情况。而下面红框的就是修改之后清爽的编译执行情况。
附赠测试代码
fn _test_error_1() { println!("test_error_1()"); let mut x: usize = 0; x -= 1; println!("{}", x); } // use std::mem; fn _test_type_1() { let a = 11; let b = 0xFFi16; println!("{} {}", b, std::mem::size_of_val(&b)); // let b = b:i8; // 类型转换 不行 // println!("{} {}", b, std::mem::size_of_val(&b)); // let b = 0xFFi8; // 隐式强转 不行 // println!("{} {}", b, std::mem::size_of_val(&b)); let b = 0xFF_i32; // 类型标注 只能用在值标定,不能使用变量 println!("{} {}", b, std::mem::size_of_val(&b)); let b = 30i128; println!("{} {}", a, std::mem::size_of_val(&b)); let c = 32_i32; println!("{} {}", b, std::mem::size_of_val(&c)); } fn _test_add() { let num = _add(_add(1, 2), _add(3, 4)); println!("{} ", num); } fn test_let_info() { let zry; zry = 1; println!("{}", zry); } fn main() { test_let_info(); } fn _add(i: i32, j: i32) -> i32 { // return i+j; i + j }
变量解构
变量解构实际上讲的是let 这个关键字 所提供的一个 绑定特性。
let 可以对复杂变量进行拆解,然后匹配对应的内存对象进行绑定
所谓复杂变量是指由基本变量组合而来的 变量对象, 比如 元组,
fn test_let_jiegou() { let (z,r,y,mut reach): (char,char,char,i32) = ('a', 'a', 'c', 100); println!("{} {} {} {}", z,r,y,reach); reach = 111; assert_eq!(111,reach); } fn main() { test_let_jiegou(); }
在这个元组中,我们由多个对象,多个类型,但是我们可以一次声明定义完成。
有点,c++中 int a = b = c =0; 那味了。当然比他更强的是跨类型声明定义。
更进一步的解构 —— 解构式赋值。
来自 Rust 1.59 版本之后更进一步的 变量绑定 。—— 左值 可以使用 元组、切片、结构体模式。
struct Struct { e: i32 } fn test_let_jiegou() { let (z,r,y,mut reach): (char,char,char,i32) = ('a', 'a', 'c', 100); println!("{} {} {} {}", z,r,y,reach); reach = 111; assert_eq!(111,reach); // 解构式赋值 let (a,b,c,d,e ); (a,b) = (11,22); [c,..,d,_] = [1,2,3,4,5,6,7,8,9]; Struct {e,..} = Struct {e: 10}; println!("{} {} {} {} {} ", a,b,c,d,e ); } fn main() { test_let_jiegou(); }
执行结果入下:
![[Pasted image 20230615214954.png]]
在我们使用 形如 [c,..,d,_]的格式时, 最后的 _实际是代称,当我们使用占位之后,我们的值也对应的修改。 [c,..,d,_,_] 这时,我们的d 值变更为7。 这里于最后两位处的值相同。
变量和常量之间的差异
在 Rust 中 常量值的定义 直观感受 有点类似于 C++ 中的 宏定义的。
在c++ 中:
#define MAX_POINTS = 100000;
在Rust 中
const MAX_POINTS: u32 =100_000;
变量的值不能更改可能让你想起其他另一个很多语言都有的编程概念:常量(constant)。与不可变变量一样,常量也是绑定到一个常量名且不允许更改的值,但是常量和变量之间存在一些差异:
常量不允许使用 mut。常量不仅仅默认不可变,而且自始至终不可变,因为常量在编译完成后,已经确定它的值。
常量使用 const 关键字而不是 let 关键字来声明,并且值的类型必须标注。
我们将在之后 介绍,因此现在暂时无需关心细节。
下面是一个常量声明的例子,其常量名为 MAX_POINTS,值设置为 100,000。(Rust 常量的命名约定是全部字母都使用大写,并使用下划线分隔单词,另外对数字字面量可插入下划线以提高可读性):
常量可以在任意作用域内声明,包括全局作用域,在声明的作用域内,常量在程序运行的整个过程中都有效。对于需要在多处代码共享一个不可变的值时非常有用,例如游戏中允许玩家赚取的最大点数或光速。
在实际使用中,最好将程序中用到的硬编码值都声明为常量,对于代码后续的维护有莫大的帮助。如果将来需要更改硬编码的值,你也只需要在代码中更改一处即可。
总结:
rust 中使用了两个不同的关键字 区分常量和变量, 不同于C++ ,const 和 let 具备同等的权重,const 不必作为修饰词限定类型声明。
变量遮蔽
所谓变量遮蔽,是指同名的变量在重定义时,后面的变量会顶替掉前面的变量。
这种顶替是从内存空间和内存绑定层次实现的,因此,我们明确第二点,rust 的类型是 经过包装的类型,内部包括了 释放和创建申请的相关操作。
在 Rust 中,drop 是一个特殊的 trait,用于定义当一个值离开作用域时应该执行的代码。这个 trait 可以用来实现资源管理,例如释放内存或关闭文件句柄。当一个值被销毁时,Rust 会自动调用其 drop 方法,以确保资源被正确释放。
所以rust 中的值都是封装之后的类型么?内部包含对应的trait ,保证释放合理
是的,Rust 中的值都是封装之后的类型,内部包含对应的 trait,以确保资源被正确释放。这种方式可以避免内存泄漏和资源泄漏等问题,提高程序的可靠性和安全性。
所以let 重定义一个变量后,原来的同名变量将会调用对应的drop 方式 释放变量和值两者的空间吗?
是的,当使用 let 重新定义一个变量时,原来的同名变量将会被销毁,其对应的值也会被释放。Rust 会自动调用其 drop 方法,以确保资源被正确释放。这种方式可以避免内存泄漏和资源泄漏等问题,提高程序的可靠性和安全性。