跳过正文

2. 变量和常量:variable/const/static

·
Rust
rust-lang - 这篇文章属于一个选集。
§ 2: 本文

使用 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 不支持全局变量,所以 let 不能在全局使用,但是可以使用 conststatic 声明全局常量,通过使用内部可变性类型,也可以实现全局变量。

Rust 是强类型静态语言,各变量都需要有明确的类型。但一般不需要指定类型,编译器根据当前赋值或后续操作情况进行自动类型推导:

// 编译器根据 expresion 结果或者后续对 var 的使用方式进行推导。
let var = expression;

fn main() {
    // 数值类型可以使用类型后缀
    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);

代码块(Block)可以返回值, 用于初始化复杂的变量:

// 使用复杂的条件判断来初始化变量。
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()  // oops, missing semicolon
}
// 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)`

Rust 变量都需要被实际使用,否则编译器警告。

解决办法:

  1. 在变量名前加 _ 来表明该变量可能不被使用;
  2. 添加 #![allow(unused)]#[allow(unused)] 属性宏;
let an_integer = 1u32;
let a_boolean = true;
let unit = ();

println!("A boolean: {:?}", a_boolean);
println!("Meet the unit value: {:?}", unit);

// 忽略警告
let _unused_variable = 3u32;

变量如果被初始化赋值后,如果未被读取而再次被赋值,编译器会警告:

let mut name = "my name"; // 警告值未被使用过
name = "your name";
println!("{}", name);

变量名是块作用域(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;
{
    let shadowed_binding = "abc"; // shadow 的变量类型可以不同
}
println!("outside inner block: {}", shadowed_binding);

let shadowed_binding = 2;
println!("shadowed in outer block: {}", shadowed_binding);

let mut _mutable_integer = 7i32;
{
    let _mutable_integer = _mutable_integer; // shadow 时去掉了 mut,变量不可变
    _mutable_integer = 50; // 报错!
}
_mutable_integer = 3; // shadow 结束,变量恢复为前面外层定义的可变变量

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);
   // 向低类型转换时,超出的高字段被丢弃。
    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 定义全局变量,但支持使用 conststatic 定义的全局常量。

全局常量名称需要使用全大写标识符,否则编译器警告:

  1. const :不可变值;
  2. static :不可变或可变的值( static mut ),需要在 unsafe 中读写 static mut 类型值;

const 和 static 常量默认具有 'static lifetime ,即它们在程序运行过程中一直存在。

const 是编译时全局常量,声明时必须指定值类型,编译器不会自动推导常量类型,同时用常量表达式进行初始化:

// 字面量常量初始化
const THING: u32 = 0xABAD1DEA;

// const 借用默认是 'static
const WORDS: &'static str = "hello rust!";
// 等效于
const WORDS: &str = "hello rust!";

const 和 static 常量的区别:

  1. const 常量 :编译器不分配内存,故没有内存地址,编译时做 inline 替换;
  2. static 常量 :有内存地址,故支持借用操作,而且 main 函数返回时并不会 drop static 常量值;
// 全局常量:在所有 scope 外声明和初始化
const THRESHOLD: i32 = 10;      // 全局常量
static LANGUAGE: &str = "Rust"; // 全局常量,默认为 'static lifetime
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 常量和 C 的 static 变量类似,在程序整个生命周期均有效(main 返回时还有效)。

fn computation() -> &'static DeepThought {
    static COMPUTATION: OnceLock<DeepThought> = OnceLock::new();
    COMPUTATION.get_or_init(|| DeepThought::new())
}

对 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 提供的 lazy_lock 宏,它们都是在变量第一次被解引用时自动运行初始化语句(基于 Deref trait 实现),表达式值会被存储在常量中供后续使用:

use std::sync::Mutex;

lazy_static! {
    // 任意初始化表达式,而不局限于 const 类型函数和值。
    static ref HOSTNAME: Mutex<String> = Mutex::new(String::new());
}

常量不可修改,所以一般使用内部可变性的 Mutex/AtomicXX/OnceCell 类型作为全局变量类型:

use std::sync::Mutex;
use std::sync::atomic::AtomicUsize;

static MY_GLOBAL: Vec<usize> = Vec::new(); // ok, 不可修改。
static PACKETS_SERVED: AtomicUsize = AtomicUsize::new(0); // ok, 可以修改
static HOSTNAME: Mutex<String> = Mutex::new(String::new()); // ok, 可以修改

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 是可以修改的全局变量,但需要在 unsafe block 中修改它,广泛应用于 C 库的 extern block 中:

extern "C" {
    // C 库全局变量
    static mut ERROR_MESSAGE: *mut std::os::raw::c_char;
}
rust-lang - 这篇文章属于一个选集。
§ 2: 本文

相关文章

1. 标识符和注释:identify/comment
·
Rust
Rust 标识符介绍
10. 泛型和特性:generic/trait
·
Rust
Rust 泛型和特性
11. 类型协变:type coercion
·
Rust
Rust 高级话题:子类型和类型协变
12. 迭代器:iterator
·
Rust
Rust 迭代器