当前位置: 首页 > news >正文

Rust:变量、常量与数据类型

Rust:变量、常量与数据类型

    • 变量绑定与可变性
      • 不可变性
      • 可变性
      • 变量遮蔽
    • 变量初始化机制
      • 延迟初始化
      • 编译时安全保证
    • 类型系统与类型标注
      • 类型推断
      • 显式类型标注语法
    • 常量与静态变量
      • 常量
      • 静态变量
    • 标量类型
      • 整数类型
      • 整数字面量与后缀
        • 前缀
        • 字节字面量
        • 类型后缀
      • 整数溢出
      • 浮点数类型
      • 布尔类型
      • 字符类型
    • 复合类型
      • 元组类型
      • 数组类型
    • 类型转换


变量绑定与可变性

在大部分编程语言中,我们说"声明变量",但在Rust中,我们更准确地说是变量绑定。这种说法更能体现Rust的核心思想:将值绑定到一个标识符上。

在Rust中,变量名本身并不拥有值,值可能在栈上也可能在堆上,let 做的是把这个名字与一个具体的值绑定在一起。之所以默认不可变,是为了让并发和优化更容易:当编译器知道某个绑定不会被改写时,就能更大胆地做优化,也能在并发场景下避免数据竞争。

let x = 5; // 将值5绑定到标识符x上
println!("The value of x is: {}", x);

不可变性

Rust中的变量默认是不可变的(immutable),这是Rust安全性和并发性的基础之一。

不可变并不是说值永远不能变化,而是说这个名字一旦绑定到某个值后,就不能再指向别的值。这样可以避免“谁在何时修改了状态”的隐蔽错误,阅读和调试也更直观。

let x = 5;
x = 6; // 错误:cannot assign twice to immutable variable

以上代码会报错,因为 x 默认是不可变的。


可变性

当确实需要修改变量的值时,可以使用mut关键字:

mut 放在变量名之前,表示允许对这个绑定指向的内存位置进行原地修改。可变会增加负担和并发风险,优先使用不可变,只有在确实需要原地更新(如计数器、缓冲区写入)时再使用可变。

let mut x = 5;
x = 6; // 现在可以修改了

mut关键字必须在变量绑定时就声明,不能后续添加


变量遮蔽

Rust允许用相同的名字声明新变量,新变量会遮蔽(shadow)之前的变量

遮蔽是重新绑定,不是修改原变量。它常用在更小的作用域里用计算后的新值而不污染外层作用域。与可变不同,遮蔽可以改变类型。

let x = 5;
let x = x + 1; // 遮蔽前一个x,创建新变量
println!("{}", x); // 6{let x = x * 2; // 在内部作用域中遮蔽println!("{}", x); // 12
}println!("{}", x); // 6,外层作用域的x
  1. 遮蔽:创建新变量,可以改变类型
  2. 可变性:修改同一变量的值,类型不能改变
// 遮蔽:可以改变类型
let spaces = "   "; // &str 类型
let spaces = 10; // usize 类型// 可变性:不能改变类型
let mut spaces = "   ";
spaces = 10; // 错误:类型不匹配

遮蔽 vs 可变性的区别

特性遮蔽(Shadowing)可变性(Mutability)
机制创建新变量修改同一变量
类型可以改变不能改变
内存新分配原地修改
作用域受作用域影响不受影响

变量初始化机制

初始化一个变量与大多数语言一致,在声明时通过赋值 = 给变量一个初始值。

let x = 1;

延迟初始化

Rust 还允许用户延迟初始化一个变量。延迟初始化常用于先声明、后在分支或计算结果确定后再赋值的场景。

let x;        // 只声明,不初始化
x = 42;       // 稍后初始化
println!("x = {}", x); // 合法
  • 即使变量是不可变的(没有mut),延迟初始化仍然合法
  • 变量的不可变性指的是初始化后不能重新赋值

简单区别一下三个概念:

概念说明可变变量不可变变量
声明告诉编译器变量存在
初始化第一次赋值
重赋值修改已初始化的值
// 不可变变量的延迟初始化
let x;        // 声明
x = 10;       // 初始化 
x = 20;    // 重赋值 ❌ 不可变变量不允许// 可变变量的延迟初始化  
let mut y;    // 声明
y = 10;       // 初始化 ✅
y = 20;       // 重赋值 ✅ 可变变量允许

编译时安全保证

Rust编译器确保变量在使用前必须被初始化:

let y;println!("y = {}", y); // 编译错误:use of possibly-uninitialized variabley = 1;println!("y = {}", y); // 初始化完后才能使用

此外,还要保证每一个分支都有初始化结果,并且各个分支的初始化类型一致:

let y;if some_condition {y = 10;
} else {y = 20;
}println!("y = {}", y); 

此处不论走哪一个 if 分支,都可以确保使用 y 之前被正确初始化。


类型系统与类型标注

Rust是静态类型语言,编译时必须知道所有变量的类型

静态类型不等于“处处手写类型”。Rust 借助局部类型推断让代码保持简洁,同时把类型错误尽早暴露到编译期。相比动态类型,静态类型能在大型代码库中降低运行时错误;相比一些拥有运行时反射的语言,Rust 的类型系统强调零成本抽象。

Rust提供了两种机制:

  1. 类型推断:编译器智能推断类型
  2. 显式标注:程序员明确指定类型

类型推断

Rust 的推断是局部的:不会跨函数、跨模块进行全局推断;当上下文不足时,必须显式标注类型或使用类型后缀。

编译器根据使用上下文推断类型:

let x = 5;          // 推断为 i32(整数默认类型)
let y = 3.14;       // 推断为 f64(浮点数默认类型)
let z = true;       // 推断为 bool
let s = "hello";    // 推断为 &str// 延迟初始化时的类型推断
let number;         // 类型未知
number = 42;        // 推断为 i32let data;           // 类型未知
data = String::from("hello"); // 推断为 String

这种推断语法,可以大部分类型简单的场景下简化编码,不用为每一个变量指明类型。


显式类型标注语法

let 变量名: 类型 =;

当编译器无法推断或存在歧义时,必须使用显式类型标注:

let x: i32 = 5;                    // 明确指定为 i32
let y: f32 = 3.14;                 // 明确指定为 f32

常量与静态变量

常量

常量在程序运行期间值永远不变,使用 const 声明:

const MAX_POINTS: u32 = 100_000;
const PI: f64 = 3.14159;
const MESSAGE: &str = "Hello";

常量必须进行类型标注,指明类型

let 的区别:常量必须是编译期可求值的表达式,通常会被内联到使用处,没有固定内存地址;可在任意作用域声明(包括全局),且总是不可变。

let mut num = 1;
const NUM: i32 = num; // 错误

比如以上代码就是错误的,因为常量 NUM 依赖了一个变量 num,变量在运行时才能确定值,导致 NUM 无法在编译期得到确定值,因此报错。


静态变量

静态变量具有'static生命周期,在程序整个运行期间有效:

static GLOBAL_COUNT: i32 = 0;
static mut COUNTER: i32 = 0;

static 具有固定内存地址,可通过引用取地址。static 也要求必须进行类型标注。


标量类型

整数类型

Rust提供了丰富的整数类型,每种都明确指定位数和符号性:

长度有符号无符号范围
8-biti8u8-128~127 / 0~255
16-biti16u16-32,768~32,767 / 0~65,535
32-biti32u32约±21亿 / 0~43亿
64-biti64u64约±922万万亿
128-biti128u128超大范围
archisizeusize取决于架构(32/64位)

对于 isizeusize,它们占用的比特位取决于系统架构,系统是多少位,就占用多少位。


整数字面量与后缀

前缀

Rust 允许通过前缀不同,来决定一个字面量的进制。

  • 无前缀:十进制(如 42
  • 0x:十六进制(如 0x2A == 42)
  • 0o:八进制(如 0o52 == 42)
  • 0b:二进制(如 0b101010 == 42)
  • 大小写均可:0xFF0xff 等价
// 进制表示
let decimal = 98;    // 十进制
let hex = 0xff;      // 十六进制
let octal = 0o77;    // 八进制  
let binary = 0b111;  // 二进制

下划线分隔符

对于一个数值,允许通过 _ 进行分割,仅用于提升可读性,没有数值语义影响。例如 1_000_0001000000 完全相同;二进制/十六进制中也可用来分组位。

字节字面量
let byte = b'A'; // 字节字面量(u8)

字节字面量 b'A':表示一个 u8(0~255)的字节值,必须是 ASCII 范围内的单字符。示例:b'A' == 65u8。非 ASCII 字符(如 b'中'b'国')是非法的。


类型后缀

对于整数,可以通过修改后缀来决定字面量的类型:

// 类型后缀
let typed = 123i64;            // i64 类型
let unsigned = 456u32;         // u32 类型
let long_num = 789i128;        // i128 类型

类型后缀把“字面量本身”的类型在语法层面固定为某个具体整型。它发生在编译期,只影响该字面量节点的静态类型。

默认:整数字面量默认 i32,除非通过上下文或后缀指定为其他类型。


整数溢出

Rust 的程序构建时,分为两种模式,debug调试构建 和 release发布构建。调

debug 模式下,会有更多的调试信息输出,一般用于开发环境。而 release 下会对代码进行更大幅度的优化,运行效率更高,一般用语正式发布环境。

当一个整数发生溢出的时候,在两个环境下效果也不同。

debug 模式下,如果整数溢出,此时会直接报错,在 Rust 中称为 panic。这会导致整个程序直接终止。

但是在 release 下,如果溢出会发生环绕。

比如:

let mut x: u8 = 255;
x += 1;
println!("x: {}", x);

这段代码在 debug 模式下直接报错退出,但是在 release 下输出 x: 0。因为 255 已经是 u8 的最大值了,当再加一个数,就会重新变回最小的值,这个过程称为环绕。

其实环绕大部分情况下是一个非常危险的操作,在开发的时候,Rust倾向于直接把这个行为作为一个错误报告给开发者,让开发者可以修改代码逻辑,或者更改更大的类型。

但是在实际发布环境,程序崩溃往往会给用户带来不好的体验,那么Rust就不再把它当做一个错误处理了。


浮点数类型

Rust有两种浮点数类型:f32(单精度)和f64(双精度),它们都遵从 IEEE-754 标准。

let x = 2.0;           // f64(默认,推荐)
let y: f32 = 3.0;      // f32(显式标注)// 科学记数法
let large = 1e6;       // 1,000,000.0 (f64)
let small = 1e-6;      // 0.000001 (f64)

一个浮点数类型的字面量默认为 f64,与现代CPU性能相近但精度更高。


布尔类型

布尔类型只表示两种真值,不与数字互转,这能防止很多隐式转换陷阱(例如把 0 误当做 false)。与控制流(if/while)结合时,编译器要求条件表达式必须是 bool,从而让代码更清晰、更安全。

let t = true;                  // bool类型
let f: bool = false;           // 显式标注// 布尔运算
let and_result = t && f;       // false
let or_result = t || f;        // true  
let not_result = !t;           // false

特点:占用1字节,只能是truefalse,不能与数字隐式转换。


字符类型

char 表示一个 Unicode 标量值,它可以存储英文字母,中文字符,拉丁文等等。Rust的char类型占4字节,使用单引号''来声明。

let c = 'z';                   // ASCII字符
let unicode = 'ℤ';             // Unicode字符
let chinese = '中';             // 中文

复合类型

元组类型

元组可以存储多个不同类型的值,长度固定,元组适合把少量相关但类型不同的数据打包传递(如函数返回多个值),使用小括号()进行定义。

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

一个元组的类型为 (type1, type2, ...),其类型也可以通过编译器自行推断,大部分时候不用手写类型标注。

访问元组的元素通过下标进行访问,从0开始:

let first = tup.0;
let second = tup.1;
let third = tup.2;

也可以通过解构赋值,把元组的元素直接赋值到变量上:

let a;
let b;
let c;
(a, b, c) = tup;let (x, y, z) = tup;

这样元组内的元素就会自动赋值到 abcxyz 上。

Rust 还允许空元组:

let unit: () = ();

空元组也称为单元类型,其类型为 () 值也为 (),可以理解为一个空值。如果一个函数没有返回值,默认就返回这个单元类型。


数组类型

数组用于存储相同类型的多个值,长度固定,适合在编译期已知大小的场景,使用方括号[]进行定义。

let arr: [i32; 5] = [1, 2, 3, 4, 5];

一个数组的类型写作 [T; N],其中 T 是元素类型,N 是长度。大多数情况下类型可由上下文推断,无需显式标注。

访问数组的元素通过下标进行访问,从0开始:

let first = arr[0];
let len = arr.len();

也可以通过解构赋值,把数组的元素直接赋值到变量上:

let [a, b, c, d, e] = arr;

Rust会在运行时检查数组边界,越界访问会panic


类型转换

Rust要求显式类型转换,不允许进行隐式转换,使用 as 关键字。

整数间转换

  • 扩大转换(如 i32 → i64):安全无数据丢失
  • 缩小转换(如 i32 → u8):高位截断(取模运算)
let num: i32 = 300;
let safe: i64 = num as i64;  // 安全扩大
let truncated: u8 = num as u8; // 44 (300 % 256)

浮点转整数

  • 直接丢弃小数部分(向零截断)
  • 浮点值超出目标整数范围时行为未定义
let pi = 3.99f32;
let int_pi = pi as i32; // 3

字符转整数

  • 转换为字符的Unicode码值
let star = '*';
let star_code = star as u32; // 42

布尔值转整数

  • true 转换为 1
  • false 转换为 0
let t = true as u8;  // 1
let f = false as i32; // 0

http://www.dtcms.com/a/349305.html

相关文章:

  • 2025 突出的时序模型
  • 【C语言强化训练16天】--从基础到进阶的蜕变之旅:Day13
  • Linux-Redis的安装
  • 第四章:并发编程的基石与高级模式之Select语句与多路复用
  • 【Linux】开发工具命令指南:深度解析Vim的使用操作
  • Allegro17.4导出带有NET的PDF文档及组装样式图
  • MongoDB vs MySQL:NoSQL 和 SQL 的核心区别与适用场景
  • 前端开发:详细介绍npm、pnpm和cnpm分别是什么,使用方法以及之间有哪些关系
  • CPTS-Pressed复现(XML-RPC)
  • Python 面向对象进阶:深入理解封装、继承与多态
  • 【C++】第二十六节—C++11(中) | 右值引用和移动语义(续集)+lambda
  • 验证码流程
  • 【AMBA总线互联IP】
  • 6、RocketMQ消息积压问题如何解决
  • QSpinBox的用法及其使用QSS对其美化
  • 【ElasticSearch】json查询语法和可用的客户端
  • Docker 在线安装 RabbitMQ
  • 开源 C++ QT Widget 开发(五)通讯--串口调试
  • NILMTK(非侵入式负载监测工具包)安装
  • Linux 进阶之性能调优,文件管理,网络安全
  • AI精准种植改写农业格局:亩产量提升18%+水资源利用率提高32%,破解小农户技术门槛难题
  • Linux下usb设备驱动涉及的结构体
  • More Effective C++ 条款06: 区分自增自减操作符的前缀和后缀形式
  • 04-ArkTS编程语言入门
  • 分享些 Function 和 枚举的经典使用案例
  • 【RAGFlow代码详解-1】概述
  • 青少年软件编程(python六级)等级考试试卷-客观题(2023年3月)
  • 同步阻塞和异步非阻塞是什么?
  • Web开发中的CGI:通用网关接口详解
  • 软件测试用例指南:覆盖 6 大设计方法