数据类型

数据类型记录 Rust 标准库提供的 原生类型,以及结构体和枚举体等编码实践。


P.TYP.01 类型转换要尽量使用安全的方式

【描述】

Rust 中的类型转换有多种方式,包括 as 强转、From/Into安全转换函数、Deref、以及 Unsafe 的 std::mem::transmute 等。在使用类型转换的时候,要注意场景,选择合适的方式和安全条件,不要让转换产生未定义行为。

P.TYP.02 对数组和集合容器进行索引要使用 usize 类型

【描述】

Rust 中只允许索引为 usize 类型,因为:

  1. 负索引是无意义的。
  2. usize和 裸指针大小相同,意味着指针算法不会有任何隐藏的强制转换
  3. std::mem::size_of()std::mem::align_of() 的函数返回 usize 类型。
  4. usize 不会因为平台架构的切换而导致索引值被截断的问题,比如 将u32类型的索引 用到 16位大小的嵌入式平台就会出问题。

P.TYP.03 必要时,应该使得类型可以表达更明确的语义,而不是只是直接使用原生类型

【描述】

这样可以增加代码的可读性。

【正例】

struct Years(i64);

fn main() {
    let years = Years(1942);
    let years_as_primitive_1: i64 = years.0; // Tuple
    let Years(years_as_primitive_2) = years; // Destructuring
}

【反例】

fn main() {
    let years = 1942;
}

G.TYP.01 类型转换尽可能使用安全的转换函数代替 as

【级别:建议】

建议按此规范执行。

【Lint 检测】

lint nameClippy 可检测Rustc 可检测Lint Grouplevel
as_conversionsyesnorestrictionallow
cast_losslessyesnopedanticallow
cast_possible_truncationyesnopedanticallow
cast_possible_wrapyesnopedanticallow
cast_precision_lossyesnopedanticallow
cast_sign_lossyesnopedanticallow
fn_to_numeric_castyesnoStylewarn
fn_to_numeric_cast_with_truncationyesnoStylewarn
char_lit_as_u8yesnoComplexitywarn
cast_ref_to_mutyesnocorrectnessdeny
ptr_as_ptryesnopedanticallow

【描述】

Rust 的 as 转换包含了「静默的有损转换(lossy conversion)」。诸如 i32::from 之类的转换函数只会执行无损转换(lossless conversion)。 如果输入表达式的类型发生变化,使用转换函数可以防止转换变成无声的有损转换,并使阅读代码的人更容易知道转换是无损的。

G.TYP.02 数字字面量在使用的时候应该明确标注好类型

【级别:建议】

建议按此规范执行。

【Lint 检测】

lint nameClippy 可检测Rustc 可检测Lint Grouplevel
default_numeric_fallbackyesnorestrictionallow

【描述】

如果数字字面量没有被指定具体类型,那么单靠类型推导,整数类型会被默认绑定为 i32 类型,而浮点数则默认绑定为 f64类型。这可能导致某些运行时的意外。

【正例】


#![allow(unused)]
fn main() {
let i = 10u32;
let f = 1.23f32;
}

【反例】


#![allow(unused)]
fn main() {
let i = 10; // i32
let f = 1.23; // f64
}

G.TYP.03 不要用数字类型边界值去判断能否安全转换,而应该使用 try_from 相关方法

【级别:建议】

建议按此规范执行。

【Lint 检测】

lint nameClippy 可检测Rustc 可检测Lint Grouplevel
checked_conversionsyesnopedanticallow

【描述】

在 Rust 中 From 代表不能失败的转换,而 TryFrom 则允许返回错误。

一般在数字类型转换的时候,不需要防御式地去判断数字大小边界,那样可读性比较差,应该使用 try_from 方法,在无法转换的时候处理错误即可。

【正例】


#![allow(unused)]
fn main() {
let foo: u32 = 5; 
let f = i16::try_from(foo).is_ok(); // 返回 false
}

【反例】


#![allow(unused)]
fn main() {
let foo: u32 = 5;
let _ = foo <= i16::MAX as u32; // 等价于 let _ = foo <= (i32::MAX as u32);
}