[Rust 入门]Rust 基本数据类型

本教程环境:

系统:MacOS

Rust 版本:1.77.2

Rust 的类型系统是它语言设计中最核心的部分之一。通过在编译时执行严格的类型检查来提供安全保障,帮助程序员避免常见的错误,例如空指针、解引用或类型不匹配等。以下是 Rust 类型系统的一些核心概念:

  • 强类型 意味着每个值都必须具有一个确切的类型,而且这个类型是在编译时已经确定的。如果代码试图对两个不同类型的值进行操作(除非明确使用了类型转换),编译器将报错。
  • 静态类型 类型检查在编译时进行,一切类型错误都会编译时捕。

同时,Rust 有以下两个特性可以让上述的这些工作变得轻松。

  • 类型推断
  • 泛型。

下表是 Rust 类型的总结。

类型 说明
i8i16i32i64i128u8u16u32u64u128 给定位宽的有符号整数和无符号整数 4-5i80x400u160o100i1620_922_789_888_000u64b'*'(u8 字节字面量)
isizeusize 与机器字(32位或64位)一样大的有符号整数和无符号整数
f32f64 单精度 IEEE 浮点数、双精度 IEEE 浮点数
bool 布尔值 truefalse
char Unicode 字符,32 位宽(4字节) 'x' '\\n'
(char, i8, bool) 元组,允许混合类型
() 单元元组、空元组
struct S { x: f32, y: f32 } 具名字段型结构体
struct T(i32, char) 元组结构体
struct E; 单元结构体
enum Attend { OnTime, Late(u32) } 枚举
Box<Attend> Box:指向堆中值的拥有型指针
&i32&mut i32 共享引用和可变引用;非拥有型指针,其声明周期不能超出引用目标
String UTF-8 字符串,动态分配大小
&str str 的引用:指向 UTF-8 文本的非拥有型指针
[f64; 8] 数组,固定长度,其元素类型都相同
Vec<f64> 向量,可变长度,其元素类型都相同 vec![1, 2, 3, 4]
&[u8]&mut [u8] 对切片(数组或向量的某一部分)的引用,包含指针和长度 &v[10..20]&mut a[..]
Result<u64, Error> 可能失败的操作结果:成功值 Ok(v);失败值 Err(e)
Option<&str> 可选值:没有值,取值为 None;有值,取值为 Some(v)
&dyn any&mut dyn Read 特型对象:对任何实现了一组给定方法的值的引用 value as &dyn Any&mut file as &mut dyn Read
fn(&str) -> bool 函数指针 str::is_empty
(闭包没有显式的书写形式) 闭包 &#124; a, b &#124; a * a + b * b

整型

无符号整型 - u8u16u32u64u128isizeu8 的取值范围 0 ~ 2^8 - 1;其他的采用相同的方法计算。 有符号整型 - i8i16i32i64i128usizei8 的取值范围 -2^7 ~ 2^7 -1; 其他的采用相同的方法计算。 isizeusize 依赖与计算机架构,64 位架构是 64 位,32 位架构是 32 位的。 要获取整型类型的取值范围可以使用标准库中的 MINMAX 常量。

rust 复制代码
// 返回 u8 类型的取值范围
println!("u8 range: {} ~ {}", u8::MIN, u8::MAX); 
// u8 range: 0 ~ 255

u8 常作为**字节值。**例如,从文件或网路中读取的二进制数据是 u8 构成的流。 Rust 中数组索引是 usize类型的值。表示数组或向量大小,或者某些数据结构中元素数量,通常也使用 usize

整型字面量

116i80xcafu320b0010_10100o106 都是整型字面量。 前缀 0x0o0b分别表示十六进制、八进制、二进制。

字节字面量

在 Rust 中,字节字面量 表示单个字节的值。可以用它们更加方便的表示 u8 类型的值。 字节字面量写作 b'x'x 可以是任何 ASCII 字符或转义字符。 例如,A 的 ASCII 码为 65,字面量 b'A'65u8 完全等效。

rust 复制代码
// 字节字面量
#[allow(non_snake_case)] // 去除警告
let A = b'A';
println!("A is {}", A); // A is 65

对于难以书写或阅读的字符,可将其编码改为十六进制。形如 b'\xHH',其中 HH 是任意两位十六进制数。可以将 ASCII 控制字符 escape 的字节字面量写成 b'\x1b',因为 escape 的 ASCII 码为 27,即十六进制的 1b

整型之间转换

在 Rust 中,整型之间不能进行隐式的转化,必须进行显式的转换

  1. 使用 as 进行转换。超出取值范围时,转换会导致数据丢失。
rust 复制代码
// 整型之间的转换
let a: i32 = 300;
let b: u8 = a as u8;
let c: i64 = a as i64;

println!("i32: {}", a); // 300
println!("u8: {}", b);  // 44 转换为 u8 类型,只有值在 u8 的范围内才安全
println!("i64: {}", c); // 300
  1. 使用 try_from()try_into() 方法进行尝试,转换失败时,返回错误。
    • try_from() 定义在 std::convert::TryFrom trait 中,尝试将一个类型转化为另一个类型。
    • try_into() 定义在 std::convert::TryInto trait 中,尝试将一个类型转化为另一个类型。
rust 复制代码
let d: i32 = 300;
// try_from
let f = u32::try_from(d).expect("数值超出了 u8 的范围");
println!("f: {}", f); // f: 300
let e = u8::try_from(d).expect("数值超出了 u8 的范围");
println!("e: {}", e); // 数值超出了 u8 的范围: TryFromIntError(())

// try_into 
let g: u32 = d.try_into().expect("数值超出了 u8 的范围");
println!("g: {}", g); // g: 300
let h = u8::try_from(d).expect("数值超出了 u8 的范围");
println!("h: {}", h); // 数值超出了 u8 的范围: TryFromIntError(())

整型的一些常用方法

标准库提供了一些计算方法:

rust 复制代码
assert_eq!(2_u16.pow(4), 16);            // 求幂
assert_eq!((-4_i32).abs(), 4);           // 求绝对值
assert_eq!(0b101101_u8.count_ones(), 4); // 求二进制1的个数"

整型溢出处理

在调试时,整型运算溢出时会出现 panic。 发布构建中,运算溢出默认会回绕。 如果这种默认行为不是你想要的,可以使用一些其他方法。 主要分为如下四大类。

检查运算

返回结果的 Option 值。方法调用添加 checked_ 前缀。

rust 复制代码
// 10与20之和可以表示为u8
assert_eq!(10_u8.checked_add(20), Some(30));

// 很遗憾,100与200之和不能表示为u8
assert_eq!(100_u8.checked_add(200), None);

// 做加法。如果溢出,则会出现panic
let sum = x.checked_add(y).unwrap();

回绕运算

默认行为。 调用的方法添加 wrapping_ 前缀。

饱和运算

饱和运算的 结果"紧贴着"该类型可表达的最大值和最小值 。调用的方法添加 saturating_ 前缀。

溢出运算

会返回一个元组 (result, overflowed),其中 result 是函数的回绕版本所返回的内容,而 overflowed 是一个布尔值,指示是否发生过溢出。调用的方法添加 overflowing_ 前缀。

浮点类型

Rust 提供了 IEEE 单精度浮点类型(f32)和双精度浮点类型(f64)。默认选择 f64f32 类型和 f64 类型具有一些特殊值的关联常量:正无穷大(INFINITY)、负无穷大(NEG_INFINITY)、非数值(NAN)、最小有限值(MIN)、最大有限值(MAX)。 std::f32::consts 模块和 std::f64::consts 模块提供了各种常用的数学常量,比如 EPI 和 2 的平方根。

类型转换

可使用 as 关键字转换。或 try_fromtry_intointo 等。

rust 复制代码
let f1: f64 = 1.03;
let f2: f32 = 1.03;
assert_eq!(f1 as f32, f2); // 通过
assert_eq!(f1, f2.into()); // assertion `left == right` failed

一些处理函数

  • round() - 四舍五入到最接近的整数;
  • ceil() - 将上取整,得到不小于浮点数的最小整数。
  • floor() - 将下取整,得到不大于浮点数的最大整数。
  • trunc() - 仅保留整数部分。
  • fract() - 仅保留小数部分。

布尔类型 - bool

Rust 的布尔类型(bool),具有两个值 truefalse。 Rust 中不能将其他类型隐式的转换为布尔类型。所以,条件或循环语句中的条件必须是 bool 表达式。 as 运算符可将布尔类型转换为整型;反之不行。

字符 - char

字符是 char 类型,会以 32 位值(4字节)表示单个 Unicode 字符 。使用单引号创建 char 字面量。

rust 复制代码
// char
#[allow(unused_variables)]
fn main() {
    let letter: char = 'a';   // 英文字母
    let number: char = '1';   // 数字字符
    let symbol: char = '$';   // 符号
    let space: char = ' ';    // 空格
    let emoji: char = '😂';  // 表情符号
    let chinese: char = '中'; // 中文字符

    // 一些 char 类型的操作
    println!("{} is alphabetic: {}", letter, letter.is_alphabetic()); // a is alphabetic: true
    println!("{} is digit: {}", number, number.is_digit(10));         // 1 is digit: true
    println!("{} to uppercase: {}", letter, letter.to_uppercase()); // a to uppercase: A
}

可以使用 aschar 转换为整型;如果转换的数值类型小于 32 位,高位会截断。 u8 是唯一能通过 as 运算符转换为 char 的类型。 标准库函数 std::char::from_u32 可以接受任何 u32 值并返回一个 Option<char>:如果此 u32 不是允许的 Unicode 码点,那么 from_u32 就会返回 None,否则,它会返回 Some(c),其中 c 是转换成 char 后的结果。

元组

元组是各种类型的值对,例如二元组、三元组等等。所以也叫做 n 元组。不同长度的元组类型不同。 元组的每个元素类型可不同。只允许通过常量下标来获取 ,例如元组 a,要获取第三个元素需要通过 a.2 获取。 元组通常用来从一个函数返回多个值。 例如,字符串切片的 split_at 方法将字符串分成两半,并返回一个元组。可以使用模式匹配的方法将返回值的每个元素赋值给不同的变量。

rust 复制代码
// 元组
#[allow(unused_variables)]
fn main() {
    let temp = (); // 零元组
    let one = (100, );
    let two = (100, 200);
    println!("{}", two.0); // 100

    let text = "Hello World";
    // 元组解构
    let (hello, world) = text.split_at(5);
    println!("hello: {}, world: {}", hello, world); // hello: Hello, world:  World
}

元组类型的零元组是 ()。不返回值的函数的返回类型是 ()如果元组只有一个元素,那么必须在后面添加逗号,例如 (100, )

指针类型

Rust 有多种表示内存地址的类型。

引用

&String 是对 String 值的引用。&i32 是对 i32 值的引用。以此类推。 表达式 &x 会生成一个对 x 的引用。给定一个引用 r,表达式 *r 会引用 r 指向的值。 Rust 的引用有两种形式:

  • &T 不可变共享引用。只读。可以同时拥有多个给定值的共享引用。
  • &mut T 可变的独占引用。可以读取和修改它指向的值。如果该引用存在,就不能对该值有任何其他类型的引用。
rust 复制代码
// 引用
fn main() {
    let mut a = 10;
    let b = &a;
    println!("b: {}", b); // 10
    println!("a: {}", a); // 10

    let c = &mut a;
    *c = 11; 
    println!("c: {}", a); // 11
}

智能指针

智能指针是一个数据结构,它除了提供对数据的访问之外,还包含了额外的元数据和功能。在 Rust 中,智能指针通常通过实现 DerefDrop trait 来提供这些额外的能力。智能指针的核心特性是它能自动管理内存,确保代码安全和高效地运行。 Rust 标准库中几种常用的智能指针如下。

  • Box<T> 一个在堆上分配内存的智能指针。它拥有它指向的数据,在离开作用域时自动清理。
  • Rc<T> 一个引用计数的智能指针,允许数据有多个所有者。它主要用于单线程场景下的数据共享。
  • Arc<T>Rc<T> 的线程安全版本,适用于多线程场景。
  • Cell<T>RefCell<T> 提供内部可变性,即使在拥有不可变引用的情况下也可以改变所包含的值。

裸指针

  • 不可变原始指针 (*const T): 可以多次复制,并且可以并发读取指向的数据,但无法保证指向的内存是有效的。
  • 可变原始指针 (*mut T): 它提供了一个可改变的内存地址,不过使用这种类型需要特别注意,因为存在潜在的数据竞争问题。

原始指针不在 Rust 的安全抽象之内,使用它们需要 unsafe 代码块,因为编译器无法保证其安全性。它们通常用于与 C 代码的交互,或者执行一些底层的内存操作。

数组、向量、切片

数组

数组的类型为 [T; N],每个值都是 T 类型,数组的长度是 N。数组是编译期确定的常量。数组的长度是类型的一部分。数组不能追加新元素或缩小。 编写数组的方法:

rust 复制代码
let a: [i32; 3] = [1, 2, 3];
let b = ["a", "b", "c"];
a[0] // 1
a.len() // 3

数组本身没有提供诸如遍历、搜索、排序等方法,这些都是切片提供的方法。 但是在写的时候,数组可以调用这些方法,是因为 Rust 会隐式的创建一个引用整个数组的切片,然后使用这个切片进行这些操作。例如:

rust 复制代码
let mut chaos = [3, 5, 4, 1, 2];
chaos.sort();
assert_eq!(chaos, [1, 2, 3, 4, 5]);

向量

Vec<T> 类型可称为 T 的向量。是一个动态分配且可增长的 T 类型的值序列。向量的元素存在于堆中,所以可以随意调整向量的大小。 向量有一下几个关键信息:

  • 有一个指向堆上数组开始位置的指针;
  • 向量的长度,即当前向量持有的元素数量;
  • 向量的容量,即堆上分配的内存能容纳的元素数量。

当堆上的缓冲区达到其最大容量时,往向量中添加另一个元素需要分配一个更大的缓冲区,将当前内容复制到其中,更新向量的指针和容量以指向新缓冲区,最后释放旧缓冲区。 创建向量方式很多。最简单使用 vec! 宏来创建向量。

rust 复制代码
let v = vec![2, 3, 5, 7];
v.push(8); // 添加元素

let repeat_v = vec![0; 4] // [0, 0, 0, 0]  

let vec_v = Vec::new(); // 创建空向量。
vec_v.push('a');

// 从迭代器生成的值构建一个向量
let v: Vec<i32> = (0..5).collect(); // 使用 collect 时候,通常要指定类型

和数组一样,可以对向量使用切片的方法。

rust 复制代码
let mut v = vec![1, 2, 3, 4];
v.reverse();

如果事先知道向量所需的元素数量,可使用 Vec::with_capacity 来创建。这样创建的缓冲区足够大,一开始就可容纳所有元素。vec! 宏就使用了这个技巧。 len() 方法返回向量包含的元素数。 capacity() 方法返回不重新分配下的课容纳的元素数量。 可以在向量的任意位置删除或插入元素。这个操作会影响之后的元素,它们需要向前或向后移动。所以,如果向量越长,操作越慢。

rust 复制代码
let mut v = vec!(1, 2, 3, 5, 6);
v.insert(3, 4); // 插入元素
v.remove(1); // 删除索引为1的元素
v.pop(); // 移除最后一个元素并返回

for item in v {
    println!("{}", item);
}

切片

切片([T]),是数组或向量中的一个区域。 类型 &[T]&mut [T] 可称为 T 的共享切片T 的可变切片 。切片是对数组或向量的一部分值的引用。 对切片的引用是一个胖指针:一个双字值,包括指向切片第一个元素的指针和切片中元素的数量。

rust 复制代码
let v: Vec<f64> = vec![0.0, 0.707, 1.0, 0.707];
let a: [f64; 4] = [0.0, -0.707, -1.0, -0.707];

let sv: &[f64] = &v;
let sa: &[f64] = &a;

普通引用是指向单个值的非拥有型指针 。对切片的引用是指向内存中一系列连续值的非拥有型指针。 如果要对数组或向量进行操作,使用切片是不错的选择。

rust 复制代码
fn print(n: &[f64]) {
	for elt in n {
        println!("{}", elt);
    }
}

可以使用范围值对数组或向量进行索引,来获取一个切片的引用。

rust 复制代码
print(&v[0..2]);
print(&v[2..]);
print(&sv[1..3]);

字符串

str

在 Rust 中 str 是不定长字符串切片类型,它是一种不拥有所有权的类型。不能直接声明。因为 Rust 在编译期间需要确定每个类型的长度。 所以一般使用的是字符串切片的引用 &str。它是不可变的。

字符串字面量

用双引号包裹,如果里面有 " 用反斜杠转译。它的类型就是 &str

rust 复制代码
let speech = "\"Ouch!\" said the well.";

Rust 提供了**原始字符串。**用小写字母 r 进行标记。原始字符串中所有反斜杠和空白字符都会逐字包含在字符串中。

rust 复制代码
let default_win_install_path = r"C:\Parogram Files\Gorillas";

字节串

带有 b 前缀的字符串字面量都是**字节串。**这样的字符串是 u8 值(字节)的切片而不是 Unicode 文本。

rust 复制代码
let method = b"GET";

method 的类型是 &[u8; 3],它是对 3 字节数组的引用。 原始字节串 要以 br" 开头。

String

在 Rust 中,String 类型是一个标准库提供的动态、可增长、可变的、拥有所有权的 UTF-8 编码的字符串类型。它是用来存储和操作可变长的文本数据的。 String 类似 Vec<T>。它会在堆上分配自己的缓冲区。 String 创建的几个方法:

  • .to_string()&str 转换为 String。会进行复制。
  • format!() 宏会返回一个新的 String
  • 字符串的数组、切片和向量都有 concat()join() 方法,能从多个字符串中形成一个新的 String
rust 复制代码
let mut s = String::new(); // 空的 String
let hello = String::from("Hello, world!"); // 从字符串字面量创建 String

let mut s = String::from("foo");
s.push_str("bar"); // 追加字符串切片
s.push('!'); // 追加单个字符
println!("{}", s); // 输出:"foobar!"

let s = String::from("example");
let slice: &str = &s; // 借用 String 为 &str

let slice = "example";
let s = slice.to_string(); // 或 String::from(slice)

其他类似字符串的类型

  • 对于 Unicode 文本,坚持使用 String&str
  • 使用文件名时,改用 std::path::PathBuf&Path
  • 当处理根本不是 UTF-8 编码的二进制数据时,请使用 Vec<u8>&[u8]
  • 当使用操作系统提供的原生形式的环境变量名和命令行参数时,使用 OsString&OsStr
  • 当和使用 null 结尾字符串的 C 语言库进行互操作时,请使用 std::ffi::CString&CStr

类型别名

使用 type 关键字来为现有类型声明一个新名称:

rust 复制代码
type Bytes = Vec<u8>;

本教程代码仓库:github.com/zcfsmile/Ru...

参考链接:

🌟🌟 🙏🙏感谢您的阅读,如果对你有帮助,欢迎关注、点赞 🌟🌟

相关推荐
幸运小圣20 小时前
Vue3 -- 项目配置之stylelint【企业级项目配置保姆级教程3】
开发语言·后端·rust
老猿讲编程1 天前
Rust编写的贪吃蛇小游戏源代码解读
开发语言·后端·rust
yezipi耶不耶1 天前
Rust 所有权机制
开发语言·后端·rust
喜欢打篮球的普通人1 天前
rust并发
rust
大鲤余1 天前
Rust开发一个命令行工具(一,简单版持续更新)
开发语言·后端·rust
梦想画家1 天前
快速学习Serde包实现rust对象序列化
开发语言·rust·序列化
数据智能老司机1 天前
Rust原子和锁——Rust 并发基础
性能优化·rust·编程语言
喜欢打篮球的普通人2 天前
Rust面向对象特性
开发语言·windows·rust
上趣工作室2 天前
uniapp中使用全局样式文件引入的三种方式
开发语言·rust·uni-app
许野平2 天前
Rust:GUI 开源框架
开发语言·后端·rust·gui