基础数据类型

开篇故事

想象你在整理一个工具箱。你会把螺丝刀、锤子、尺子放在不同的格子里,因为每种工具用途不同。Rust 的数据类型就像这些格子——它们告诉编译器每个值应该如何存储和使用。


本章适合谁

如果你已经学完了变量与表达式,现在想了解 Rust 有哪些数据类型以及如何使用它们,本章适合你。数据类型是编程的基础,理解它们对编写正确的代码至关重要。


你会学到什么

完成本章后,你可以:

  1. 区分标量类型和复合类型
  2. 使用整数、浮点数、布尔值和字符类型
  3. 理解 String 和 &str 的区别
  4. 使用元组、数组、Vec 和 HashMap
  5. 进行类型转换和类型推断

前置要求


第一个例子

最简单的数据类型声明:

fn main() {
    // 整数类型
    let age: u8 = 25;
    
    // 浮点数类型
    let price: f64 = 19.99;
    
    // 布尔类型
    let is_active: bool = true;
    
    // 字符类型
    let initial: char = 'R';
    
    // 字符串类型
    let message: String = String::from("Hello, Rust!");
    
    println!("Age: {}, Price: {}, Active: {}, Initial: {}", 
             age, price, is_active, initial);
    println!("Message: {}", message);
}

完整示例: datatype_sample.rs


原理解析

标量类型 (Scalar Types)

Rust 有四种基本标量类型:

1. 整数类型

长度有符号无符号
8-biti8u8
16-biti16u16
32-biti32u32
64-biti64u64
128-biti128u128
架构相关isizeusize
#![allow(unused)]
fn main() {
let x: i32 = 42;      // 32 位有符号整数
let y: u64 = 1000;    // 64 位无符号整数
let z: isize = -5;    // 取决于架构
}

2. 浮点类型

#![allow(unused)]
fn main() {
let pi: f32 = 3.14;   // 32 位浮点数
let e: f64 = 2.718;   // 64 位浮点数 (默认)
}

3. 布尔类型

#![allow(unused)]
fn main() {
let is_true: bool = true;
let is_false: bool = false;
}

4. 字符类型

#![allow(unused)]
fn main() {
let letter: char = 'A';           // Unicode 字符
let emoji: char = '🦀';           // 支持 emoji
let chinese: char = '中';          // 支持中文
}

复合类型 (Compound Types)

1. 元组 (Tuple)

#![allow(unused)]
fn main() {
let tuple: (i32, f64, char) = (42, 3.14, 'A');

// 访问元组元素
let (x, y, z) = tuple;
println!("x: {}, y: {}, z: {}", x, y, z);

// 或使用索引
println!("First: {}", tuple.0);
}

2. 数组 (Array)

#![allow(unused)]
fn main() {
let array: [i32; 5] = [1, 2, 3, 4, 5];

// 访问数组元素
println!("First: {}", array[0]);
println!("Length: {}", array.len());

// 创建重复元素的数组
let repeated = [0; 5];  // [0, 0, 0, 0, 0]
}

3. 字符串 (String)

#![allow(unused)]
fn main() {
// String - 可增长的字符串
let mut greeting = String::from("Hello");
greeting.push_str(", world!");

// &str - 字符串切片
let slice: &str = &greeting[0..5];  // "Hello"
}

4. Vec (动态数组)

#![allow(unused)]
fn main() {
let mut numbers: Vec<i32> = Vec::new();
numbers.push(1);
numbers.push(2);
numbers.push(3);

// 或使用宏
let numbers = vec![1, 2, 3];
}

5. HashMap (哈希表)

#![allow(unused)]
fn main() {
use std::collections::HashMap;

let mut scores = HashMap::new();
scores.insert("Alice", 95);
scores.insert("Bob", 87);

// 访问
let alice_score = scores.get("Alice");
}

类型转换

1. 隐式转换 (类型推断)

#![allow(unused)]
fn main() {
let x = 42;  // 编译器推断为 i32
let y = 3.14; // 编译器推断为 f64
}

2. 显式转换

#![allow(unused)]
fn main() {
// 数值转换
let x: i32 = 42;
let y: f64 = x as f64;  // 42.0

// 字符串转换
let num_str = "42";
let num: i32 = num_str.parse().unwrap();
}

常见错误

错误 1: 整数溢出

let x: u8 = 255;
let y = x + 1;  // ❌ 编译错误:溢出

错误信息:

error: this arithmetic operation will overflow

修复方法:

#![allow(unused)]
fn main() {
// 使用更大的类型
let x: u16 = 255;
let y = x + 1;  // ✅ 256

// 或使用检查过的运算
let y = x.checked_add(1).unwrap();
}

错误 2: 类型不匹配

let x: i32 = 42;
let y: f64 = x;  // ❌ 编译错误:类型不匹配

修复方法:

let y: f64 = x as f64;  // ✅ 显式转换

错误 3: 数组越界

let array = [1, 2, 3];
let x = array[5];  // ❌ 运行时 panic

修复方法:

// 使用 get 方法安全访问
match array.get(5) {
    Some(value) => println!("{}", value),
    None => println!("Index out of bounds"),
}

动手练习

练习 1: 创建个人信息

创建一个程序,存储并打印个人信息:

fn main() {
    // TODO: 定义以下变量
    // - 姓名 (String)
    // - 年龄 (u8)
    // - 身高 (f32)
    // - 是否学生 (bool)
    
    // TODO: 打印所有信息
}
点击查看答案
fn main() {
    let name = String::from("张三");
    let age: u8 = 25;
    let height: f32 = 1.75;
    let is_student: bool = false;
    
    println!("姓名:{}, 年龄:{}, 身高:{}, 学生:{}", 
             name, age, height, is_student);
}

练习 2: 使用集合类型

创建一个程序,管理学生成绩:

use std::collections::HashMap;

fn main() {
    // TODO: 创建 HashMap 存储学生成绩
    // TODO: 添加 3 个学生
    // TODO: 计算平均分
}
点击查看答案
use std::collections::HashMap;

fn main() {
    let mut scores = HashMap::new();
    scores.insert("Alice", 95);
    scores.insert("Bob", 87);
    scores.insert("Charlie", 92);
    
    let total: i32 = scores.values().sum();
    let avg = total as f64 / scores.len() as f64;
    
    println!("平均分:{}", avg);
}

练习 3: 类型转换实践

fn main() {
    let num_str = "42";
    
    // TODO: 将字符串转换为 i32
    // TODO: 将 i32 转换为 f64
    // TODO: 打印结果
}
点击查看答案
fn main() {
    let num_str = "42";
    let num: i32 = num_str.parse().unwrap();
    let num_f64: f64 = num as f64;
    
    println!("i32: {}, f64: {}", num, num_f64);
}

故障排查 (FAQ)

Q: String 和 &str 有什么区别?

A:

  • String: 可增长、可变的字符串,存储在堆上
  • &str: 不可变的字符串切片,通常指向字符串字面量
#![allow(unused)]
fn main() {
let s1: String = String::from("hello");  // 可修改
let s2: &str = "world";                   // 不可修改
}

Q: 什么时候使用 Vec 而不是数组?

A:

  • 数组: 长度固定,类型 [T; N]
  • Vec: 长度可变,类型 Vec<T>
#![allow(unused)]
fn main() {
let array: [i32; 5] = [1, 2, 3, 4, 5];  // 固定长度
let vec: Vec<i32> = vec![1, 2, 3];       // 可变长度
}

Q: 如何选择整数类型?

A:

  • 一般情况:使用 i32 (最常用)
  • 需要大数:使用 i64i128
  • 索引/计数:使用 usize
  • 节省内存:使用 i8i16

知识扩展

日期与时间

Rust 标准库提供基础时间类型,第三方库 chrono 提供完整的日期时间处理:

1. 标准库时间类型

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

// Instant - 用于测量时间间隔(如性能测试)
let start = Instant::now();
// ... 执行一些代码 ...
let elapsed = start.elapsed();
println!("耗时:{:?}", elapsed);

// Duration - 时间间隔
let one_second = Duration::from_secs(1);
let one_millisecond = Duration::from_millis(1);

// SystemTime - 系统时钟(可获取当前时间)
let now = SystemTime::now();
let since_epoch = now.duration_since(SystemTime::UNIX_EPOCH).unwrap();
println!("Unix 时间戳:{} 秒", since_epoch.as_secs());
}

2. chrono 库日期时间

use chrono::{Utc, Local, DateTime, NaiveDateTime};

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

// 获取本地时间
let now_local: DateTime<Local> = Local::now();
println!("本地时间:{}", now_local);

// 创建指定时间
let specific = Utc.with_ymd_and_hms(2024, 10, 26, 12, 30, 0).unwrap();
println!("指定时间:{}", specific);

// Unix 时间戳转换
let timestamp = 1700000000;
let datetime = DateTime::from_timestamp(timestamp, 0).unwrap();
println!("时间戳 {} 对应:{}", timestamp, datetime);

// 格式化输出
println!("{}", now_utc.format("%Y-%m-%d %H:%M:%S"));
println!("{}", now_utc.format("%Y年%m月%d日"));

// 解析字符串
let parsed = "2024-10-26 12:30:00"
    .parse::<NaiveDateTime>()
    .unwrap();
println!("解析结果:{}", parsed);

3. 时间类型对比

类型用途是否含时区使用场景
Instant测量时间间隔性能测试、超时检测
SystemTime系统时钟文件时间戳、日志时间
DateTime<Utc>UTC 时间是 (UTC)服务器时间、数据库存储
DateTime<Local>本地时间是 (本地)用户界面显示
NaiveDateTime无时区时间内部计算、解析中间值

4. Unix 时间戳

Unix 时间戳是从 1970 年 1 月 1 日 00:00:00 UTC 开始经过的秒数:

use chrono::{DateTime, Utc};

// DateTime → Unix 时间戳
let now = Utc::now();
let timestamp = now.timestamp();
println!("Unix 时间戳:{}", timestamp);

// Unix 时间戳 → DateTime
let datetime = DateTime::from_timestamp(timestamp, 0).unwrap();
println!("对应时间:{}", datetime);

BigDecimal 高精度计算

use bigdecimal::BigDecimal;

let a = BigDecimal::from(10);
let b = BigDecimal::from(3);
let result = &a / &b;

println!("{}", result);  // 3.333...

chrono 日期时间

use chrono::{Utc, DateTime};

let now: DateTime<Utc> = Utc::now();
println!("当前时间:{}", now);

// 格式化
println!("{}", now.format("%Y-%m-%d %H:%M:%S"));

集合类型对比

类型特点使用场景
数组固定长度,栈上已知大小的数据
Vec可变长度,堆上动态数据集合
HashMap键值对,哈希快速查找
BTreeMap键值对,有序有序数据

小结

核心要点:

  1. 标量类型: i32, f64, bool, char - 单个值
  2. 复合类型: 元组、数组、Vec、HashMap - 多个值
  3. String vs &str: 可变 vs 不可变
  4. 类型转换: 使用 as 进行显式转换
  5. 集合选择: 根据需求选择合适的数据结构

关键术语:

  • Scalar Type (标量类型): 单个值的类型
  • Compound Type (复合类型): 多个值的类型
  • Type Inference (类型推断): 编译器自动推断类型
  • Type Conversion (类型转换): 显式或隐式的类型转换

术语表

English中文
Scalar Type标量类型
Compound Type复合类型
Type Inference类型推断
Type Conversion类型转换
String Slice字符串切片
Vector向量
Hash Map哈希表

延伸阅读

学习完数据类型后,你可能还想了解:

选择建议:

继续学习

前一章: 变量与表达式
下一章: 了解所有权

相关章节:

返回: 基础入门


完整示例: datatype_sample.rs


📚 扩展阅读

完整示例代码:

相关章节:

外部资源:


🎓 知识检查题库

问题 1 🟢 (基础)

以下哪个是复合类型?

A) i32
B) bool
C) Vec
D) char

点击查看答案

答案: C) Vec

解析: Vec 是动态数组,可以存储多个值,属于复合类型。i32、bool、char 都是标量类型。

问题 2 🟡 (中等)

以下代码会编译通过吗?

let mut names = Vec::new();
names.push("Alice");
names.push(42);
点击查看答案

答案: 不会编译通过

解析: Vec 是类型安全的,所有元素必须是同一类型。这里试图混合 String 和 i32。

修复:

#![allow(unused)]
fn main() {
let mut names: Vec<&str> = Vec::new();
names.push("Alice");
// names.push(42);  // 需要改为字符串
}

问题 3 🔴 (困难)

HashMap 的底层实现是什么?

A) 红黑树
B) 哈希表
C) 链表
D) 堆

点击查看答案

答案: B) 哈希表

解析: HashMap 使用哈希表实现,提供 O(1) 平均时间复杂度的插入和查找。BTreeMap 使用红黑树。


✅ 本章完成检查点

  • 理解标量类型和复合类型的区别
  • 能够使用 String 和 &str
  • 能够创建和使用 Vec
  • 能够使用 HashMap 存储键值对
  • 能够进行类型转换
  • 完成 3 个动手练习
  • 通过知识检查题库

完整示例: datatype_sample.rs