使用 let
声明变量, 默认不可变 (immutable
),使用 mut
声明可变变量:
let _immutable_binding = 1;
let mut mutable_binding = 1;
println!("Before mutation: {}", mutable_binding);
mutable_binding += 1;
println!("After mutation: {}", mutable_binding);
// Error! Cannot assign a new value to an immutable variable
// _immutable_binding += 1;
Rust 是强类型静态语言,各变量都需要有明确的类型,但一般不需要指定类型,编译器根据当前赋值或后续操作情况进行推导:
// 编译器根据表达式结果或者后续对 var 的使用方式进行推导。
let var = expression;
// 数值类型可以使用类型后缀
let elem = 5u8;
// Vec 是泛型类型,元素类型由 Rust 推导,等效于:`let mut vec: Vec<_> = Vec::new();`
let mut vec = Vec::new();
vec.push(elem);
println!("{:?}", vec);
变量必须先声明且被初始化后才能使用 #
声明和初始化可以分开进行:
let another_binding;
// 变量被声明后未初始化,使用时报错。
// println!("another binding: {}", another_binding);
// 变量被初始化后可以使用。
another_binding = 1;
println!("another binding: {}", another_binding);
// 先声明但未初始化。
let a_binding;
{
let x = 2; // 声明并初始化变量
a_binding = x * x; // 变量被首次初始化后,后续才能使用。
}
println!("a binding: {}", a_binding);
// 使用复杂的条件判断来初始化变量。
let name;
if user.has_nickname() {
name = user.nickname();
} else {
name = generate_unique_name();
user.register(&name);
} // 代码块后面的分号是可选的。
用表达式初始化变量 #
代码块的最后一条语句可以返回值, 用于初始化复杂的变量:
let msg = {
let dandelion_control = puffball.open();
dandelion_control.release_all_seeds(launch_codes);
// 表达式结尾没有分号,结果作为 block 的返回值。
dandelion_control.get_status()
};
// match/if/loop 等语句均有返回值,但各分支的返回值类型必须一致。
let display_name = match post.author() {
Some(author) => author.name(),
None => {
let network_info = post.get_network_metadata()?; // ? 会导致所在的函数返回
let ip = network_info.client_address();
ip.to_string()
}
};
// 如果 if 表达式结果没有用于赋值, 则 block 不能有返回值:
let suggested_pet = if with_wings { Pet::Buzzard } else { Pet::Hyena }; // OK
if preferences.changed() {
page.compute_size() // 错误:缺少了分号
}
// error[E0308]: mismatched types
// 22 | page.compute_size() // oops, missing semicolon
// | ^^^^^^^^^^^^^^^^^^^- help: try adding a semicolon:
// `;`
// ||
// | expected (), found tuple |
// = note: expected unit type `()`
// found tuple `(u32, u32)`
未使用的变量 #
变量需要被实际使用,否则编译器警告,解决办法:
- 在变量名前加
_
,表明该变量可能不被使用; - 添加
#![allow(unused)]
或#[allow(unused)]
属性宏;
// 忽略警告
let _unused_variable = 3u32;
变量如果被初始化赋值后,如果未被读取而再次被赋值,编译器会警告:
let mut name = "my name";
// 警告:值未被使用过
name = "your name";
println!("{}", name);
变量名 shadow #
变量名是块作用域(block scope
) ,可以被同 block 或子 block 的同名变量 shadow。
shadow 可以为同名变量指定不同的可变性和值类型。
被 shadow 的变量没有被 drop,shadow 结束后还可以使用:
// 未使用变量 shadow 时,需要定义临时变量。
for line_result in file.lines() {
let line = line_result?;
// ...
}
// 使用同名变量 shadow,减少一个变量定义。
for line in file.lines() {
let line = line?;
// ...
}
let x = 5;
let x = x + 1; // shadow 上一个同 block 级别的变量 x。
{
let x = x * 2; // shadow 上一级变量 x
println!("The value of x in the inner scope is: {}", x);
}
// shadow 不会 drop 对象, x 可以继续使用。
println!("The value of x is: {}", x);
let shadowed_binding = 1;
{
// shadow 的变量类型可以不同
let shadowed_binding = "abc";
}
println!("outside inner block: {}", shadowed_binding);
// 外层 shadow
let shadowed_binding = 2;
println!("shadowed in outer block: {}", shadowed_binding);
let mut _mutable_integer = 7i32;
{
// shadow 时去掉了 mut,变量不可变。
let _mutable_integer = _mutable_integer;
// 报错!
//_mutable_integer = 50;
}
// shadow 结束,变量恢复为前面外层定义的可变变量
_mutable_integer = 3;
类型转换 #
Rust 默认不对 变量做自动类型转换,但在变量赋值、函数传参等场景,可能会做隐式类型转换,称为 type coercion
。
在表达式中使用 as
运算符进行显式类型转换:
#![allow(overflowing_literals)]
fn main() {
let decimal = 65.4321_f32;
// 错误:除了 type coercion 外,不同类型变量值之间不能直接赋值。
// let integer: u8 = decimal;
// 显式类型转换
let integer = decimal as u8;
// integer 作为 unicode point,可以被转换为 char
let character = integer as char;
// 错误,浮点数不能被转换为 char
//let character = decimal as char;
// OK,u16 可以存下 1000
println!("1000 as a u16 is: {}", 1000 as u16);
// OK,向低类型转换时,超出的高字段被丢弃。
println!("1000 as a u8 is : {}", 1000 as u8);
// -1 + 256 = 255
// -1 的符号位对于 u8 来说是 +256;
println!(" -1 as a u8 is : {}", (-1i8) as u8);
println!("1000 mod 256 is : {}", 1000 % 256);
println!(" 128 as a i8 is : {}", 128 as i8); // -128
// 300.0 as u8 is 255
println!(" 300.0 as u8 is : {}", 300.0_f32 as u8);
// -100.0 as u8 is 0
println!("-100.0 as u8 is : {}", -100.0_f32 as u8);
// nan as u8 is 0
println!(" nan as u8 is : {}", f32::NAN as u8);
}
全局常量 #
Rust 不支持全局变量,所以 let
不能在全局使用。
可以使用 const
和 static
声明全局常量。
const 和 static 常量默认具有 'static lifetime
,即它们在程序运行过程中一直存在(即使 main
函数返回,它们还存在)。
全局常量名称需要使用全大写标识符,否则编译器警告:
const
:不可变值;static
:不可变(static
),或可变的值(static mut
),后者需要在unsafe
中读写;
两者的区别:
const 常量
:编译器不分配内存,故没有内存地址,编译时做inline
替换;static 常量
:有内存地址,支持借用操作;
const
是编译时全局常量,声明时必须指定值类型(编译器不会自动推导常量类型)和用常量表达式进行初始化:
// 全局常量:在所有 scope 外声明和初始化
// const/static 标识符需要全大写,声明类型的同时必须初始化。
const THRESHOLD: i32 = 10; // 全局常量
static LANGUAGE: &str = "Rust"; // 全局常量,默认为 'static lifetime, 等效为:static LANGUAGE: &'static str = "Rust";
static mut stat_mut = "abc"; // 全局 static 可变变量,需要在 unsafe 中读写
fn is_big(n: i32) -> bool {
n > THRESHOLD
}
fn main() {
let n = 16;
println!("This is {}", LANGUAGE);
println!("The threshold is {}", THRESHOLD);
println!("{} is {}", n, if is_big(n) { "big" } else { "small" });
// 错误:不可以修改 const 值。
// THRESHOLD = 5;
}
除了全局作用域外,也可以在函数内声明 const/static
变量。
函数内 static 变量 #
在函数内声明的 static
变量和 C static
变量类似:只初始化一次,在程序整个生命周期均有效。
use std::sync::OnceLock;
fn computation() -> u128 {
// 函数返回后,该变量还一直有效,下次再调用该函数式,下面的表达式不会被执行。
static COMPUTATION: OnceLock<u128> = OnceLock::new();
let v = COMPUTATION.get_or_init(|| {
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_micros()
});
*v
}
fn main() {
let v1 = computation();
std::thread::sleep(std::time::Duration::from_secs(2));
let v2 = computation();
println!("after: {} {}", v1, v2);
// 先后两次调用值相同,说明函数内的静态常量 COMPUTATION 在多次调用期间一直存在且只初始化了一次。
// after: 1754139639614887 1754139639614887
}
全局常量初始化 #
对 const/static
常量的初始化, 只能使用 const
类型函数,或编译时可以确定结果的常量表达式:
// static 常量不能被修改,也不能被 move:
static VEC: Vec<u32> = vec![];
fn move_vec(v: Vec<u32>) -> Vec<u32> {
v
}
// move_vec(VEC); // 错误
// 常量初始化表达式
const BIT1: u32 = 1 << 0;
const BIT2: u32 = 1 << 1;
const BITS: [u32; 2] = [BIT1, BIT2]; // 数组常量
const STRING: &'static str = "bitstring";
struct BitsNStrings<'a> {
mybits: [u32; 2],
mystring: &'a str,
}
const BITS_N_STRINGS: BitsNStrings<'static> = BitsNStrings {
mybits: BITS,
mystring: STRING,
};
为了克服该限制,可以使用 lazy_static crate提供的 lazy_static!{}
宏或者 Rust 1.80 提供的 LazyLock 类型
,它们都是在变量第一次被解引用时自动运行初始化语句(基于 Deref trait
实现),表达式值会被存储在常量中供后续使用:
use std::sync::Mutex;
lazy_static! {
// 任意初始化表达式,而不局限于 const 类型函数和值。
// static ref 的 ref 是必须的,表示 HOSTNAME 是一个引用,即将表达式的值绑定为一个引用而不是值本身。
// 该 HOSTNAME 引用类型是 'static 生命周期。
static ref HOSTNAME: Mutex<String> = Mutex::new(String::new());
}
具有内部可变性的全局常量 #
全局常量不可修改,所以一般使用内部可变性的 Mutex/AtomicXX/OnceCell/LazyLock
类型作为全局变量类型:
use std::sync::Mutex;
use std::sync::atomic::AtomicUsize;
// ok, 不可修改。
static MY_GLOBAL: Vec<usize> = Vec::new();
// ok, 可以修改
static PACKETS_SERVED: AtomicUsize = AtomicUsize::new(0);
// ok, 可以修改
static HOSTNAME: Mutex<String> = Mutex::new(String::new());
fn main() {
let mut name = HOSTNAME.lock().unwrap();
name.push_str("localhost");
println!("Results: {name}");
}
// use `once_cell` crate if you need lazy initialization of a constant
use once_cell::sync::OnceCell;
const HOME_DIR: OnceCell<String> = OnceCell::new();
// use .set to set the value (can only be done once)
HOME_DIR.set(std::env::var("HOME").expect("HOME not set"));
// use .get to retrieve the value
HOME_DIR.get().unwrap();
static mut #
static mut
是可以修改的全局变量,但需要在 unsafe block
中读写该变量。广泛应用于 C 库的 extern block 中:
extern "C" {
// C 库全局变量
static mut ERROR_MESSAGE: *mut std::os::raw::c_char;
}
Rust 1.85.0 开始, static mut
不支持 ref 和 mut ref,否则报错。