rust基础


cargo管理项目

cargo new <项目名> 会创建一个新示范项目文件夹
cargo init 在当前文件夹作为项目根目录,初始化一个项目
cargo run 编译并运行
cargo build 只编译
cargo build --release 编译优化版本
cargo check 快速检查(不生成二进制,比 build 快)
cargo test 运行测试
cargo clippy 代码风格和常见错误检查器
cargo fmt 自动格式化代码

添加依赖:cargo add <包名>
https://crates.io/
vs code左下角会有一个"RUST DEPENDENCES“的菜单可以看到依赖

单独编译

单独编译某个文件 rustc test.rs

动态类型&静态类型

rs
// 声明一个静态,所有的静态都会被编译器写死并占用内存
let mut s = "hello"; 
// s.push_str(" world");

let mut s = String::from("hello"); // 抄到堆里,变成笔记本
s.push_str(" world"); // ✅ 成功!现在 s 变成了 "hello world"

变量

  • const 声明常量
    • 常量在任何作用域都能声明,包括函数外的全局作用域。
    • 命名约定:全大写 + 下划线分隔。必须显式标注类型。
    • 常量的值必须是【编译期】就能确定的常量表达式。
    • 必须标注类型。
rs
const MAX_POINTS: u32 = 100_000; // 数字里的下划线只是为了方便阅读,不影响值
  • let声明变量
    • 只能出现在函数体、代码块、闭包等运行时作用域内。
    • 用 let 声明的变量默认是【不可变的】。
    • 如果确实需要修改变量,在 let 后面加 mut 关键字。
    • 必须先初始化才能使用,可以延迟赋值,但是使用前必须赋值。
rs
fn main() {
    let x = 5; // 不可变变量
    println!("x 的值是: {}", x);
    let mut y = 10; //可变变量
    println!("y 的初始值: {}", y);
    y = 20; // OK,因为 y 是可变的
    println!("y 修改后的值: {}", y);
    // 必须先初始化才能使用
    let late_init;
    late_init = 42; // 延迟初始化是允许的
    println!("延迟初始化的值: {}", late_init);
}
  • 变量遮蔽(shadowing)
    • 你可以用同一个名字声明一个新变量,新变量会"遮蔽"旧变量。
    • 变量遮蔽是使用let新创建一个变量,不是重新赋值。
    • 蔽可以发生在内层作用域,内层结束后外层变量恢复。
rs
fn main() {
    let z = 5;
    let z = z + 1; // 新的 z,值为 6,遮蔽了旧的 z
    let z = z * 2; // 又一个新的 z,值为 12
    println!("经过两次遮蔽后 z = {}", z);
	
	// 遮蔽的强大之处:可以【改变类型】,而 mut 做不到。
    let spaces = "   ";        // spaces 是 &str(字符串)
    let spaces = spaces.len(); // 新的 spaces 是 usize(数字)
    println!("空格数量: {}", spaces);

	// 遮蔽可以发生在内层作用域,内层结束后外层变量恢复。
    let value = 100;
    {
        let value = value * 10; // 内层作用域的新 value = 1000
        println!("内层作用域 value = {}", value);
    }
    println!("外层作用域 value = {}", value); // 仍然是 100

}

数据类型

Rust 是【静态类型】语言:编译期必须知道每个值的类型。
多数情况下编译器能自动推断,但有时需要你显式标注。

  • 标量类型(Scalar):整数、浮点数、布尔、字符
rs
fn main() {
    // ------------------------------------------------------------------
    // 1. 整数(Integers)
	// ------------------------------------------------------------------
    // 有符号:i8 i16 i32 i64 i128 isize
    // 无符号:u8 u16 u32 u64 u128 usize
    // 其中 isize/usize 的位数取决于运行平台(64 位机器上是 64 位)。
    // 默认整数类型是 i32。
    let a: i32 = -42;
    let b: u8 = 255; // u8 范围是 0..=255
    let big: i64 = 9_000_000_000;
    println!("整数: a={}, b={}, big={}", a, b, big);
 
    // 整数字面量的多种写法:
    let decimal = 98_222; // 十进制
    let hex = 0xff; // 十六进制
    let octal = 0o77; // 八进制
    let binary = 0b1111_0000; // 二进制
    let byte = b'A'; // 字节(仅限 u8),值为 65
    println!(
        "字面量: 十进制={}, 十六进制={}, 八进制={}, 二进制={}, 字节={}",
        decimal, hex, octal, binary, byte
    );
 
    // 整数溢出:
    // 在 debug 模式下溢出会 panic;release 模式下会"环绕"(wrapping)。
    // 想明确处理溢出,用这些方法:
    let x: u8 = 250;
    println!("checked_add(10): {:?}", x.checked_add(10)); // 返回 Option,溢出时为 None
    println!("wrapping_add(10): {}", x.wrapping_add(10)); // 环绕:250+10=260 -> 4
    println!("saturating_add(10): {}", x.saturating_add(10)); // 饱和:停在最大值 255
 
    // ------------------------------------------------------------------
    // 2. 浮点数(Floating-Point)
    // ------------------------------------------------------------------
    // 两种:f32(单精度)和 f64(双精度)。默认是 f64。
    let f1 = 3.14; // 推断为 f64
    let f2: f32 = 2.71828;
    println!("浮点数: f1={}, f2={}", f1, f2);
 
    // ------------------------------------------------------------------
    // 3. 数值运算
    // ------------------------------------------------------------------
    let sum = 5 + 10;
    let difference = 95.5 - 4.3;
    let product = 4 * 30;
    let quotient = 56.7 / 32.2;
    let remainder = 43 % 5; // 取余
    // 注意:整数除法会截断小数部分
    let int_division = 7 / 2; // 结果是 3,不是 3.5
    println!(
        "运算: 和={}, 差={:.1}, 积={}, 商={:.4}, 余={}, 整数除法={}",
        sum, difference, product, quotient, remainder, int_division
    );
 
    // ------------------------------------------------------------------
    // 4. 布尔(Boolean)
    // ------------------------------------------------------------------
    let t = true;
    let f: bool = false;
    println!("布尔: t={}, f={}", t, f);
 
    // ------------------------------------------------------------------
    // 5. 字符(char)
    // ------------------------------------------------------------------
    // Rust 的 char 是 4 字节,表示一个 Unicode 标量值,
    // 所以它不仅能存 ASCII,还能存中文、emoji 等。
    // 注意:char 用【单引号】,字符串用双引号。
    let c = 'z';
    let chinese = '中';
    let emoji = '😻';
    println!("字符: c={}, 中文={}, emoji={}", c, chinese, emoji);

	// ==================================================================
    // 类型转换(Type Casting)
    // ==================================================================
    // Rust 【不会】隐式转换数值类型,必须用 as 显式转换。
    let integer = 65;
    let float_val = integer as f64; // i32 -> f64
    let character = integer as u8 as char; // i32 -> u8 -> char,得到 'A'
    println!("转换: 整数={}, 浮点={}, 字符={}", integer, float_val, character);
 
    // 缩窄转换可能截断,要小心:
    let large = 300;
    let small = large as u8; // 300 超出 u8 范围,结果是 300 % 256 = 44
    println!("缩窄转换: 300 as u8 = {}", small);
 
    // ==================================================================
    // 四、类型推断与显式标注
    // ==================================================================
    // 大多数时候编译器能推断类型:
    let inferred = 2.0; // 推断为 f64
 
    // 但有些场景必须标注,否则编译器不知道用哪个类型,例如 parse:
    let guess: u32 = "42".parse().expect("不是数字");
    // 或者用"涡轮鱼"(turbofish)语法指定:
    let guess2 = "42".parse::<u32>().expect("不是数字");
    println!("推断: {}, parse 标注: {}, turbofish: {}", inferred, guess, guess2);

}
  • 复合类型(Compound):元组、数组
rs
fn main() {
    // ------------------------------------------------------------------
    // 6. 元组(Tuple)
    // ------------------------------------------------------------------
    // 把多个不同类型的值组合成一个复合值。长度固定。
    let tup: (i32, f64, char) = (500, 6.4, 'A');
 
    // 解构(destructuring):一次性把元组拆成多个变量
    let (one, two, three) = tup;
    println!("元组解构: {}, {}, {}", one, two, three);
 
    // 用 . 加索引访问元组元素(从 0 开始)
    println!("元组索引访问: {}, {}, {}", tup.0, tup.1, tup.2);
 
    // 空元组 () 叫做"单元类型"(unit type),表示"没有值",
    // 是没有返回值的函数的隐式返回类型。
    let unit: () = ();
    println!("单元类型: {:?}", unit);
 
    // ------------------------------------------------------------------
    // 7. 数组(Array)
    // ------------------------------------------------------------------
    // 数组中所有元素【类型相同】,长度固定(编译期已知)。
    // 数组数据存放在【栈】上。
    let arr = [1, 2, 3, 4, 5];
 
    // 显式标注:[类型; 长度]
    let typed_arr: [i32; 5] = [10, 20, 30, 40, 50];
 
    // 快速初始化:[初始值; 长度] -> 5 个 3
    let repeated = [3; 5];
    println!("数组: {:?}", arr);
    println!("带类型数组: {:?}", typed_arr);
    println!("重复初始化: {:?}", repeated);
 
    // 用索引访问数组元素
    println!("第一个元素: {}, 第二个元素: {}", arr[0], arr[1]);
    println!("数组长度: {}", arr.len());
 
    // 越界访问:编译期已知的越界会编译错误;
    // 运行期(比如用变量索引)越界会 panic,而不是读到非法内存。
    // 例如 arr[10] 会触发 "index out of bounds" panic。
}

函数

  • 函数定义的位置不影响调用:Rust 不要求"先定义后使用",
  • 只要在同一作用域里能找到即可。下面调用的函数都定义在 main 之后。
  • 函数中最后的那个值则为返回值,如果要提前返回则使用return
rs
fn main() {
    // ------------------------------------------------------------------
    // 1. 无参数、无返回值的函数
    // ------------------------------------------------------------------
    say_hello();
 
    // ------------------------------------------------------------------
    // 2. 带参数的函数
    // ------------------------------------------------------------------
    // 每个参数都【必须】标注类型,这是函数签名的硬性要求。
    print_labeled_measurement(5, 'h');
 
    // ------------------------------------------------------------------
    // 3. 带返回值的函数
    // ------------------------------------------------------------------
    let five = five();
    println!("five() 返回: {}", five);
 
    let sum = add(3, 4);
    println!("add(3, 4) = {}", sum);
 
    // ------------------------------------------------------------------
    // 4. 语句 vs 表达式
    // ------------------------------------------------------------------
    // 这是 Rust 区别于很多语言的关键点:
    //   - 【语句】(statement):执行某个动作,不返回值。如 `let x = 5;`
    //   - 【表达式】(expression):计算并产生一个值。如 `5 + 6`、函数调用、{ } 块
    //
    // Rust 是"基于表达式"的语言,很多东西都是表达式。
 
    // 一个【块】 { } 也是表达式,它的值是最后一个表达式的值。
    // 注意:作为返回值的那一行【结尾没有分号】!加了分号就变成语句了。
    let y = {
        let inner = 3;
        inner + 1 // 没有分号 -> 这是块的返回值,所以 y = 4
    };
    println!("块表达式的值 y = {}", y);
 
    // 对比:if 也是表达式,可以直接赋值
    let condition = true;
    let number = if condition { 5 } else { 6 };
    println!("if 表达式赋值 number = {}", number);
 
    // ------------------------------------------------------------------
    // 5. 多返回值:用元组
    // ------------------------------------------------------------------
    let (q, r) = divmod(17, 5);
    println!("17 除以 5: 商={}, 余={}", q, r);
 
    // ------------------------------------------------------------------
    // 6. 提前返回:return 关键字
    // ------------------------------------------------------------------
    println!("abs_value(-8) = {}", abs_value(-8));
    println!("abs_value(8) = {}", abs_value(8));
 
    // ------------------------------------------------------------------
    // 7. 函数作为值(函数指针)
    // ------------------------------------------------------------------
    // 函数本身也有类型,可以赋值给变量、当作参数传递。
    let f: fn(i32, i32) -> i32 = add;
    println!("通过函数指针调用: {}", f(10, 20));
 
    // 把函数传给另一个函数
    let result = apply(add, 6, 7);
    println!("apply(add, 6, 7) = {}", result);
 
    // ------------------------------------------------------------------
    // 8. 闭包(Closure)简介
    // ------------------------------------------------------------------
    // 闭包是可以捕获其所在环境的匿名函数,用 |参数| 表达式 的形式写。
    let multiplier = 3;
    let multiply = |n: i32| n * multiplier; // 捕获了外部变量 multiplier
    println!("闭包 multiply(5) = {}", multiply(5));
 
    // 闭包常用于把行为传给函数(后面集合那章会大量用到)
    let nums = [1, 2, 3, 4];
    let doubled: Vec<i32> = nums.iter().map(|x| x * 2).collect();
    println!("用闭包把每个元素翻倍: {:?}", doubled);
}
 
// ----------------------------------------------------------------------------
// 下面是各个被调用的函数定义
// ----------------------------------------------------------------------------
 
// 无参数、无返回值。无返回值其实是返回单元类型 ()。
fn say_hello() {
    println!("你好,Rust!");
}
 
// 带两个参数。注意每个参数都标注了类型。
fn print_labeled_measurement(value: i32, unit_label: char) {
    println!("测量值是: {}{}", value, unit_label);
}
 
// 有返回值的函数:用 -> 标注返回类型。
// 函数体最后一个表达式(无分号)就是返回值。
fn five() -> i32 {
    5 // 没有分号,作为返回值
}
 
// 两数相加。最后一行是表达式,作为返回值。
fn add(a: i32, b: i32) -> i32 {
    a + b
}
 
// 返回多个值:打包成元组返回。
fn divmod(a: i32, b: i32) -> (i32, i32) {
    (a / b, a % b)
}
 
// 使用 return 提前返回(常配合条件判断)。
fn abs_value(n: i32) -> i32 {
    if n < 0 {
        return -n; // 提前返回,用 return 时通常带分号
    }
    n // 正常情况下的返回值
}
 
// 高阶函数:接收一个函数作为参数。
// 参数 op 的类型是 "接收两个 i32、返回 i32 的函数"。
fn apply(op: fn(i32, i32) -> i32, a: i32, b: i32) -> i32 {
    op(a, b)
}

流程控制

  1. if / else if / else
  2. if 作为表达式
  3. loop 无限循环 + break 返回值
  4. while 条件循环
  5. for 遍历(配合 Range、集合)
  6. 循环标签(loop labels)
rs
fn main() {
    // ==================================================================
    // 一、if / else
    // ==================================================================
    let number = 7;
 
    // if 的条件【必须】是 bool 类型。
    // Rust 不会把非布尔值自动当真假,例如 `if number` 是编译错误。
    if number < 5 {
        println!("条件为真:number 小于 5");
    } else {
        println!("条件为假:number 不小于 5");
    }
 
    // 多重条件:else if
    let n = 6;
    if n % 4 == 0 {
        println!("{} 能被 4 整除", n);
    } else if n % 3 == 0 {
        println!("{} 能被 3 整除", n);
    } else if n % 2 == 0 {
        println!("{} 能被 2 整除", n);
    } else {
        println!("{} 不能被 4、3、2 整除", n);
    }
 
    // ------------------------------------------------------------------
    // if 是表达式,可以直接给变量赋值
    // ------------------------------------------------------------------
    let condition = true;
    // 注意:if 和 else 两个分支返回的类型必须相同。
    let value = if condition { 5 } else { 6 };
    println!("if 表达式赋值: value = {}", value);
 
 
    // ==================================================================
    // 二、loop:无限循环
    // ==================================================================
    // loop 会一直循环,直到遇到 break。
    let mut counter = 0;
 
    // loop 也是表达式:break 后面可以跟一个值,作为整个 loop 的返回值。
    let result = loop {
        counter += 1;
        if counter == 10 {
            break counter * 2; // 跳出循环并返回 20
        }
    };
    println!("loop 的返回值: {}", result);
 
    // continue:跳过本次循环剩余部分,直接进入下一次
    let mut i = 0;
    let mut sum_of_odds = 0;
    loop {
        i += 1;
        if i > 10 {
            break;
        }
        if i % 2 == 0 {
            continue; // 偶数跳过
        }
        sum_of_odds += i;
    }
    println!("1~10 中奇数之和: {}", sum_of_odds);
 
    // ==================================================================
    // 三、while:条件循环
    // ==================================================================
    // 条件为真时继续循环,条件为假时结束。
    let mut count = 3;
    while count != 0 {
        println!("倒计时: {}", count);
        count -= 1;
    }
    println!("发射!🚀");
 
    // ==================================================================
    // 四、for:遍历
    // ==================================================================
    // for 是 Rust 中最常用、最安全的循环方式。
 
    // 4.1 遍历数组/集合元素
    let arr = [10, 20, 30, 40, 50];
    for element in arr {
        println!("数组元素: {}", element);
    }
 
    // 4.2 配合 Range 遍历数字区间
    // 1..4   表示 1, 2, 3(不含右端 4)
    // 1..=4  表示 1, 2, 3, 4(含右端,注意 = 号)
    for x in 1..4 {
        println!("1..4 -> {}", x);
    }
    for x in 1..=3 {
        println!("1..=3 -> {}", x);
    }
 
    // 4.3 反向遍历:.rev()
    for x in (1..4).rev() {
        println!("反向 -> {}", x);
    }
 
    // 4.4 带索引遍历:.enumerate() 返回 (索引, 值)
    let letters = ['a', 'b', 'c'];
    for (index, letter) in letters.iter().enumerate() {
        println!("索引 {} 对应字母 {}", index, letter);
    }
 
    // 4.5 用 step_by 控制步长(每隔几个取一个)
    for x in (0..10).step_by(2) {
        print!("{} ", x); // 0 2 4 6 8
    }
    println!();
 
    // ==================================================================
    // 五、循环标签(Loop Labels)
    // ==================================================================
    // 当循环嵌套时,break / continue 默认只作用于【最内层】循环。
    // 想跳出外层循环,给循环加一个标签(以 ' 开头),然后 break '标签。
    let mut total = 0;
    'outer: for i in 1..=5 {
        for j in 1..=5 {
            if i * j > 6 {
                println!("i={}, j={} 时 i*j>6,跳出外层循环", i, j);
                break 'outer; // 直接跳出外层 'outer 循环
            }
            total += i * j;
        }
    }
    println!("累加结果: {}", total);
}

所有权

  • Rust 中每一个【值】都有一个【所有者】(owner)。
  • 同一时刻,值【只能有一个】所有者。
  • 当所有者离开作用域时,这个值就会被【自动释放】(drop)。
rs
fn main() {
    // ==================================================================
    // 一、作用域和自动释放
    // ==================================================================
	// 这里之所以为放在堆,是因为大小可能改变的数据,数据本体一定在堆上,实际是分配了额一个固定大小的栈身份卡指向堆。
    {
        let s = String::from("hello"); // s 进入作用域,在堆上分配内存
        println!("作用域内: {}", s);
    } // s 离开作用域,Rust 自动调用 drop 释放内存,无需手动 free
}

启动入口

Rust 可执行程序最终需要一个入口函数(通常是 main),但它不一定在 src/main.rs 中;src/main.rs 只是 Cargo 的默认入口位置。