基本类型

Rust 每个值都有其确切的数据类型,总的来说可以分为两类:基本类型和复合类型。 基本类型意味着它们往往是一个最小化原子类型,无法解构为其它类型(一般意义上来说),由以下组成:

  • 数值类型: 有符号整数 (i8i16i32i64isize)、 无符号整数 (u8u16u32u64usize) 、浮点数 (f32f64)、以及有理数、复数
  • 字符串:字符串字面量和字符串切片 &str
  • 布尔类型: truefalse
  • 字符类型: 表示单个 Unicode 字符,存储为 4 个字节
  • 单元类型: 即 () ,其唯一的值也是 ()

类型推导与标注

Rust 是一门静态类型语言,也就是编译器必须在编译期知道我们所有变量的类型,但这不意味着你需要为每个变量指定类型,因为 Rust 编译器可以根据变量的值和上下文中的使用方式来自动推导出变量的类型,但在某些情况下,它无法推导出变量类型,需要手动去给予一个类型标注

整数

整数是没有小数部分的数字。之前使用过的 i32 类型,表示有符号的 32 位整数( i 是英文单词 integer 的首字母,与之相反的是 u,代表无符号 unsigned 类型)。下表显示了 Rust 中的内置的整数类型:

长度 有符号类型 无符号类型
8 位 i8 u8
16 位 i16 u16
32 位 i32 u32
64 位 i64 u64
128 位 i128 u128
视架构而定 isize usize

类型定义的形式统一为:有无符号 + 类型大小(位数)无符号数表示数字只能取正数,而有符号则表示数字既可以取正数又可以取负数。就像在纸上写数字一样:当要强调符号时,数字前面可以带上正号或负号;然而,当很明显确定数字为正数时,就不需要加上正号了。有符号数字以补码形式存储。

每个有符号类型规定的数字范围是 -(2n - 1) ~ 2n - 1 - 1,其中 n 是该定义形式的位长度。因此 i8 可存储数字范围是 -(27) ~ 27 - 1,即 -128 ~ 127。无符号类型可以存储的数字范围是 0 ~ 2n - 1,所以 u8 能够存储的数字为 0 ~ 28 - 1,即 0 ~ 255。

此外,isize 和 usize 类型取决于程序运行的计算机 CPU 类型: 若 CPU 是 32 位的,则这两个类型是 32 位的,同理,若 CPU 是 64 位,那么它们则是 64 位。

整形字面量可以用下表的形式书写:

数字字面量 示例
十进制 98_222
十六进制 0xff
八进制 0o77
二进制 0b1111_0000
字节 (仅限于 u8) b'A'

这么多类型,有没有一个简单的使用准则?答案是肯定的, Rust 整型默认使用 i32,例如 let i = 1,那 i 就是 i32 类型,因此你可以首选它,同时该类型也往往是性能最好的isize 和 usize 的主要应用场景是用作集合的索引。

整型溢出

假设有一个 u8 ,它可以存放从 0 到 255 的值。那么当你将其修改为范围之外的值,比如 256,则会发生整型溢出。关于这一行为 Rust 有一些有趣的规则:当在 debug 模式编译时,Rust 会检查整型溢出,若存在这些问题,则使程序在编译时 panic(崩溃,Rust 使用这个术语来表明程序因错误而退出)。

在当使用 --release 参数进行 release 模式构建时,Rust 检测溢出。相反,当检测到整型溢出时,Rust 会按照补码循环溢出(two’s complement wrapping)的规则处理。简而言之,大于该类型最大值的数值会被补码转换成该类型能够支持的对应数字的最小值。比如在 u8 的情况下,256 变成 0,257 变成 1,依此类推。程序不会 panic,但是该变量的值可能不是你期望的值。依赖这种默认行为的代码都应该被认为是错误的代码。

要显式处理可能的溢出,可以使用标准库针对原始数字类型提供的这些方法:

  • 使用 wrapping_* 方法在所有模式下都按照补码循环溢出规则处理,例如 wrapping_add
  • 如果使用 checked_* 方法时发生溢出,则返回 None 值
  • 使用 overflowing_* 方法返回该值和一个指示是否存在溢出的布尔值
  • 使用 saturating_* 方法使值达到最小值或最大值

浮点数

按照存储大小,把浮点型划分为 f32 和 f64。其中 f64 是默认的浮点类型。

  • f32 又称为 单精度浮点型
  • f64 又称为 双精度浮点型,它是 Rust 默认的浮点类型.

Rust 中不能将 0.0 赋值给任意一个整型,也不能将 0 赋值给任意一个浮点型。

fn main() {
let x = 2.0; // f64
let y: f32 = 3.0; // f32
}

_下划线

当数字很大的时候,Rust 可以用 **(_下划线) ** ,来让数字变得可读性更好。

PLAINTEXT

let price12 =1_000_000;
println!("price12 {}", price12); //输出 price12 1000000 let price13 =1_000_000.666_123;
println!("price13 {}", price13);//输出 price13 1000000.666123

浮点数陷阱

浮点数由于底层格式的特殊性,导致了如果在使用浮点数时不够谨慎,就可能造成危险,有两个原因:

  1. 浮点数往往是你想要数字的近似表达 浮点数类型是基于二进制实现的,但是我们想要计算的数字往往是基于十进制,例如 0.1 在二进制上并不存在精确的表达形式,但是在十进制上就存在。这种不匹配性导致一定的歧义性,更多的,虽然浮点数能代表真实的数值,但是由于底层格式问题,它往往受限于定长的浮点数精度,如果你想要表达完全精准的真实数字,只有使用无限精度的浮点数才行
  2. 浮点数在某些特性上是反直觉的 例如大家都会觉得浮点数可以进行比较,对吧?是的,它们确实可以使用 >>= 等进行比较,但是在某些场景下,这种直觉上的比较特性反而会害了你。因为 f32 , f64 上的比较运算实现的是 std::cmp::PartialEq 特征(类似其他语言的接口),但是并没有实现 std::cmp::Eq 特征,但是后者在其它数值类型上都有定义,说了这么多,可能大家还是云里雾里,用一个例子来举例:

Rust 的 HashMap 数据结构,是一个 KV 类型的 Hash Map 实现,它对于 K 没有特定类型的限制,但是要求能用作 K 的类型必须实现了 std::cmp::Eq 特征,因此这意味着你无法使用浮点数作为 HashMap 的 Key,来存储键值对,但是作为对比,Rust 的整数类型、字符串类型、布尔类型都实现了该特征,因此可以作为 HashMap 的 Key

为了避免上面说的两个陷阱,你需要遵守以下准则:

  • 避免在浮点数上测试相等性
  • 当结果在数学上可能存在未定义时,需要格外的小心
fn main() {
let abc: (f32, f32, f32) = (0.1, 0.2, 0.3);
let xyz: (f64, f64, f64) = (0.1, 0.2, 0.3); println!("abc (f32)");
println!(" 0.1 + 0.2: {:x}", (abc.0 + abc.1).to_bits());
println!(" 0.3: {:x}", (abc.2).to_bits());
println!(); println!("xyz (f64)");
println!(" 0.1 + 0.2: {:x}", (xyz.0 + xyz.1).to_bits());
println!(" 0.3: {:x}", (xyz.2).to_bits());
println!(); assert!(abc.0 + abc.1 == abc.2);
assert!(xyz.0 + xyz.1 == xyz.2);
}

输出如下:

abc (f32)
0.1 + 0.2: 3e99999a
0.3: 3e99999a xyz (f64)
0.1 + 0.2: 3fd3333333333334
0.3: 3fd3333333333333 thread 'main' panicked at 'assertion failed: xyz.0 + xyz.1 == xyz.2',
➥ch2-add-floats.rs.rs:14:5
note: run with `RUST_BACKTRACE=1` environment variable to display
➥a backtrace

对 f32 类型做加法时,0.1 + 0.2 的结果是 3e99999a0.3 也是 3e99999a,因此 f32下的 0.1 + 0.2 == 0.3通过测试,但是到了 f64类型时,结果就不一样了,因为 f64精度高很多,因此在小数点非常后面发生了一点微小的变化,0.1 + 0.2以 4结尾,但是 0.3以结尾,这个细微区别导致 f64下的测试失败了,并且抛出了异常。

NaN

对于数学上未定义的结果,例如对负数取平方根 -42.1.sqrt() ,会产生一个特殊的结果:Rust 的浮点数类型使用 NaN (not a number)来处理这些情况。

所有跟 NaN 交互的操作,都会返回一个 NaN,而且 NaN 不能用来比较,下面的代码会崩溃:

fn main() {
let x = (-42.0_f32).sqrt();
assert_eq!(x, x);
}

出于防御性编程的考虑,可以使用 is_nan() 等方法,可以用来判断一个数值是否是 NaN :

fn main() {
let x = (-42.0_f32).sqrt();
if x.is_nan() {
println!("未定义的数学行为")
}
}

数字运算

// 加法
let sum = 5 + 10;
// 减法
let difference = 95.5 - 4.3;
// 乘法
let product = 4 * 30;
// 除法
let quotient = 56.7 / 32.2;
// 求余
let remainder = 43 % 5;
fn main() {
// 编译器会进行自动推导,给予twenty i32的类型
let twenty = 20;
// 类型标注
let twenty_one: i32 = 21;
// 通过类型后缀的方式进行类型标注:22是i32类型
let twenty_two = 22i32; // 只有同样类型,才能运算
let addition = twenty + twenty_one + twenty_two;
println!("{} + {} + {} = {}", twenty, twenty_one, twenty_two, addition); // 对于较长的数字,可以用_进行分割,提升可读性
let one_million: i64 = 1_000_000;
println!("{}", one_million.pow(2)); // 定义一个f32数组,其中42.0会自动被推导为f32类型
let forty_twos = [
42.0,
42f32,
42.0_f32,
]; // 打印数组中第一个值,并控制小数位为2位
println!("{:.2}", forty_twos[0]);
}

位运算

Rust的运算基本上和其他语言一样

运算符 说明
& 位与 相同位置均为1时则为1,否则为0
位或
^ 异或 相同位置不相同则为1,相同则为0
! 位非 把位中的0和1相互取反,即0置为1,1置为0
<< 左移 所有位向左移动指定位数,右位补零
>> 右移 所有位向右移动指定位数,左位补零
fn main() {
// 二进制为00000010
let a:i32 = 2;
// 二进制为00000011
let b:i32 = 3; println!("(a & b) value is {}", a & b);
// (a & b) value is 2
println!("(a | b) value is {}", a | b);
// (a | b) value is 3
println!("(a ^ b) value is {}", a ^ b);
// (a ^ b) value is 1
println!("(!b) value is {} ", !b);
// (!b) value is -4
println!("(a << b) value is {}", a << b);
// (a << b) value is 16
println!("(a >> b) value is {}", a >> b);
// (a >> b) value is 0
let mut a = a;
// 注意这些计算符除了!之外都可以加上=进行赋值 (因为!=要用来判断不等于)
a <<= b;
println!("(a << b) value is {}", a);
// (a << b) value is 16
}

序列(Range)

Rust 提供了一个非常简洁的方式,用来生成连续的数值,例如 1..5,生成从 1 到 4 的连续数字,不包含 5 ;1..=5,生成从 1 到 5 的连续数字,包含 5,它的用途很简单,常常用于循环中:

for i in 1..=5 {
println!("{}",i);
}

最终程序输出:

1
2
3
4
5

序列只允许用于数字或字符类型,原因是:它们可以连续,同时编译器在编译期可以检查该序列是否为空,字符和数字值是 Rust 中仅有的可以用于判断是否为空的类型。如下是一个使用字符类型序列的例子:

for i in 'a'..='z' {
println!("{}",i);
}
a
b
c
d
e
f
g
h
i
j
k
l
m
n
o
p
q
r
s
t
u
v
w
x
y
z

字符

下面的代码展示了几个颇具异域风情的字符:

fn main() {
let c = 'z';
let z = 'ℤ';
let g = '国';
let heart_eyed_cat = '';
}

在 Rust 语言中这些都是字符,Rust 的字符不仅仅是 ASCII,所有的 Unicode 值都可以作为 Rust 字符,包括单个的中文、日文、韩文、emoji 表情符号等等,都是合法的字符类型。Unicode 值的范围从 U+0000 ~ U+D7FF 和 U+E000 ~ U+10FFFF

由于 Unicode 都是 4 个字节编码,因此字符类型也是占用 4 个字节:

fn main() {
let x = '中';
println!("字符'中'占用了{}字节的内存大小",std::mem::size_of_val(&x));
}

输出如下:

$ cargo run
Compiling ... 字符'中'占用了4字节的内存大小

和一些语言不同,Rust 的字符只能用 '' 来表示, "" 是留给字符串的

布尔

Rust 中的布尔类型有两个可能的值:true 和 false,布尔值占用内存的大小为 1 个字节:

fn main() {
let t = true; let f: bool = false; // 使用类型标注,显式指定f的类型 if f {
println!("这是段毫无意义的代码");
}
}

使用布尔类型的场景主要在于流程控制,例如上述代码的中的 if 就是其中之一。

复合类型

元组(tuple)用一对 ( ) 包括的一组数据,可以包含不同种类的数据:

let tup: (i32, f64, u8) = (500, 6.4, 1);
// tup.0 等于 500
// tup.1 等于 6.4
// tup.2 等于 1
let (x, y, z) = tup;
// y 等于 6.4

数组用一对 [ ] 包括的同类型数据。

let a = [1, 2, 3, 4, 5];
// a 是一个长度为 5 的整型数组 let b = ["January", "February", "March"];
// b 是一个长度为 3 的字符串数组 let c: [i32; 5] = [1, 2, 3, 4, 5];
// c 是一个长度为 5 的 i32 数组 let d = [3; 5];
// 等同于 let d = [3, 3, 3, 3, 3]; let first = a[0];
let second = a[1];
// 数组访问 a[0] = 123; // 错误:数组 a 不可变
let mut a = [1, 2, 3];
a[0] = 4; // 正确

Rust基本数据类型的更多相关文章

  1. Rust学习笔记一 数据类型

    写在前面 我也不是什么特别厉害的大牛,学历也很低,只是对一些新语言比较感兴趣,接触过的语言不算多也不算少,大部分也都浅尝辄止,所以理解上可能会有一些偏差. 自学了Java.Kotlin.Python. ...

  2. Rust之路(2)——数据类型 上篇

    [未经书面同意,严禁转载] -- 2020-10-13 -- Rust是系统编程语言.什么意思呢?其主要领域是编写贴近操作系统的软件,文件操作.办公工具.网络系统,日常用的各种客户端.浏览器.记事本. ...

  3. Rust 入门 (三)_上

    这部分我们学习 rust 语言的 变量.数据类型.函数.注释.流程控制 这五个方面的内容.本文我们介绍前两个内容,下一篇文章介绍后三个内容. 变量 默认情况下,rust 语言中声明的变量是不可变的,比 ...

  4. Rust之旅 02.通过例子学习自定义类型

    本期文章接上期继续讲述Rust语言中的数据类型,Rust自定义数据类型主要是通过下面这两个关键字来创建: 结构体( struct ): 定义一个结构体(structure) 枚举( enum ): 定 ...

  5. FinClip小程序+Rust(三):一个加密钱包

    ​ 一个加密货币钱包,主要依赖加密算法构建.这部分逻辑无关iOS还是Android,特别适合用Rust去实现.我们看看如何实现一个生成一个模拟钱包,准备供小程序开发采用 前言 在之前的内容我们介绍了整 ...

  6. 在 Pisa-Proxy 中,如何利用 Rust 实现 MySQL 代理

    一.前言 背景 在 Database Mesh 中,Pisanix 是一套以数据库为中心的治理框架,为用户提供了诸多治理能力,例如:数据库流量治理,SQL 防火墙,负载均衡和审计等.在 Pisanix ...

  7. 2.2 Rust 数据类型

    2.2 数据类型 let guess: u32 = "42".parse().expect("Not a number!"); Rust has four pr ...

  8. Rust <1>:数据类型、变量、可变性、常量、隐藏

    rust 是强类型语言,所有变量.常量都必须有明确的数据类型:很多情况下,省略类型声明,编译器可自动推导,但不是所有情况下都会成功. rust 有整型.浮点型.布尔型.字符型.数组.元组.枚举.结构体 ...

  9. Rust 数据类型

    Rust中的每个值都具有特定的数据类型. 基础类型: 整数,浮点数,布尔值和字符 i8,i16,i32,i64,i64,i128,isize, u8,u16,u32,u64,u64,u128,usiz ...

随机推荐

  1. NC212914 牛牛与后缀表达式

    NC212914 牛牛与后缀表达式 题目 题目描述 给定牛牛一个后缀表达式 \(s\) ,计算它的结果,例如,1+1对应的后缀表达式为1#1#+,'#'作为操作数的结束符号. 其中,表达式中只含有'+ ...

  2. NC17857 起床困难综合症

    NC17857 起床困难综合症 题目 题目描述 21 世纪,许多人得了一种奇怪的病:起床困难综合症,其临床表现为:起床难,起床后精神不佳.作为一名青春阳光好少年,atm 一直坚持与起床困难综合症作斗争 ...

  3. SpringCloud微服务实战——搭建企业级开发框架(四十三):多租户可配置的电子邮件发送系统设计与实现

      在日常生活中,邮件已经被聊天软件.短信等更便捷的信息传送方式代替.但在日常工作中,我们的重要的信息通知等非常有必要去归档追溯,那么邮件就是不可或缺的信息传送渠道.对于我们工作中经常用到的系统,里面 ...

  4. Overfitting & Train Set & Test Set

    假设数据集是独立同分布的,可以将数据集划分为不同的比例:Train Set and Test Set. 同时在Train Set and Test Set上做精度测试,或者隔一段时间在Test Set ...

  5. 『现学现忘』Git后悔药 — 31、reset版本回退命令总结

    目录 1.--soft回退说明 2.--mixed回退说明 3.--hard回退说明 4.总结 在Git中进行版本回退需要使用git reset命令. 以前面文章中的示例为例,当我准备在V4版本,回退 ...

  6. HashSet集合介绍和哈希值

    HashSet集合介绍 ~java.util.Set接口 extends Collection 接口~Set接口的特点: 1.不允许存储重复的元素 2.没有索引,没有带索引的方法,也不能使用普通的fo ...

  7. MISC 2022/4/21 刷题记录-千字文

    1.千字文 得到名为png的无类型文件,010 Editor查看,png,改后缀,得到二维码 QR扫描,得到一句话"这里只有二维码" 思路不对,binwalk一下,发现有错误信息 ...

  8. Java基础语法02

    回顾前面的章节,我们学习了(1.注释,2.标识符和关键字,3.数据类型)今天让我们继续加油. 四.变量,常量,作用域1.变量是什么:存数的(可以变化的量) Java是一种强类型语言,每个变量都必须声明 ...

  9. nodejs学习总结01

    主流渲染引擎介绍1.渲染引擎又叫 排版引擎 或 浏览器内核 .(双内核:执行html和css的)2,主流的渲染引擎有**Chrome浏览器**:Blink引壁(WebKit的一个分支)**Safari ...

  10. 说起分布式自增ID只知道UUID?SnowFlake(雪花)算法了解一下(Python3.0实现)

    原文转载自「刘悦的技术博客」https://v3u.cn/a_id_155 但凡说起分布式系统,我们肯定会对一些海量级的业务进行分拆,比如:用户表,订单表.因为数据量巨大一张表完全无法支撑,就会对其进 ...