跳到主要内容

变量

变量声明

局部变量

局部变量声明一定是以关键字let开头,类型一定是跟在冒号:的后面。

let x = 5;       // 不可变变量
let mut y = 10; // 可变变量
  • let 关键字用于声明变量。
  • mut 关键字表示变量是可变的。

常量

const MAX_POINTS: u32 = 100_000;
  • 常量用 const 声明,必须指定类型,且在整个程序运行过程中保持不变。

静态变量

  • 使用static关键字来声明静态变量。
  • 静态变量在整个程序运行期间存在,拥有static生命周期(整个程序。
  • 静态变量的内存分配在编译时完成(不会在执行过程中收回。
  • 默认情况下,静态变量是不可变的;可以使用static mut声明可变静态变量,但需要使用unsafe代码块来访问和修改。
  • ·全局变量必须在声明的时候马上初始化;
  • 全局变量的初始化必须是编译期可确定的常量,不能包括执行期才能确定的表达式、语句和函数调用;

不可变静态变量

  • 声明和使用不可变静态变量

    static HELLO_WORLD: &str = "Hello, world!";

    fn main() {
    println!("{}", HELLO_WORLD);
    }

可变静态变量

  • 声明和使用可变静态变量

    static mut COUNTER: i32 = 0;

    fn add_to_counter(value: i32) {
    unsafe {
    COUNTER += value;
    }
    }

    fn main() {
    add_to_counter(3);
    unsafe {
    println!("COUNTER: {}", COUNTER);
    }
    }

线程安全的可变静态变量

  • 使用Mutex确保线程安全

    use std::sync::Mutex;

    static GLOBAL_COUNTER: Mutex<i32> = Mutex::new(0);

    fn main() {
    {
    let mut counter = GLOBAL_COUNTER.lock().unwrap();
    *counter += 1;
    }
    println!("GLOBAL_COUNTER: {}", GLOBAL_COUNTER.lock().unwrap());
    }

类型推导

  • Rust 可以自动推导变量的类型,不需要显式指定类型,除非编译器无法推导出。

类型推导示例

  • 简单变量的类型推导

    let x = 5; // x 被推导为 i32
    let y = 3.14; // y 被推导为 f64
    let is_active = true; // is_active 被推导为 bool
    let name = "Alice"; // name 被推导为 &str
  • 推导数组类型

    let numbers = [1, 2, 3, 4, 5]; // numbers 被推导为 [i32; 5]

显式指定类型的情况

  • 当类型不明确或无法推导时需要显式指定

    let guess: u32 = "42".parse().expect("Not a number!");
  • 使用泛型或复杂类型时

    let result: Result<i32, _> = "42".parse();

初始化绑定 Initializing bindings

  • 在声明变量时立即为其赋值。

  • 无构造函数,无初始值

    如果不赋值,Rust 是不会让我们使用一个没有经过初始化的值的。

举例:

fn main() {
let x: i32;
println!("The value of x is: {}", x);
}

构建后出现,error

use of possibly uninitialized variable: `x`

作用域 Scope

  • 变量在程序中有效的范围。
  • 通过花括号{}来定义作用域。
  • 变量超出作用域时会被自动销毁。

示例:

  • 变量的作用域

    fn main() {
    let x = 5; // x 的作用域开始
    {
    let y = 10; // y 的作用域开始
    println!("x: {}, y: {}", x, y);
    } // y 的作用域结束
    // println!("y: {}", y); // 编译错误:y 不在作用域内
    println!("x: {}", x);
    } // x 的作用域结束
  • 嵌套作用域

    fn main() {
    let x = 5;
    {
    let x = x * 2;
    println!("Inner scope x: {}", x); // 打印: Inner scope x: 10
    }
    println!("Outer scope x: {}", x); // 打印: Outer scope x: 5
    }

隐藏 shadowing

  • 在同一作用域或内嵌作用域中重新声明一个变量名,以隐藏前一个同名变量。
  • 隐藏允许改变变量的类型和可变性。

示例

  • 简单的隐藏

    fn main() {
    let x = 5;
    let x = x + 1; // 隐藏了之前的 x
    {
    let x = x * 2; // 在嵌套作用域中再次隐藏 x
    println!("Inner scope x: {}", x); // 打印: Inner scope x: 12
    }
    println!("Outer scope x: {}", x); // 打印: Outer scope x: 6
    }
  • 改变变量类型和可变性

    fn main() {
    let spaces = " ";
    let spaces = spaces.len(); // 隐藏并改变类型
    println!("spaces length: {}", spaces);

    let mut x = 5; // 可变变量
    x = 6;
    let x = x; // 隐藏并变为不可变变量
    // x = 7; // 编译错误:x 现在是不可变的
    }

数据类型

基本数据类型

bool

Rust 的布尔类型用于表示真或假。

  • 布尔值bool

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

char

Rust 的字符类型用于表示单个Unicode字符,用单引号包裹。

  • 字符char
let c: char = 'A';
let emoji = '😊';

不像其它语言,这意味着Rust的char并不是 1 个字节,而是 4 个。

整数类型

Rust 提供了多种整数类型,包括有符号和无符号整数。

  • 有符号整数i8, i16, i32, i64, i128, isize
    • 范围:i8(-128 到 127),i16(-32,768 到 32,767),i32(-2,147,483,648 到 2,147,483,647),i64i128具有更大的范围。
    • isize:平台相关大小的整数(32位或64位)。
  • 无符号整数u8, u16, u32, u64, u128, usize
    • 范围:u8(0 到 255),u16(0 到 65,535),u32(0 到 4,294,967,295),u64u128具有更大的范围。
    • usize:平台相关大小的无符号整数。

浮点类型

Rust 提供了单精度和双精度浮点类型。

  • 单精度浮点数f32
  • 双精度浮点数f64(默认)

复合类型

元组 Tuples

元组可以将多个不同类型的值组合在一起。元组的长度是固定的。

let tup: (i32, f64, u8) = (500, 6.4, 1);
let (x, y, z) = tup; // 解构元组
println!("x: {}, y: {}, z: {}", x, y, z);

可以通过索引访问元组中的值,下标是从0开始:

let a = tup.0;
let b = tup.1;
let c = tup.2;

数组

数组包含一组相同类型的值,长度是固定的。

let arr: [i32; 3] = [1, 2, 3];
let first = arr[0];
let second = arr[1];

你还可以用相同的值初始化数组:

let arr = [3; 5]; // 等同于 [3, 3, 3, 3, 3]

切片 Slices

  • 切片是对数组或字符串的一部分的引用 所以有&

  • 切片没有所有权,只是借用数组或字符串的一部分。

  • 使用范围语法[start..end]来创建切片。

  • 起始索引(start):包含在切片中,从0开始。

  • 结束索引(end):排他性的(exclusive),即不包括在切片中。

let arr = [1, 2, 3, 4, 5];
let slice = &arr[1..3]; // 包含索引 1 和 2 的元素,即 2 和 3
println!("slice: {:?}", slice); // 输出: slice: [2, 3]

省略索引:

  • 省略起始索引
let arr = [1, 2, 3, 4, 5];
let slice = &arr[..3]; // 等同于 &arr[0..3]
println!("slice: {:?}", slice); // 输出: slice: [1, 2, 3]
  • 省略结束索引
let arr = [1, 2, 3, 4, 5];
let slice = &arr[2..]; // 等同于 &arr[2..5]
println!("slice: {:?}", slice); // 输出: slice: [3, 4, 5]
  • 省略起始索引和结束索引
let arr = [1, 2, 3, 4, 5];
let slice = &arr[..]; // 等同于 &arr[0..5]
println!("slice: {:?}", slice); // 输出: slice: [1, 2, 3, 4, 5]
  • 字符串切片
let s = String::from("hello world");
let hello = &s[..5]; // 等同于 &s[0..5]
let world = &s[6..]; // 等同于 &s[6..11]
let whole = &s[..]; // 等同于 &s[0..11]
println!("hello: {}", hello); // 输出: hello: hello
println!("world: {}", world); // 输出: world: world
println!("whole: {}", whole); // 输出: whole: hello world

str

str 是字符串切片类型,通常以引用的形式使用,即 &str

  1. 字符串切片 (&str):不可变的字符串引用,通常用来引用字符串字面量或 String 类型的数据。
  2. 字符串 (String):可变的、拥有所有权的字符串类型,允许字符串的修改和动态增长。

字符串切片 (&str)

  • 定义字符串切片

    let greeting: &str = "Hello, world!";
    println!("{}", greeting); // 输出: Hello, world!

String 类型

  • 定义和修改 String

    let mut s = String::from("Hello");
    s.push_str(", world!"); // 添加一个字符串切片到 `String` 末尾
    println!("{}", s); // 输出: Hello, world!

转换

  • String&str

    let s = String::from("Hello");
    let slice: &str = &s; // `String` 转 `&str`
    println!("{}", slice); // 输出: Hello
  • &strString

    let slice: &str = "Hello, world!";
    let s = String::from(slice); // `&str` 转 `String`
    println!("{}", s); // 输出: Hello, world!

使用

  • 字符串切片

    let s = String::from("Hello, world!");
    let hello = &s[0..5]; // 切片,包含索引 0 到 4 的字符
    let world = &s[7..12]; // 切片,包含索引 7 到 11 的字符
    println!("hello: {}, world: {}", hello, world); // 输出: hello: Hello, world: world