it编程 > 编程语言 > rust

Rust的基础数据类型、变量系统、类型转换以及实战应用

9人参与 2026-01-31 rust

一、学习目标与重点

1.1 学习目标

  1. 掌握基础数据类型:理解rust所有标量类型(整数、浮点数、布尔值、字符)的定义、内存布局、范围限制与字面量写法
  2. 精通复合类型:熟练运用元组(tuple)、数组(array)、切片(slice)处理复杂数据结构,理解其固定/动态特性
  3. 理解变量系统:深入掌握变量的声明、可变性控制、作用域规则,以及shadowing(变量隐藏)机制的应用场景
  4. 熟练类型转换:熟悉rust严格的类型安全规则,掌握隐式转换(极少)与显式转换(as关键字、from/into traits)的方法
  5. 实战应用:能结合真实场景运用基础数据类型编写简单但实用的代码,解决常见问题

1.2 学习重点

💡 三大核心难点

  1. 整数类型的无符号/有符号区分溢出处理机制
  2. 切片(slice)的引用本质生命周期依赖(简单引入,后续章节深入)
  3. shadowing(变量隐藏)与mut(可变性)的本质区别

⚠️ 三大高频错误点

  1. 浮点数精度问题导致的逻辑错误
  2. 数组越界访问引发的编译/运行时崩溃
  3. 错误使用类型转换导致的未定义行为

二、rust的基础数据类型详解

rust的数据类型分为标量类型(单个值)和复合类型(多个值的组合),所有变量在编译期必须明确类型(强静态类型语言)。

2.1 标量类型

2.1.1 整数类型

rust提供了10种整数类型,分为无符号整数(以u开头,只能表示正数和0)有符号整数(以i开头,能表示正负整数),每种类型的位数从8位到128位不等。

📊 整数类型表

长度(位)无符号类型有符号类型最小值最大值
8u8i80255
16u16i16-3276832767
32u32i32-2³¹2³¹-1
64u64i64-2⁶³2⁶³-1
128u128i128-2¹²⁷2¹²⁷-1
平台相关usizeisize0取决于cpu架构(x86为2³²,x86_64为2⁶⁴)

⌨️ 整数字面量写法示例

// 十进制(默认)
let a = 10;         // i32(默认)
let b: u32 = 20;    // 显式类型注解

// 十六进制(0x开头)
let c = 0xff;       // i32,255
let d: u8 = 0x1a;   // 26

// 八进制(0o开头)
let e = 0o77;       // i32,63

// 二进制(0b开头)
let f = 0b1010;     // i32,10

// 字节字面量(u8,仅适用于ascii)
let g = b'a';       // u8,65

// 分隔符(_,增强可读性)
let h = 1_000_000;  // i32,1000000

⚠️ 整数溢出问题
rust在debug模式(默认)下会检查整数溢出,若发生溢出会直接崩溃(panic!);在release模式下会默认启用“两补数环绕”(wrapping)行为,但这是未定义行为(ub),建议显式处理溢出。

⌨️ 溢出处理方法示例

// 使用checked_*方法,溢出时返回none
let x: u8 = 255;
match x.checked_add(1) {
    some(y) => println!("255 + 1 = {}", y),
    none => println!("255 + 1 发生溢出"),  // 会执行这条
}

// 使用saturating_*方法,溢出时取最大值/最小值
let y: u8 = 255;
let z = y.saturating_add(1);  // z = 255

// 使用wrapping_*方法,强制两补数环绕
let w: u8 = 255;
let v = w.wrapping_add(1);    // v = 0

2.1.2 浮点数类型

rust提供了两种浮点数类型

⌨️ 浮点数字面量写法示例

// 十进制小数
let a = 3.14;         // f64(默认)
let b: f32 = 2.718;   // 显式类型注解

// 科学计数法
let c = 1e5;          // f64,100000.0
let d: f32 = 2.5e-3;  // 0.0025

⚠️ 浮点数精度问题
浮点数无法精确表示所有十进制小数,会导致逻辑错误。

⌨️ 精度问题示例

let x = 0.1 + 0.2;
println!("0.1 + 0.2 = {}", x);        // 输出0.30000000000000004
println!("x == 0.3? {}", x == 0.3);   // 输出false

// 解决方法:比较差值是否小于一个极小值(epsilon)
fn float_equals(a: f64, b: f64) -> bool {
    (a - b).abs() < 1e-9
}

println!("x == 0.3? {}", float_equals(x, 0.3));  // 输出true

2.1.3 布尔类型

rust的布尔类型只有bool一种,值只能是truefalse,占用1字节内存(确保内存对齐)。

⌨️ 布尔类型使用示例

let is_true = true;
let is_false: bool = false;

// 布尔类型常用于条件判断
if is_true {
    println!("这是真的");
} else {
    println!("这是假的");
}

// 布尔类型可以转换为整数
let true_as_u8 = is_true as u8;  // 1
let false_as_u8 = is_false as u8;  // 0

2.1.4 字符类型

rust的字符类型是char,占用4字节内存,支持unicode标量值(包括中文、日文、韩文、表情符号等,范围是u+0000到u+10ffff)。

⌨️ 字符类型使用示例

let a = 'a';         // 英文大写字母,ascii码65
let b: char = '中';  // 中文,unicode值u+4e2d
let c = '😀';        // 表情符号,unicode值u+1f600

// 字符类型可以转换为整数
let a_as_u32 = a as u32;  // 65
println!("'a'的unicode值是u+{:x}", a_as_u32);  // 输出u+41

2.2 复合类型

2.2.1 元组类型

元组是固定长度、异质数据类型的组合,长度在声明时必须明确,且后续无法修改。

⌨️ 元组声明与访问示例

// 声明元组
let t1: (i32, f64, bool) = (10, 3.14, true);
let t2 = ("hello", 'r', 2024);  // 编译器自动推断类型

// 访问元组元素
// 方法1:索引访问(从0开始)
println!("t1的第0个元素:{}", t1.0);  // 10
println!("t1的第1个元素:{}", t1.1);  // 3.14
println!("t1的第2个元素:{}", t1.2);  // true

// 方法2:解构赋值
let (x, y, z) = t2;
println!("x = {}, y = {}, z = {}", x, y, z);  // x = hello, y = r, z = 2024

// 单元素元组(必须加逗号)
let t3 = (5,);  // 类型是(i32,)
let t4 = (5);   // 这不是元组,而是整数5

// 空元组(单元类型,值只有一个())
let t5 = ();    // 类型是(),常用于表示无返回值的函数

⌨️ 元组作为函数返回值示例

// 计算矩形的面积和周长
fn calculate_rectangle(width: u32, height: u32) -> (u32, u32) {
    let area = width * height;
    let perimeter = (width + height) * 2;
    (area, perimeter)  // 返回元组
}

fn main() {
    let (area, perimeter) = calculate_rectangle(10, 5);
    println!("面积:{},周长:{}", area, perimeter);  // 面积:50,周长:30
}

2.2.2 数组类型

数组是固定长度、同质数据类型的组合,存储在栈内存上,长度在声明时必须明确,且后续无法修改。

⌨️ 数组声明与访问示例

// 声明数组
let a: [i32; 3] = [1, 2, 3];  // 类型注解:[元素类型; 长度]
let b = [10, 20, 30];         // 编译器自动推断类型
let c = [5; 4];               // 初始化:每个元素都是5,长度为4

// 访问数组元素
println!("a的第0个元素:{}", a[0]);  // 1
println!("a的第2个元素:{}", a[2]);  // 3
println!("c的第1个元素:{}", c[1]);  // 5

// 获取数组长度
println!("a的长度:{}", a.len());  // 3

// 数组遍历
for element in a.iter() {
    println!("{}", element);  // 输出1、2、3
}

// 可变数组
let mut d = [1, 2, 3];
d[0] = 10;  // 修改第0个元素
println!("d的第0个元素:{}", d[0]);  // 10

⚠️ 数组越界访问问题
rust在编译期无法检查所有越界访问,但在运行期会检查,若发生越界会直接崩溃(panic!)。

⌨️ 越界访问示例

let a = [1, 2, 3];
println!("a的第3个元素:{}", a[3]);  // 运行期崩溃:index out of bounds: the len is 3 but the index is 3

2.2.3 切片类型

切片是数组或vec(动态数组)的动态视图,存储在栈内存上,包含两个部分

  1. 指向数组/vec的指针(*const t 或 *mut t)
  2. 切片的长度(usize)

💡 核心特性

⌨️ 切片声明与访问示例

// 不可变数组切片
let a = [1, 2, 3, 4, 5];
let slice1 = &a[1..3];  // 从索引1到索引3(不包含3),元素是[2, 3]
let slice2 = &a[..2];   // 从开头到索引2(不包含2),元素是[1, 2]
let slice3 = &a[3..];   // 从索引3到结尾,元素是[4, 5]
let slice4 = &a[..];    // 整个数组,元素是[1,2,3,4,5]

// 访问切片元素
println!("slice1的第0个元素:{}", slice1[0]);  // 2
println!("slice1的长度:{}", slice1.len());    // 2

// 可变数组切片
let mut b = [1, 2, 3, 4, 5];
let mut_slice = &mut b[1..3];
mut_slice[0] = 20;  // 修改切片的第0个元素,也就是原数组的第1个元素
println!("原数组b:{:?}", b);  // 输出[1,20,3,4,5]

// vec的切片(与数组切片类似)
let mut vec = vec![10, 20, 30, 40, 50];
let vec_slice = &vec[2..4];
println!("vec切片:{:?}", vec_slice);  // [30,40]

2.3 字符串类型

rust的字符串类型分为两种,初学者容易混淆:

  1. &str:不可变字符串切片,存储在静态内存(如字符串字面量)或栈内存(指向其他字符串的切片)上,类型是&str
  2. string:可变性字符串,存储在堆内存上,类型是string

⌨️ 字符串类型使用示例

// 字符串字面量(&str)
let s1: &str = "hello, rust!";  // 存储在静态内存上
let s2 = "这是中文";             // 编译器自动推断类型为&str

// string类型的创建
let s3 = string::new();           // 创建空字符串
let s4 = string::from(s1);        // 从&str创建string
let s5 = string::from("动态字符串");  // 直接创建

// 字符串连接
let s6 = s4 + " " + s5.as_str();  // 注意:s4被转移所有权,s5需要.as_str()转换为&str
println!("s6:{}", s6);  // 输出hello, rust! 动态字符串

// 不可变字符串连接(保留所有变量的所有权)
let s7 = format!("{} {} {}", s1, s5, 2024);
println!("s7:{}", s7);  // 输出hello, rust! 动态字符串 2024

// 字符串切片(按字节索引,注意:rust的字符串是utf-8编码的,一个中文字符占3字节)
let s8 = "rust语言开发";
println!("s8的第0-3字节:{}", &s8[0..3]);  // rust
// println!("s8的第4-5字节:{}", &s8[4..6]);  // 编译期崩溃:byte index 4 is not a char boundary

// 获取字符串的字符迭代器(按字符索引)
for c in s8.chars() {
    println!("{}", c);  // 输出r、u、s、t、语、言、开、发
}

2.4 变量系统

2.4.1 变量声明

rust的变量声明必须使用let关键字,变量默认是不可变的(immutable)。

⌨️ 变量声明示例

// 简单声明(编译器自动推断类型)
let x = 10;
let y = "hello";

// 显式类型注解
let z: u8 = 255;
let w: string = string::from("rust");

2.4.2 变量的可变性

如果需要修改一个变量,必须在声明时添加mut关键字(mutable)。

⌨️ 可变变量示例

let mut x = 10;
x = 20;
println!("x:{}", x);  // 输出20

let mut s = string::from("hello");
s.push_str(", rust!");
println!("s:{}", s);  // 输出hello, rust!

2.4.3 变量的作用域

变量的作用域是从声明位置所在代码块({})的结束位置

⌨️ 变量作用域示例

fn main() {
    let x = 10;  // x的作用域开始
    {
        let y = 20;  // y的作用域开始
        println!("x + y = {}", x + y);  // 30,x可以访问,y可以访问
    }  // y的作用域结束
    // println!("y = {}", y);  // 编译错误:y未声明
    println!("x = {}", x);  // 10,x的作用域未结束
}  // x的作用域结束

2.4.4 shadowing(变量隐藏)

shadowing是指在同一作用域或嵌套作用域内,用相同的变量名声明一个新变量,新变量会隐藏旧变量。

⌨️ shadowing示例

// 同一作用域内的shadowing
let x = 10;
let x = x + 5;  // 新变量x隐藏旧变量x,类型仍然是i32,值是15
println!("x:{}", x);  // 15

// 嵌套作用域内的shadowing
let y = 20;
{
    let y = "hello";  // 新变量y隐藏旧变量y,类型是&str
    println!("内部y:{}", y);  // hello
}
println!("外部y:{}", y);  // 20,旧变量y重新可见

// shadowing与类型转换结合
let z = "123";
let z = z.parse::<i32>().unwrap();  // 新变量z隐藏旧变量z,类型是i32,值是123
println!("z:{}", z);  // 123

💡 shadowing vs mut

特性shadowingmut
变量名可以相同可以相同
变量类型可以不同必须相同
内存地址可能不同必须相同
作用域同一或嵌套作用域同一作用域
适用场景需要类型转换或重新计算需要修改值但类型不变

2.5 类型转换

rust是强静态类型语言,几乎不支持隐式类型转换,所有类型转换必须显式声明

2.5.1 as关键字转换

as关键字是rust中最常用的类型转换方法,适用于基础数据类型之间的转换。

⌨️ as关键字转换示例

// 整数类型之间的转换
let a: i32 = 100;
let b: u8 = a as u8;  // 100
let c: i8 = 255 as i8;  // -1(两补数环绕)

// 整数转浮点数
let d: i32 = 5;
let e: f64 = d as f64;  // 5.0

// 浮点数转整数(截断)
let f: f64 = 3.14;
let g: i32 = f as i32;  // 3

// 字符转整数
let h: char = 'a';
let i: u32 = h as u32;  // 65

⚠️ as关键字转换的限制

  1. 不能在非基础数据类型之间转换(如string和&str不能用as转换)
  2. 浮点数转整数会截断小数部分,可能导致精度损失
  3. 大整数转小整数会发生两补数环绕,可能导致未定义行为

2.5.2 from/into traits转换

from/into traits是rust中更安全、更通用的类型转换方法,适用于所有实现了这些traits的类型

⌨️ from/into traits转换示例

// string和&str之间的转换
let s1: &str = "hello";
let s2: string = string::from(s1);  // 使用from trait
let s3: string = s1.into();         // 使用into trait(自动实现)

// 整数类型之间的转换(需要实现from trait)
use std::convert::tryfrom;
use std::convert::tryinto;

let a: i32 = 100;
let b: result<u8, std::num::tryfrominterror> = u8::try_from(a);  // 安全转换,返回result
match b {
    ok(x) => println!("a转换为u8:{}", x),
    err(e) => println!("转换失败:{}", e),
}

let c: result<u8, std::num::tryfrominterror> = a.try_into();  // 使用tryinto trait(自动实现)

// 自定义类型的转换
struct person {
    name: string,
    age: u32,
}

struct personinfo {
    name: &'static str,
    age: u32,
}

impl from<personinfo> for person {
    fn from(info: personinfo) -> self {
        person {
            name: string::from(info.name),
            age: info.age,
        }
    }
}

let info = personinfo { name: "张三", age: 25 };
let person: person = info.into();  // 使用into trait(自动实现)
println!("姓名:{},年龄:{}", person.name, person.age);  // 姓名:张三,年龄:25

三、真实案例应用

3.1 案例1:计算多种几何图形的面积

💡 场景分析:需要编写一个函数,根据不同的几何图形(圆形、矩形、三角形)计算面积,输入参数类型不同,但返回值都是浮点数。

⌨️ 代码示例

// 定义几何图形的枚举类型
#[derive(debug)]
enum shape {
    circle(f64),          // 半径
    rectangle(f64, f64),  // 长和宽
    triangle(f64, f64),   // 底和高
}

// 计算面积的函数
impl shape {
    fn area(&self) -> f64 {
        match self {
            shape::circle(radius) => std::f64::consts::pi * radius * radius,
            shape::rectangle(width, height) => width * height,
            shape::triangle(base, height) => base * height / 2.0,
        }
    }
}

fn main() {
    // 创建不同的几何图形
    let circle = shape::circle(5.0);
    let rectangle = shape::rectangle(10.0, 5.0);
    let triangle = shape::triangle(6.0, 4.0);

    // 计算面积
    println!("圆形面积:{:.2}", circle.area());    // 78.54
    println!("矩形面积:{:.2}", rectangle.area());  // 50.00
    println!("三角形面积:{:.2}", triangle.area()); // 12.00
}

3.2 案例2:处理用户输入的成绩数据

💡 场景分析:需要编写一个程序,读取用户输入的多个学生成绩(整数),计算平均分、最高分、最低分,并输出成绩的分布情况。

⌨️ 代码示例

use std::io;

fn main() {
    // 存储成绩的数组(最多50个学生)
    let mut scores = [0; 50];
    let mut count = 0;

    println!("请输入学生成绩(输入-1结束):");

    loop {
        let mut input = string::new();
        io::stdin().read_line(&mut input).expect("读取输入失败");

        let score: i32 = match input.trim().parse() {
            ok(num) => num,
            err(_) => {
                println!("请输入有效的整数");
                continue;
            }
        };

        if score == -1 {
            break;
        }

        if score < 0 || score > 100 {
            println!("成绩必须在0-100之间");
            continue;
        }

        if count >= 50 {
            println!("最多只能输入50个成绩");
            break;
        }

        scores[count] = score;
        count += 1;
    }

    if count == 0 {
        println!("没有输入成绩");
        return;
    }

    // 计算平均分、最高分、最低分
    let sum: i32 = scores[0..count].iter().sum();
    let average = sum as f64 / count as f64;
    let max_score = scores[0..count].iter().max().unwrap();
    let min_score = scores[0..count].iter().min().unwrap();

    // 统计成绩分布
    let mut grade_counts = [0; 5];  // 0-59, 60-69, 70-79, 80-89, 90-100
    for &score in scores[0..count].iter() {
        match score {
            0..=59 => grade_counts[0] += 1,
            60..=69 => grade_counts[1] += 1,
            70..=79 => grade_counts[2] += 1,
            80..=89 => grade_counts[3] += 1,
            90..=100 => grade_counts[4] += 1,
            _ => (),
        }
    }

    // 输出结果
    println!("成绩统计结果:");
    println!("----------------------");
    println!("学生人数:{}", count);
    println!("平均分:{:.2}", average);
    println!("最高分:{}", max_score);
    println!("最低分:{}", min_score);
    println!("----------------------");
    println!("成绩分布:");
    println!("0-59分:{}人", grade_counts[0]);
    println!("60-69分:{}人", grade_counts[1]);
    println!("70-79分:{}人", grade_counts[2]);
    println!("80-89分:{}人", grade_counts[3]);
    println!("90-100分:{}人", grade_counts[4]);
}

3.3 案例3:解析csv格式的产品数据

💡 场景分析:需要编写一个程序,读取csv格式的产品数据(包含产品名称、价格、库存),解析并存储在数组中,然后根据价格范围筛选产品。

⌨️ 代码示例

// 产品结构体
#[derive(debug)]
struct product {
    name: string,
    price: f64,
    stock: u32,
}

// 解析csv行的函数
fn parse_product_csv(line: &str) -> option<product> {
    let fields: vec<&str> = line.split(',').collect();
    if fields.len() != 3 {
        return none;
    }

    let name = fields[0].trim().to_string();
    let price: f64 = match fields[1].trim().parse() {
        ok(num) => num,
        err(_) => return none,
    };
    let stock: u32 = match fields[2].trim().parse() {
        ok(num) => num,
        err(_) => return none,
    };

    some(product { name, price, stock })
}

fn main() {
    // 模拟csv数据
    let csv_data = "
        苹果, 5.99, 100
        香蕉, 2.49, 200
        橙子, 3.99, 150
        葡萄, 9.99, 50
        西瓜, 12.99, 30
        错误数据, abc, 10
    ";

    // 解析csv数据
    let mut products = vec::new();
    for line in csv_data.lines() {
        let trimmed_line = line.trim();
        if trimmed_line.is_empty() {
            continue;
        }

        match parse_product_csv(trimmed_line) {
            some(product) => products.push(product),
            none => println!("忽略无效行:{}", trimmed_line),
        }
    }

    // 筛选价格在5-10元之间的产品
    let filtered_products: vec<&product> = products
        .iter()
        .filter(|p| p.price >= 5.0 && p.price <= 10.0)
        .collect();

    // 输出结果
    println!("价格在5-10元之间的产品:");
    println!("----------------------------------");
    println!("产品名称\t价格\t库存");
    println!("----------------------------------");
    for product in filtered_products {
        println!("{}\t{:.2}\t{}", product.name, product.price, product.stock);
    }
}

四、常见问题与解决方案

4.1 整数溢出导致的崩溃

问题现象:在debug模式下,整数溢出会导致程序崩溃(panic!)。

解决方案

  1. 使用checked_*系列方法(溢出时返回none)
  2. 使用saturating_*系列方法(溢出时取最大值/最小值)
  3. 使用wrapping_*系列方法(强制两补数环绕)

4.2 浮点数精度导致的逻辑错误

问题现象:0.1+0.2的结果不等于0.3,比较浮点数相等时返回false。

解决方案:比较两个浮点数的差值是否小于一个极小值(epsilon),如1e-9。

4.3 数组越界访问导致的崩溃

问题现象:访问数组的索引超过其长度时,程序崩溃。

解决方案

  1. 在访问数组元素前,先检查索引是否在有效范围内
  2. 使用get()方法(返回option类型),避免崩溃

⌨️ get()方法示例

let a = [1, 2, 3];
if let some(element) = a.get(3) {
    println!("{}", element);
} else {
    println!("索引无效");
}

4.4 类型不匹配导致的编译错误

问题现象:函数接受的参数类型与传入的类型不一致,导致编译错误。

解决方案

  1. 显式类型转换(使用as关键字或from/into traits)
  2. 检查变量的类型注解是否正确
  3. 使用dbg!()宏打印变量类型(调试时)

⌨️ dbg!()宏示例

let x = 10;
dbg!(x);  // 输出[src/main.rs:2:5] x = 10
let y = x as u8;
dbg!(y);  // 输出[src/main.rs:4:5] y = 10

五、总结与展望

✅ 掌握了rust所有标量类型的定义、内存布局、范围限制与字面量写法
✅ 熟练运用了元组、数组、切片处理复杂数据结构,理解了其固定/动态特性
✅ 深入理解了变量的声明、可变性控制、作用域规则,以及shadowing(变量隐藏)机制的应用场景
✅ 熟练掌握了rust严格的类型转换方法,包括as关键字和from/into traits
✅ 结合真实场景编写了三个实用的代码案例,解决了常见问题

通过学习这些内容,我们将能够编写更复杂、逻辑更清晰的rust程序。

到此这篇关于rust的基础数据类型、变量系统、类型转换以及实战应用的文章就介绍到这了,更多相关rust的基础数据类型、变量系统、类型转换内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!

(0)

您想发表意见!!点此发布评论

推荐阅读

Rust 所有权(Ownership)的使用小结

01-14

Rust时间库Chrono最佳使用实践

01-13

浅谈Rust中错误处理与响应构建

01-08

从入门到精通详解Rust错误处理完全指南

12-29

Rust CLI 项目构建的实现步骤

12-28

nginx作为下载服务器配置过程

12-21

猜你喜欢

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论