《Effective Rust》方法 1:使用类型系统表达你的数据结构

  • King
  • 更新于 2024-04-19 21:54
  • 阅读 1125

方法1:使用类型系统表达你的数据结构“谁叫他们是程序员,而不是打字员”——@thingskatedid对于来自其他静态类型编程语言(如C++、Go或Java)的人来说,Rust类型系统的基本概念是非常熟悉的。有一系列具有特定大小的整数类型,包括有符号(i8,i16,i32,

方法 1:使用类型系统表达你的数据结构

“谁叫他们是程序员,而不是打字员” —— @thingskatedid

对于来自其他静态类型编程语言(如 C++、Go 或 Java)的人来说,Rust 类型系统的基本概念是非常熟悉的。有一系列具有特定大小的整数类型,包括有符号(i8, i16, i32, i64, i128)和无符号(u8, u16, u32, u64, u128)。

还有两种整数类型,其大小与目标系统上的指针大小匹配:有符号(isize)和无符号(usize)。Rust 并不是那种会在指针和整数之间进行大量转换的语言,所以这种特性并不是特别相关。然而,标准集合返回它们的大小作为一个 usize(来自 .len()),所以集合索引意味着 usize 值非常常见 —— 从容量的角度来看,这是显然没有问题的,因为内存中的集合不可能有比系统上的内存地址更多的项。

整数类型确实让我们第一次意识到 Rust 是一个比 C++ 更严格的世界 —— 尝试将一个 quart(i32)放入 pint pot(i16)会在编译时产生错误。

let x: i32 = 42;
let y: i16 = x;
error[E0308]: mismatched types
  --> use-types/src/main.rs:14:22
   |
14 |         let y: i16 = x;
   |                ---   ^ expected `i16`, found `i32`
   |                |
   |                expected due to this
   |
help: you can convert an `i32` to an `i16` and panic if the converted value doesn't fit
   |
14 |         let y: i16 = x.try_into().unwrap();
   |                       ++++++++++++++++++++

这让人感到安心:当程序员进行有风险的操作时,Rust 不会安静地坐视不管。这也早早地表明,尽管 Rust 有更严格的规则,但它也有助于编译器消息指向如何遵守规则的方法。

建议的解决方案是抛出一个问题,即如何处理转换会改变值的情况,关于错误处理([方法 4])和使用 panic!([方法 18])我们将在后面有更多的讨论。

Rust 也不允许一些可能看起来“安全”的操作:

let x = 42i32; // Integer literal with type suffix
let y: i64 = x;
error[E0308]: mismatched types
  --> use-types/src/main.rs:23:22
   |
23 |         let y: i64 = x;
   |                ---   ^ expected `i64`, found `i32`
   |                |
   |                expected due to this
   |
help: you can convert an `i32` to an `i64`
   |
23 |         let y: i64 = x.into();
   |                       +++++++

在这里,建议的解决方案并没有提出错误处理的方法,但转换仍然需要是显式的。我们将在后面章节更详细地讨论类型转换([方法 6])。

现在继续探讨不出乎意料的原始类型,Rust 有布尔类型(bool)、浮点类型(f32, f64)和单元类型 ()(类似于 Cvoid)。

更有趣的是 char 字符类型,它持有一个 [Unicode 值](类似于 Go 的 [rune 类型])。尽管它在内部以 4 字节存储,但与 32 位整数的转换仍然不会有静默转换。

类型系统中的这种精确性迫使你明确地表达你想要表达的内容 —— u32 值与 char 不同,后者又与序列 UTF-8 字节不同,这又与序列任意字节不同,而且需要你准确地指定你的意思1。[Joel Spolsky 的著名博客]文章可以帮助你理解需要哪种类型。

当然,有一些辅助方法允许你在这不同的类型之间进行转换,但它们的签名迫使你处理(或明确忽略)失败的可能性。例如,一个 Unicode 代码点2 总是可以用 32 位表示,所以 'a' as u32 是允许的,但反向转换就比较复杂了(因为有些 u32 值不是有效的 Unicode 代码点),例如:

  • [char::from_u32] 返回一个 Option<char>,迫使调用者处理失败的情况
  • [char::from_u32_unchecked] 假设有效性,但由于结果是未定义的,因此被标记为unsafe,迫使调用者也使用unsafe([方法 16])。

聚合类型

继续讨论聚合类型,Rust 有:

  • 数组(Arrays),它们持有单个类型的多个实例,实例的数量在编译时已知。例如 [u32; 4] 是四个连续的 4 字节整数。
  • 元组(Tuples),它们持有多个异构类型的实例,元素的数量和类型在编译时已知,例如 (WidgetOffset, WidgetSize, WidgetColour)。如果元组中的类型不够独特 —— 例如 (i32, i32, &'static str, bool) —— 最好给每个元素命名并使用 …
  • 结构体(Structs),它们也持有编译时已知的异构类型实例,但是允许通过名称来引用整个类型和各个字段。
  • 元组结构体(Tuple structs)是结构体和元组的杂交体:整个类型有一个名称,但各个字段没有名称 —— 它们通过数字来引用:s.0, s.1 等。
struct TextMatch(usize, String);
let m = TextMatch(12, "needle".to_owned());
assert_eq!(m.0, 12);

这让我们来到了 Rust 类型系统的皇冠上的宝石:枚举(enum)。

在其基本形式中,很难看出有什么值得兴奋的。与其他语言一样,枚举允许你指定一组互斥的值,可能附带一个数字或字符串值。


enum HttpResultCode {
   Ok = 200,
   NotFound = 404,
   Teap...

剩余50%的内容订阅专栏后可查看

点赞 1
收藏 1
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

2 条评论

请先 登录 后评论
King
King
0x56af...a0dd
擅长Rust/Solidity/FunC/Move开发