基础数据类型

在 Rust 中,每一个值都有一个特定数据类型(data type),这告诉 Rust 它被指定为何种数据,以便明确数据处理方式。我们将看到两类数据类型子集:标量(scalar)和复合(compound)。

Rust 是静态类型(statically typed)语言,也就是说在编译时就必须知道所有变量的类型。根据值及其使用方式,编译器通常可以推断出我们想要用的类型。

标量类型

标量(scalar)类型代表一个单独的值。Rust 有四种基本的标量类型:整型、浮点型、布尔类型和字符类型。你可能在其他语言中见过它们。让我们深入了解它们在 Rust 中是如何工作的。

Rust 支持常见的基本类型:

  • 整型: i8, i16, i32, i64, i128, isize
  • 无符号整型: u8, u16, u32, u64, u128, usize
  • 浮点数: f32, f64
  • 布尔值: bool
  • 字符: char

整型

整型 是一个没有小数部分的数字。我们在Getting Started 部分已经使用过 u64 整数类型。该类型声明表明,它关联的值应该是一个占据 64 比特位的无符号整数(有符号整数类型以 i 开头而不是 u)。表格 1-1 展示了 Rust 内建的整数类型。我们可以使用其中的任一个来声明一个整数值的类型。

Rust 支持多种整数类型: i8, i16, i32, i64, i128, isize 和 u8, u16, u32, u64, u128, usize。 其中 isize 和 usize 是指针大小的整数类型。

表格 1-1 Rust 中的整型:

长度有符号无符号
8-biti8u8
16-biti16u16
32-biti32u32
64-biti64u64
128-biti128u128
架构相关isizeusize

每一个整型变量都可以是有符号或无符号的,并有一个明确的大小。有符号 和 无符号 代表数字能否为负值,也就是说,这个数字是否有可能是负数(有符号数),或者永远为正而不需要符号(无符号数)。这有点像在纸上书写数字:当需要考虑符号的时候,数字以加号或减号作为前缀;然而,可以安全地假设为正数时,加号前缀通常省略。有符号数以二进制补码形式(two’s complement representation) 存储。

另外,isize 和 usize 类型依赖运行程序的计算机架构:64 位架构上它们是 64 位的,32 位架构上它们是 32 位的。

浮点数

Rust 也有两个原生的浮点数(floating-point numbers)类型,它们是带小数点的数字。Rust 的浮点数类型是 f32 和 f64,分别占 32 位和 64 位。默认类型是 f64,因为在现代 CPU 中,它与 f32 速度几乎一样,不过精度更高。所有的浮点型都是有符号的。

Rust 中的所有数字类型都支持基本数学运算:加法、减法、乘法、除法和取余。整数除法会向零舍入到最接近的整数。下面的代码展示了如何在 let 语句中使用各种数值运算:

#![allow(unused)]
fn main() {
/**
* 数字计算方法。
* 加、减、乘、除、余
*/
pub(crate) fn number_calc() {
    // 加
    let sum = 5 + 11;

    let difference = 45.5 - 4.3;

    // 乘
    let product = 4 * 30;
  
    // 除
    let quotient = 86.7 / 32.2;
   
    // 求余
    let remainder = 63 % 5;

    println!(
        "sum: {}, diff: {}, product: {}, quotient: {}, remainder:{}",
        sum, difference, product, quotient, remainder
    );
}

// 测试运行
 number_calc();

///
/// 单元测试
/// #[cfg(test)]
///
#[cfg(test)]
mod tests {
    // 注意这个惯用法:在 tests 模块中,从外部作用域导入所有名字。
    use super::*;

    #[test]
    fn test_number_calc() {
        let sum = 5 + 10;
        // 加法测试。注意这个断言会导致测试失败。
        assert_eq!(sum, 15);

         number_calc();
    }
}

}

布尔类型

Rust 中的布尔类型有两个可能的值:true 和 false。Rust 中的布尔类型使用 bool 表示。例如:

fn main() {
     let t = true;

    let f: bool = false; // with explicit type annotation
}

使用布尔值的主要场景是条件表达式,例如 if 表达式。

字符类型

Rust 的 char 类型是语言中最原始的字母类型。下面是一些声明 char 值的例子:

fn main() {
    let c = 'z';
    let z: char = 'ℤ'; // with explicit type annotation
    let heart_eyed_cat = '😻';
    let ok = '好';
}

注意,我们用单引号声明 char 字面值,而与之相反的是,使用双引号声明字符串字面值。Rust 的 char 类型的大小为四个字节 (four bytes),并代表了一个 Unicode 标量值(Unicode Scalar Value),这意味着它可以比 ASCII 表示更多内容。在 Rust 中,带变音符号的字母(Accented letters),中文、日文、韩文等字符,emoji(绘文字)以及零长度的空白字符都是有效的 char 值。Unicode 标量值包含从 U+0000 到 U+D7FF 和 U+E000 到 U+10FFFF 在内的值。不过,“字符” 并不是一个 Unicode 中的概念,所以人直觉上的 “字符” 可能与 Rust 中的 char 并不符合。

Note

什么是数据类型?

在计算机编程语言中,数据类型(Data Type)是用来定义变量或数据的属性,指定其存储方式、取值范围以及可以执行的操作。数据类型为编译器或解释器提供了如何处理数据的指令,包括:

  • 内存分配:决定变量占用多少内存(如 32 位整数占 4 字节)。
  • 操作支持:定义允许的操作(如整数支持加减,字符串支持拼接)。
  • 值范围:限定数据的有效范围(如 i32 的范围是 -2³¹ 到 2³¹-1)。
  • 类型安全:确保操作符合数据类型的规则,防止错误(如避免将字符串当作数字相加)。

数据类型是编程语言的基础,影响程序的性能、内存使用和代码的表达性及可读性。在像 Rust 这样的强类型语言中,数据类型在编译时严格检查,确保类型安全。数据类型的简要历史数据类型的发展与编程语言的演进密切相关:

  • 早期(1950s-1960s):在汇编语言和早期高级语言(如 Fortran、ALGOL)中,数据类型较为简单,主要包括整数、浮点数和字符。例如,Fortran 引入了 INTEGER 和 REAL 类型,用于数值计算。
  • 结构化编程(1970s):随着 Pascal 和 C 的出现,数据类型变得更加丰富,支持结构体(struct)和数组等复合类型,允许更复杂的数据组织。
  • 面向对象编程(1980s-1990s):Smalltalk 和 C++ 引入了类和对象作为数据类型,结合数据和行为。类型系统开始支持继承和多态。
  • 现代语言(2000s-至今):语言如 Java、Python 和 Rust 引入了更高级的类型系统,包括泛型、动态类型、枚举和模式匹配。Rust 等语言强调类型安全和内存安全,引入所有权和生命周期等概念。
  • 类型系统理论:类型理论(如 Hindley-Milner 类型推导)影响了现代语言(如 Haskell、TypeScript),支持更复杂的类型推导和静态检查。

数据类型的演进反映了编程语言对性能、灵活性和安全性的不断追求。数据类型的分类数据类型的分类方式因编程语言而异,但通常可以分为以下几大类:

  1. 按数据结构分类
  • 标量类型(Scalar Types):
    • 表示单一值。
    • 示例:
      • 整数(如 Rust 的 i8, i32, u64):存储整数值。
      • 浮点数(如 f32, f64):存储小数。
      • 布尔值(bool):表示 truefalse
      • 字符(char):存储单个 Unicode 字符(如 Rust 的 char)。
    • 常见于所有语言,用于基本计算。
  • 复合类型(Compound Types):
    • 由多个值组成,封装更复杂的数据结构。
    • 示例:
      • 数组(Array):固定长度的同类型元素集合(如 Rust 的 [i32; 5])。
      • 元组(Tuple):固定长度的异构类型集合(如 Rust 的 (i32, f64, char))。
      • 结构体(Struct):自定义类型,包含命名字段(如 Rust 的 struct)。
      • 枚举(Enum):表示一组可能的值(如 Rust 的 enum)。
      • 字符串:如 Rust 的 String&str,存储文本数据。
      • 集合类型:如向量(Rust 的 Vec<T>)、哈希表(HashMap)等。
    • 复合类型用于组织复杂数据,支持更高层次的抽象。
  1. 按类型检查分类
  • 静态类型(Static Typing):
    • 类型在编译时确定,变量的类型不可更改。
    • 示例:Rust、C、Java。
    • 优点:编译时捕获类型错误,性能更高。
  • 动态类型(Dynamic Typing):
    • 类型在运行时确定,变量类型可动态变化。
    • 示例:Python、JavaScript。
    • 优点:灵活性高,开发速度快。
  • 结构化类型 vs 名称类型:
    • 结构化类型(Structural Typing):类型兼容性基于结构(如 TypeScript 的鸭子类型)。
    • 名称类型(Nominal Typing):类型基于名称(如 Rust、Java)。
  1. 按内存分配分类
  • 基本/原生类型(Primitive Types):
    • 由语言直接支持,存储在栈上,固定大小。
    • 示例:Rust 的 i32, f64, bool, char
  • 引用/堆类型(Reference/Heap Types):
    • 数据存储在堆上,通过引用访问,动态分配内存。
    • 示例:Rust 的 String, Vec<T>, Box<T>
    • 通常涉及指针或引用,需考虑内存管理。
  1. 按功能分类
  • 数值类型:用于计算,如整数、浮点数。
  • 文本类型:如字符串(String, &str)或字符(char)。
  • 逻辑类型:如布尔值(bool)。
  • 集合类型:如数组、列表、字典。
  • 时间类型:如 Rust 标准库的 std::time::Durationchrono::DateTime,用于表示时间和日期。
  • 函数类型:在支持函数式编程的语言中,函数本身也是一种类型(如 Rust 的闭包类型 FnFnMut)。
  • 用户自定义类型:通过结构体、枚举或类定义的类型。
  1. 其他特殊分类
  • 泛型类型(Generic Types):允许类型参数化(如 Rust 的 Vec<T>)。
  • 空类型/可选类型:如 Rust 的 Option<T>,表示可能为空的值。
  • 错误类型:如 Rust 的 Result<T, E>,用于错误处理。
  • 动态类型(Any Types):如 Rust 的 Box<dyn Trait>,支持运行时多态。

复合类型

复合类型(Compound types)可以将多个值组合成一个类型。Rust 有两个原生的复合类型:元组(tuple)和数组(array)。

元组(tuple)

#![allow(unused)]
fn main() {
/**
 * tupl_sample
 */
pub(crate) fn tupl_sample() {
    println!("datatype tupl_sample .....start");

    let x: (i32, f64, u8) = (500, 6.4, 1);

    //使用dot(.) 获取元组数值,offset 从0开始。
    //获取元组第1个值
    let five_hundred = x.0;

    //获取元组 第2个值
    let six_point_four: f64 = x.1;
    //获取元组 第3个值
    let one: u8 = x.2;

    println!("tupl:({},{},{})", five_hundred, six_point_four, one);

    let s1 = String::from("hello");

    let (s2, len) = calc_length(s1);

    println!("The length of '{}' is {}.", s2, len);

    println!("datatype tupl_sample .....end\n");
}

/**
 * calc length
 */
fn calc_length(s: String) -> (String, usize) {
    let length = s.len(); // len() 返回字符串的长度

    (s, length)
}

}

数组(array)

  • 固定数组 array:

定义:使用方括号 [] 定义一个数组,每个元素之间用逗号分隔。

完整的array样例代码如下:

#![allow(unused)]
fn main() {
///
/// Array Sample
/// 数组
///
pub(crate) fn array_sample() {
    println!("datatype::array_sampe ...... start");

    //定义 12个月 数组
    let months = [
        "January",
        "February",
        "March",
        "April",
        "May",
        "June",
        "July",
        "August",
        "September",
        "October",
        "November",
        "December",
    ];

    //获取数组中元素
    println!("Months array first is {:?}", months[0]);
    println!("Months array second  is {:?}", months[1]);

    println!("Months array all is {:?}", months);

    // 编译器自动推导出one的类型
    let one = [1, 2, 3];

    // 显式类型标注
    let two: [u8; 3] = [1, 2, 3];
    let blank1 = [0; 3];
    let blank2: [u8; 3] = [0; 3];

    // arrays是一个二维数组,其中每一个元素都是一个数组,元素类型是[u8; 3]
    let arrays: [[u8; 3]; 4] = [one, two, blank1, blank2];

    // 借用arrays的元素用作循环中
    for a in &arrays {
        print!("{:?}: ", a);
        // 将a变成一个迭代器,用于循环
        // 你也可以直接用for n in a {}来进行循环
        for n in a.iter() {
            print!("\t{} + 10 = {}", n, n + 10);
        }

        let mut sum = 0;
        // 0..a.len,是一个 Rust 的语法糖,其实就等于一个数组,元素是从0,1,2一直增加到到a.len-1
        for i in 0..a.len() {
            sum += a[i];
        }
        println!("\t({:?} = {})", a, sum);
    }

    println!("array_sampe ...... end \n");
}


}
  • 变长数组 Vec:

定义一个变量并使用 vec! 宏来初始化它。vec! 宏会自动推断数组的类型。

或者创建一个空的数组: let mut empty_vec = Vec::new();

完整的Vec样例代码如下:

#![allow(unused)]
fn main() {
///
/// Vec Samle
/// Vector 为动态数组
///
pub(crate) fn vet_sample() {
    println!("vet_sample ......start");

    let v = vec![12, 34, 56, 78];

    let first = v.first();

    println!("ver fist is {:?}", first);

    //fist unwrap option is 12
    println!("ver fist is {}", first.unwrap());

    let mut sum = 0;
    //iter every item
    for n in v {
        println!("vet print item is {}", n);
        sum += n;
    }

    println!("vet all sum is {}", sum);

    //创建一个空的Vec
    let mut empty_vec = Vec::new();
    //push item to vec
    empty_vec.push(123);
    empty_vec.push(456);
    empty_vec.push(789);
    println!("empty vec is {:?}", empty_vec);

    //创建一个包含5个元素的Vec,每个元素都是0
    let zero_vec = vec![0; 5];
    println!("zero vec is {:?}", zero_vec);

    //创建一个包含5个元素的Vec,每个元素都是0
    let mut zero_vec = Vec::with_capacity(5);
    //push item to vec
    zero_vec.push(0);
    zero_vec.push(0);
    zero_vec.push(0);
    zero_vec.push(0);
    zero_vec.push(0);
    println!("zero vec is {:?}", zero_vec);

    println!("vet_sample ......end\n");
}

}

字符串

在 Rust 中,字符串类型是 str,它是不可变的字符串字面量切片,类型为 &str, 是在编译时就固定不可变的。 而 String 是一个字符串类型, 编译时动态分配, 可变长度。

日期时间

  • Rust 标准库时间

Rust 的标准库,std::time 库提供了基础的时间处理功能。

  • SystemTime 是一个表示时间的结构体,它是一个不可变的时间戳。表示一个时间点。 你可以使用 SystemTime 来获取当前时间点,并且可以使用 UNIX_EPOCH 来获取自Unix epoch (1970-01-01 00:00:00 UTC)以来的时间戳(Unixtime)。
  • Instant 是用于高精度单调时间测量(不考虑系统时钟调整),适合性能测试或计时。
  • Duration 是表示一个时间间隔,以秒和纳秒为单位。时间加减:可与 SystemTime 或 Instant 结合使用

以下是使用 SystemTimeInstant 的示例,你可以直接复制到你的 Rust 项目中直接运行,查看输出结果:

#![allow(unused)]
fn main() {
use std::time::{Duration, Instant, SystemTime};
use std::thread::sleep;

/// Sample function to demonstrate time operations.
pub fn time_sample() {
    //Instant
    let now: Instant = Instant::now();
    println!("Instant: {:?}!", now); // 输出当前时间戳

    let elapsed = now.elapsed();
    println!("Elapsed: {:.2?}", elapsed); // 输出经过的时间

    //Duration
    let timeout = Duration::from_secs(1);
    let start = Instant::now();

    sleep(Duration::from_millis(500));

    if start.elapsed() > timeout {
        println!("Timeout!");
    } else {
        println!("Operation completed within timeout.");
    }

    //SystemTime 获取当前时间戳
    let now = SystemTime::now();

    //fetch unixtime, return current time in seconds since Unix epoch.it since 1970-01-01 00:00:00 UTC was seconds ago
    let since_the_epoch = now.duration_since(SystemTime::UNIX_EPOCH).unwrap();

    //unixtime
    println!("Seconds since the epoch: {}s", since_the_epoch.as_secs());

    //当前线程休眠1000ms
    sleep(Duration::from_millis(1000));

    //返回now与当前时间的差值,单位为秒
    let elapsed = now.elapsed().unwrap();
    println!("Seconds since the elapsed: {}s", elapsed.as_secs()); //打印输出 1s
}

///to call run the time_sample function
time_sample();

// to unit test the time_sample function
#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_time_sample() {
        time_sample();
    }
}

}

tip

什么是Unix epoch?

Unix epoch 是一个固定的时间点,即 1970年1月1日 00:00:00 UTC。

什么是Unixtime?

Unix time 是 Unix epoch 的时间戳,(也称为 POSIX time 或 epoch time),它是一个自从 Unix epoch 开始经过的秒数,它不考虑闰秒,以简化和标准化时间计算。通常表示为一个整数。简单来说,Unix epoch 是基准点,Unix time 是从这个基准点开始的秒数计数。Unixtime 是一个非常常用的时间戳格式,在许多编程语言和系统中都广泛使用。

Unix Time 的作用和优点:

  • 简化时间存储和计算: Unix time 是一个简单的整数,非常适合在计算机内部存储和进行时间比较、计算等操作。这比处理复杂的日期、月份、年份、时区和闰年规则要简单得多。
  • 跨平台兼容性: 几乎所有主流的操作系统、编程语言和数据库都支持 Unix time,使其成为在不同系统之间传递时间信息时的通用标准。
  • 精确性和一致性: Unix time 避免了时区、夏令时等问题,确保了时间表示的精确性和一致性。它通常以 32 位或 64 位整数存储,能够表示非常长的时间范围。
  • Chrono 库日期时间

Rust 社区提供了 chrono 库来处理日期和时间。 你可以使用 chrono::prelude::NaiveDate 来表示日期和 chrono::prelude::NaiveDateTime 来表示日期时间,并且可以使用 chrono::Duration 来表示时间差。

  • 依赖配置

添加 chrono 库到你的项目中,你可以使用以下命令来安装:


cargo add chrono

或者修改 Cargo.toml 文件中的依赖项:

[dependencies]
chrono = { version = "0.4.39", features = ["serde"] }

  • 示例代码

如何获取当前日期和时间呢? 你可以使用 chrono::Local.now() 来获取当前的本地日期和时间。 你也可以使用 chrono::Utc.now() 来获取当前的 UTC 日期和时间。

获取本地当前日期和时间:


use chrono::prelude::*;
use chrono::{DateTime, Local};
fn main() {
    let now = Local.now();
    println!("Current date and time: {}", now);
}

获取 UTC当前日期和时间:


use chrono::{DateTime, Utc};
fn main() {
    let now = Utc.now();
    println!("Current date and time: {}", now);
}

一个更完成样例代码,展示了如何使用 chrono 库来获取当前日期和时间,并将其格式化为字符串:


fn date_sample() {
    // 使用 from_ymd_opt 创建 NaiveDate
    let date = NaiveDate::from_ymd_opt(2024, 10, 26).unwrap();
    println!("Date: {}", date);

    // 使用 from_hms_opt 创建 NaiveTime
    let time = NaiveTime::from_hms_opt(12, 30, 0).unwrap();
    println!("Time: {}", time);

    // 使用 new 创建 NaiveDateTime
    let datetime = NaiveDateTime::new(date, time);
    println!("DateTime: {}", datetime);

    // 使用 with_ymd_and_hms 创建 DateTime<Utc>
    let utc_datetime = Utc.with_ymd_and_hms(2024, 10, 26, 12, 30, 0).unwrap();
    println!("UTC DateTime: {}", utc_datetime);

    // 使用 with_ymd_and_hms 创建 DateTime<Local>
    let local_datetime = Local.with_ymd_and_hms(2024, 10, 26, 12, 30, 0).unwrap();
    println!("Local DateTime: {}", local_datetime);

    // 获取当前 UTC 时间
    let now_utc = Utc::now();
    println!("Now (UTC): {}", now_utc);

    // 获取当前本地时间
    let now_local = Local::now();
    println!("Now (Local): {}", now_local);

    //日期格式化
    let now = Utc::now();

    // 常用格式
    println!("ISO 8601 / RFC 3339: {}", now.to_rfc3339()); // 推荐的格式
    println!(
        "Year-Month-Day Hour:Minute:Second: {}",
        now.format("%Y-%m-%d %H:%M:%S")
    );
    println!(
        "Day/Month/Year Hour:Minute:Second: {}",
        now.format("%d/%m/%Y %H:%M:%S")
    );
    println!("Month Day, Year: {}", now.format("%B %d, %Y"));
    println!("Weekday, Day Month Year: {}", now.format("%A, %d %B %Y"));

    // 自定义格式
    println!("Custom format: {}", now.format("%a %b %e %T %Y"));

    // 时间戳 (Unix timestamp)
    println!("Timestamp (seconds): {}", now.timestamp());
    println!("Timestamp (milliseconds): {}", now.timestamp_millis());

    //日期解析
    let datetime_str = "2024-10-26 12:30:00";
    let datetime = NaiveDateTime::parse_from_str(datetime_str, "%Y-%m-%d %H:%M:%S").unwrap();
    println!("Parsed DateTime: {}", datetime);

    let date_str = "2024-10-26";
    let date = NaiveDate::parse_from_str(date_str, "%Y-%m-%d").unwrap();
    println!("Parsed Date: {}", date);

    let rfc3339_str = "2024-10-26T12:30:00Z";
    let rfc3339_datetime = DateTime::parse_from_rfc3339(rfc3339_str).unwrap();
    println!("Parsed RFC3339 DateTime: {}", rfc3339_datetime);

    //错误处理
    let invalid_date_str = "2024-13-26";
    let invalid_date = NaiveDate::parse_from_str(invalid_date_str, "%Y-%m-%d");
    match invalid_date {
        Ok(_) => println!("Parsed Date: {:?}", invalid_date),
        Err(e) => println!("Error parsing date: {}", e),
    }
}