【Rust编程:从新手到大师】Rust变量深度详解

本文聚焦 Rust 变量的核心特性,从 "定义 - 可变性 - 遮蔽 - 作用域 - 生命周期" 全流程展开,通过对比案例、错误分析、实操技巧,帮零基础学生理解 Rust 变量与其他语言的差异,掌握规范使用方法,为后续学习打下坚实基础。

一、Rust 变量的核心本质

在 Rust 中,变量不仅是 "存储数据的容器",更是 "内存安全的守护者"。Rust 通过变量的不可变性默认显式可变性作用域约束等设计,从源头避免内存泄漏、数据竞争等问题,这是 Rust 与 C/C++、Python 等语言的核心区别。

1.1 变量与内存的关系

每一个变量在定义时,Rust 都会:

  1. 为变量在内存中分配一块 "固定大小" 的空间(大小由变量类型决定,如i32占 4 字节、bool占 1 字节);

  2. 将数据存入该内存空间;

  3. 记录变量的 "作用域"(变量生效的代码范围),作用域结束后自动释放内存(无需手动管理,避免内存泄漏)。

案例 1:变量的内存分配示意
复制代码
fn main() {

   // 定义i32类型变量x,内存分配4字节,存入值5

   let x: i32 = 5;

   println!("x的值:{}", x);

   // 此处x的作用域结束,内存自动释放

}

二、变量的定义与初始化

Rust 对变量的 "定义" 和 "初始化" 有严格要求,核心原则:变量必须初始化后才能使用(避免使用未初始化的脏数据,保障内存安全)。

2.1 基本定义语法

变量定义的基础语法:let [mut] 变量名[: 类型] = 初始值;,其中[]内为可选内容。

2.1.1 类型推断(推荐)

Rust 能根据 "初始值" 自动推断变量类型,无需手动指定,减少冗余代码:

复制代码
fn main() {

   // 类型推断为i32(整数默认类型)

   let age = 25;

   // 类型推断为f64(浮点数默认类型)

   let height = 1.75;

   // 类型推断为bool

   let is\_student = true;

   // 类型推断为char

   let initial = 'A';

   // 类型推断为\&str(字符串切片,后续详解)

   let name = "张三";

  

   println!("年龄:{}(类型:i32)", age);

   println!("身高:{}(类型:f64)", height);

}
2.1.2 显式指定类型(必要场景)

当 "初始值无法明确类型" 或 "需要指定非默认类型" 时,必须显式标注类型:

复制代码
fn main() {

   // 场景1:初始值为0,需指定为u8类型(默认是i32)

   let byte: u8 = 0;

   // 场景2:空字符串切片,需指定类型(无法推断)

   let empty\_str: \&str = "";

   // 场景3:科学计数法,需指定为f32(默认是f64)

   let small\_float: f32 = 1e-3;

  

   println!("字节值:{}(类型:u8)", byte);

   println!("空字符串:{}(类型:\&str)", empty\_str);

}
2.1.3 错误案例:未初始化变量

Rust 不允许定义 "未初始化的变量",编译时会直接报错:

复制代码
fn main() {

   // 错误:变量x未初始化

   let x: i32;

   // println!("x的值:{}", x);  // 取消注释后编译报错:use of possibly uninitialized variable \`x\`

   x = 10;  // 必须先赋值,再使用

   println!("x的值:{}", x);  // 正确:赋值后可使用

}

2.2 变量的可变性:默认不可变(Immutable)

Rust 变量默认 "不可变"------ 即变量赋值后,值不能修改。这是 Rust 保障 "数据一致性" 和 "线程安全" 的核心设计,避免意外修改导致的 bug。

2.2.1 不可变变量的特性
复制代码
fn main() {

   // 定义不可变变量(默认)

   let price = 99;

   println!("初始价格:{}", price);

  

   // 尝试修改不可变变量(错误)

   // price = 89;  // 编译报错:cannot assign twice to immutable variable \`price\`

}
2.2.2 显式可变:mut 关键字

若需要动态修改变量的值,需在定义时添加mut关键字(mut = mutable,可变的):

复制代码
fn main() {

   // 用mut定义可变变量

   let mut count = 0;

   println!("初始计数:{}", count);

  

   // 第一次修改

   count = count + 1;

   println!("修改后计数1:{}", count);  // 输出1

  

   // 第二次修改

   count \*= 2;

   println!("修改后计数2:{}", count);  // 输出2

}
2.2.3 可变变量的限制

即使使用mut,变量的 "类型" 也不能改变 ------Rust 是静态类型语言,类型一旦确定,终身不变:

复制代码
fn main() {

   let mut num = 10;  // 类型为i32

   num = 20;          // 正确:类型不变,仅值修改

   // num = "20";      // 错误:类型不匹配(i32 vs \&str)

}

三、变量遮蔽(Shadowing):重新定义同名变量

Rust 允许在同一作用域内,用let重新定义 "同名变量",新变量会 "遮蔽" 旧变量(旧变量在当前作用域内失效)。这与 "可变变量" 有本质区别,是 Rust 特有的变量特性。

3.1 变量遮蔽的基础用法

复制代码
fn main() {

   // 第一次定义变量x(i32类型,值5)

   let x = 5;

   println!("第一次定义x:{}(类型:i32)", x);  // 输出5

  

   // 第二次定义x(遮蔽旧x,值x+3=8,类型仍为i32)

   let x = x + 3;

   println!("第二次定义x:{}(类型:i32)", x);  // 输出8

  

   // 第三次定义x(遮蔽旧x,值"x is 8",类型变为\&str)

   let x = format!("x is {}", x);

   println!("第三次定义x:{}(类型:\&str)", x);  // 输出"x is 8"

}

3.2 变量遮蔽 vs 可变变量(核心区别)

很多零基础学生容易混淆 "变量遮蔽" 和 "可变变量",两者的核心差异如下:

对比维度 变量遮蔽(let 重定义) 可变变量(mut)
语法 let x = 5; let x = 10; let mut x = 5; x = 10;
是否创建新变量 是(每次 let 都创建新变量,旧变量失效) 否(仅修改原有变量的值,不创建新变量)
能否改变类型 能(新变量可与旧变量类型不同) 不能(类型必须始终一致)
作用域影响 新变量定义后,旧变量在当前作用域失效 变量始终有效,直到作用域结束
内存分配 每次遮蔽都重新分配内存 仅一次内存分配,后续修改值
案例 2:对比演示
复制代码
fn main() {

   // 案例A:变量遮蔽(可改变类型)

   let a = 10;          // i32类型

   let a = a.to\_string();// 遮蔽旧a,类型变为String

   println!("变量遮蔽a:{}(类型:String)", a);

  

   // 案例B:可变变量(不可改变类型)

   let mut b = 10;      // i32类型

   b = 20;              // 正确:类型不变

   // b = b.to\_string();  // 错误:类型不匹配(i32 vs String)

   println!("可变变量b:{}(类型:i32)", b);

}

3.3 变量遮蔽的适用场景

  1. 类型转换:需要将变量从一种类型转为另一种类型,且希望保留变量名(如整数转字符串);

  2. 值预处理:对变量值进行加工后,用同名变量存储结果(如去空格、格式化);

  3. 缩小变量范围:在子作用域内重新定义变量,避免影响外部作用域。

案例 3:变量遮蔽的实际应用
复制代码
fn main() {

   // 原始输入(带空格的字符串)

   let input = "  123  ";

   println!("原始输入:{}", input);

  

   // 第一步:去除空格(遮蔽旧input,类型仍为\&str)

   let input = input.trim();

   println!("去空格后:{}", input);

  

   // 第二步:转为整数(遮蔽旧input,类型变为i32)

   let input: i32 = input.parse().unwrap();

   println!("转为整数后:{}(类型:i32)", input);

  

   // 第三步:计算平方(遮蔽旧input,类型仍为i32)

   let input = input \* input;

   println!("平方结果:{}", input);

}
运行结果:
复制代码
原始输入:   123 

去空格后:123

转为整数后:123(类型:i32)

平方结果:15129

四、变量的作用域(Scope):变量的 "生效范围"

变量的 "作用域" 是指 "变量能被访问的代码范围",超出范围后变量自动失效,内存被释放(Rust 的 "所有权" 机制基础,后续详解)。

4.1 作用域的基本规则

Rust 中,作用域通常由 "花括号{}" 划分,常见场景:

  1. 函数作用域:变量在fn main() {}内定义,仅在函数内生效;

  2. 代码块作用域:变量在if {}for {}{}等代码块内定义,仅在块内生效;

  3. 子作用域:在父作用域内嵌套子作用域,子作用域可访问父作用域变量,但父作用域不能访问子作用域变量。

案例 4:不同作用域的变量访问
复制代码
fn main() {

   // 父作用域变量:在main函数内生效

   let parent\_var = "父作用域变量";

   println!("父作用域内访问:{}", parent\_var);

  

   // 子作用域(用{}创建)

   {

       // 子作用域变量:仅在当前{}内生效

       let child\_var = "子作用域变量";

       // 子作用域可访问父作用域变量

       println!("子作用域内访问父变量:{}", parent\_var);

       println!("子作用域内访问子变量:{}", child\_var);

   }

   // 父作用域不能访问子作用域变量(错误)

   // println!("父作用域访问子变量:{}", child\_var);  // 编译报错:cannot find value \`child\_var\` in this scope

}
运行结果:
复制代码
父作用域内访问:父作用域变量

子作用域内访问父变量:父作用域变量

子作用域内访问子变量:子作用域变量

4.2 作用域与变量遮蔽的结合

在子作用域内,可通过 "变量遮蔽" 重新定义父作用域的同名变量,且仅在子作用域内生效,不影响父作用域变量:

复制代码
fn main() {

   let x = 10;  // 父作用域变量x

   println!("父作用域x:{}", x);  // 输出10

  

   {

       // 子作用域遮蔽x,仅在子作用域内生效

       let x = 20;

       println!("子作用域x(遮蔽后):{}", x);  // 输出20

   }

  

   // 父作用域x不受子作用域遮蔽影响

   println!("父作用域x(子作用域后):{}", x);  // 输出10

}
运行结果:
复制代码
父作用域x:10

子作用域x(遮蔽后):20

父作用域x(子作用域后):10

五、变量的生命周期(Lifetime):变量的 "存活时间"

变量的 "生命周期" 与 "作用域" 紧密相关,指 "变量从定义到作用域结束的存活时间"。在 Rust 中,生命周期由编译器自动管理,无需手动控制,核心规则:

  1. 变量在定义时 "出生"(分配内存);

  2. 变量在作用域结束时 "死亡"(释放内存);

  3. 生命周期内,变量可被多次访问和修改(若可变)。

5.1 生命周期的实际体现

复制代码
fn main() {

   // 变量a的生命周期开始(分配内存)

   let a = 5;

   println!("a的生命周期内:{}", a);

  

   // 变量b的生命周期开始

   let mut b = 10;

   b = 20;  // 可变变量,生命周期内可修改

   println!("b的生命周期内:{}", b);

   // 变量b的生命周期结束(main函数结束前)

  

   // 变量a的生命周期结束(main函数结束)

}

5.2 生命周期与内存安全

Rust 通过 "生命周期约束",避免 "悬垂引用"(引用了已释放的变量内存),这是 Rust 内存安全的核心保障。后续学习 "引用" 和 "所有权" 时会深入讲解,此处先了解基础概念:

复制代码
fn main() {

   let reference;  // 定义引用变量

   {

       let x = 5;  // x的生命周期开始

       reference = \&x;  // 引用x的内存

       println!("子作用域内引用x:{}", reference);  // 正确:x仍存活

   }  // x的生命周期结束,内存释放

   // 错误:引用了已释放的x的内存(悬垂引用)

   // println!("父作用域引用x:{}", reference);  // 编译报错:borrowed value does not live long enough

}

六、常量(Constant):特殊的 "不可变变量"

Rust 中的 "常量" 与 "不可变变量" 相似(值都不能修改),但有本质区别,是专门用于存储 "编译期已知、全局生效、永不改变的值" 的变量类型。

6.1 常量的定义语法

常量定义的语法:const 常量名: 类型 = 初始值;,注意:

  1. 必须显式指定类型(不能依赖类型推断);

  2. 初始值必须是 "编译期可计算的值"(不能是运行时才能确定的值,如用户输入、随机数);

  3. 常量名通常用 "全大写 + 下划线" 命名(规范);

  4. 常量可在全局作用域定义(函数外),全程序生效。

案例 5:常量的使用
复制代码
// 全局常量:在函数外定义,全程序生效

const MAX\_SCORE: i32 = 100;

const PI: f64 = 3.1415926;

fn main() {

   // 函数内使用全局常量

   println!("满分:{}", MAX\_SCORE);

   println!("圆周率:{}", PI);

  

   // 函数内定义局部常量(较少用,通常全局定义)

   const MIN\_AGE: u8 = 18;

   println!("最小年龄:{}", MIN\_AGE);

  

   // 常量不能修改(错误)

   // MAX\_SCORE = 90;  // 编译报错:cannot assign to \`MAX\_SCORE\`, which is a constant

}
运行结果:
复制代码
满分:100

圆周率:3.1415926

最小年龄:18

6.2 常量 vs 不可变变量(区别)

对比维度 常量(const) 不可变变量(let)
类型推断 不支持,必须显式指定类型 支持,可通过初始值自动推断类型
定义位置 可在全局作用域(函数外)或局部作用域 仅能在局部作用域(函数内、代码块内)
初始值要求 必须是编译期可计算的值(如字面量、编译期常量表达式) 可是编译期值或运行时值(如用户输入、函数返回值)
内存分配 编译期嵌入代码,运行时无额外内存分配 运行时在栈上分配内存,作用域结束释放
命名规范 全大写 + 下划线(如MAX_SCORE),强制规范 蛇形命名(如max_score),建议规范
适用场景 存储全局固定值(如最大值、常量配置) 存储局部固定值(如临时计算结果)
案例 6:常量与不可变变量的场景差异
复制代码
// 全局常量:编译期已知,全程序生效

const MAX\_RETRY: u32 = 3;  // 必须显式指定类型

fn main() {

   // 不可变变量:运行时确定值(如函数返回值)

   let current\_time = get\_current\_second();  // 类型自动推断为u32

   println!("当前秒数:{}", current\_time);

  

   // 常量:编译期已知,用于固定逻辑

   for i in 0..MAX\_RETRY {

       println!("第{}次重试", i + 1);

   }

}

// 模拟获取当前秒数(运行时才能确定值)

fn get\_current\_second() -> u32 {

   // 实际场景中会调用系统API,此处用固定值模拟

   45

}
运行结果:
复制代码
当前秒数:45

第1次重试

第2次重试

第3次重试
关键说明:
  • 常量MAX_RETRY是编译期已知的固定值,适合用于循环次数、配置上限等场景;

  • 不可变变量current_time的值由函数get_current_second()返回(运行时确定),无法用常量定义;

  • 若尝试将运行时值赋值给常量(如const TIME: u32 = get_current_second();),会编译报错,因为常量要求初始值是编译期可计算的。

6.3 静态变量(static):特殊的 "全局变量"

除了常量,Rust 还提供 "静态变量"(static),用于存储 "全局生命周期、运行时初始化" 的变量,与常量的核心区别是:静态变量在运行时分配固定内存(通常在数据段),且可修改(需用static mut)。

静态变量的定义与使用:
复制代码
// 全局静态变量:不可变,全程序生命周期

static GLOBAL\_VERSION: \&str = "1.0.0";

// 可变全局静态变量:需用static mut,访问时需unsafe块(有内存安全风险)

static mut COUNTER: u32 = 0;

fn main() {

   // 访问不可变静态变量(安全,无需unsafe)

   println!("程序版本:{}", GLOBAL\_VERSION);

  

   // 访问可变静态变量(不安全,需用unsafe块)

   unsafe {

       COUNTER += 1;

       println!("计数器:{}", COUNTER);  // 输出1

      

       COUNTER += 1;

       println!("计数器:{}", COUNTER);  // 输出2

   }

}
注意事项:
  • 静态变量的生命周期是 "全局"(程序运行期间始终存在),内存不会自动释放;

  • 不可变静态变量(static)访问安全,可变静态变量(static mut)访问需用unsafe块,因为多线程环境下可能存在数据竞争,风险较高;

  • 零基础阶段建议优先使用常量(const),避免使用static mut,除非有明确的全局变量需求且能保证内存安全。

七、变量相关常见错误与解决方案

零基础学生在使用 Rust 变量时,容易遇到以下几类错误,掌握错误原因和解决方法能大幅提升学习效率。

7.1 错误 1:未初始化变量(use of possibly uninitialized variable)

错误示例:
复制代码
fn main() {

   let x: i32;  // 仅定义,未初始化

   println!("x的值:{}", x);  // 编译报错

}
错误原因:

Rust 不允许使用未初始化的变量,避免访问内存中的 "脏数据"(随机值),保障内存安全。

解决方案:
  1. 定义变量时直接初始化(推荐):let x: i32 = 10;

  2. 若无法立即初始化,确保使用前赋值:

    fn main() {

    复制代码
    let x: i32;
    
    x = 10;  // 使用前赋值
    
    println!("x的值:{}", x);  // 正确

    }

7.2 错误 2:不可变变量重复赋值(cannot assign twice to immutable variable)

错误示例:
复制代码
fn main() {

   let x = 5;

   x = 10;  // 编译报错:不可变变量不能重复赋值

}
错误原因:

变量默认不可变,定义后无法修改值,需显式声明mut才能可变。

解决方案:
  1. 定义变量时添加mut关键字:

    fn main() {

    复制代码
    let mut x = 5;
    
    x = 10;  // 正确:可变变量可重复赋值
    
    println!("x的值:{}", x);  // 输出10

    }

  2. 若无需修改值,删除重复赋值语句;

  3. 若需要改变类型,使用变量遮蔽:let x = 10;(重新定义同名变量)。

7.3 错误 3:变量作用域外访问(cannot find value in this scope)

错误示例:
复制代码
fn main() {

   {

       let x = 5;  // 子作用域变量

   }

   println!("x的值:{}", x);  // 编译报错:子作用域结束,x已失效

}
错误原因:

变量的作用域已结束,变量已失效(内存被释放),无法在作用域外访问。

解决方案:
  1. 将变量定义在更外层的作用域(如父作用域):

    fn main() {

    复制代码
    let x = 5;  // 父作用域变量
    
    {
    
        println!("子作用域内访问x:{}", x);  // 正确:子作用域可访问父作用域变量
    
    }
    
    println!("父作用域内访问x:{}", x);  // 正确

    }

  2. 若变量必须在子作用域内定义,可通过 "返回值" 将变量传递到父作用域:

    fn main() {

    复制代码
    let x = {
    
        let temp = 5;  // 子作用域变量
    
        temp  // 子作用域最后一行的值作为返回值,赋值给x
    
    };
    
    println!("x的值:{}", x);  // 正确:输出5

    }

7.4 错误 4:类型不匹配(mismatched types)

错误示例:
复制代码
fn main() {

   let x: i32 = "5";  // 编译报错:将字符串赋值给整数变量

}
错误原因:

变量的类型与赋值的值类型不一致,Rust 是静态类型语言,编译时会严格检查类型匹配。

解决方案:
  1. 确保值的类型与变量类型一致:let x: i32 = 5;

  2. 若需要不同类型,进行合法的类型转换:

    fn main() {

    复制代码
    // 字符串转整数(需处理可能的转换失败,用unwrap()简化,实际场景需处理错误)
    
    let x: i32 = "5".parse().unwrap();
    
    println!("x的值:{}(类型:i32)", x);  // 正确:输出5

    }

  3. 检查是否混淆了相似类型(如char&stri32u32)。

八、Rust 变量实操技巧(零基础必备)

掌握以下技巧,能更高效、规范地使用 Rust 变量,避免常见问题。

8.1 优先使用类型推断,必要时显式指定类型

Rust 的类型推断能力很强,大部分场景下无需手动指定类型,可减少冗余代码;但在 "类型不明确" 或 "需要特定类型" 的场景下,必须显式指定类型,避免编译器推断错误。

推荐做法:
复制代码
fn main() {

   // 推荐:类型推断(清晰明确,无冗余)

   let age = 25;          // 自动推断为i32

   let height = 1.75;     // 自动推断为f64

   let name = "张三";      // 自动推断为\&str

  

   // 必要时显式指定类型(避免推断错误)

   let byte: u8 = 0;      // 需指定为u8,避免推断为i32

   let score: f32 = 95.5; // 需指定为f32,避免推断为f64

}

8.2 尽量使用不可变变量,仅在必要时用 mut

Rust 默认不可变的设计,是为了保障数据一致性和线程安全。在实际开发中,应尽量使用不可变变量,仅当需要动态修改变量值时,才添加mut关键字,这能减少意外修改导致的 bug。

推荐做法:
复制代码
fn main() {

   // 推荐:不可变变量(值无需修改)

   let username = "张三";

   let max\_age = 120;

  

   // 必要时用mut(值需要动态修改)

   let mut count = 0;

   for \_ in 0..5 {

       count += 1;  // 必须用mut才能修改

   }

   println!("计数结果:{}", count);

}

8.3 合理使用变量遮蔽,避免变量名冗余

当需要对变量值进行加工(如类型转换、值预处理)且希望保留变量名时,变量遮蔽是最佳选择,可避免定义 "x1、x2、x3" 等冗余的变量名。

推荐做法:
复制代码
fn main() {

   // 推荐:变量遮蔽,避免冗余变量名

   let input = "  123.45  ";    // 原始输入(带空格的字符串)

   let input = input.trim();    // 去空格(遮蔽旧input)

   let input: f64 = input.parse().unwrap();  // 转浮点数(遮蔽旧input)

   let input = input \* 2;       // 计算翻倍(遮蔽旧input)

  

   println!("最终结果:{}", input);  // 输出246.9

}
不推荐做法(冗余变量名):
复制代码
fn main() {

   // 不推荐:变量名冗余,可读性差

   let input\_str = "  123.45  ";

   let input\_trimmed = input\_str.trim();

   let input\_float: f64 = input\_trimmed.parse().unwrap();

   let input\_result = input\_float \* 2;

  

   println!("最终结果:{}", input\_result);

}

8.4 全局值优先用 const,避免用 static mut

对于全局固定值(如配置、常量),优先使用const(编译期安全,无内存风险);除非有 "全局可变状态" 的特殊需求(如全局计数器),否则避免使用static mut(需unsafe访问,有内存安全风险)。

推荐做法:
复制代码
// 推荐:全局常量(安全,编译期嵌入)

const API\_BASE\_URL: \&str = "https://api.example.com";

const MAX\_REQUESTS: u32 = 10;

fn main() {

   println!("API地址:{}", API\_BASE\_URL);

   println!("最大请求数:{}", MAX\_REQUESTS);

}

九、总结与后续学习方向

9.1 核心知识点总结

通过本文学习,你已掌握 Rust 变量的核心内容:

  1. 变量本质:不仅是存储数据的容器,更是内存安全的守护者,自动管理内存(分配与释放);

  2. 定义与初始化:必须初始化后才能使用,支持类型推断和显式类型标注;

  3. 可变性 :默认不可变,mut关键字实现可变,可变变量类型不能改变;

  4. 变量遮蔽 :用let重新定义同名变量,可改变类型,创建新变量遮蔽旧变量;

  5. 作用域与生命周期:作用域划分变量的生效范围,生命周期管理变量的存活时间,超出作用域自动释放内存;

  6. 常量与静态变量:常量是编译期已知的全局固定值,静态变量是运行时全局变量,需谨慎使用可变静态变量。

9.2 后续学习方向

Rust 变量是后续学习的基础,掌握变量后,可继续深入以下内容:

  1. 所有权(Ownership):Rust 的核心特性,解释变量如何管理内存,避免内存泄漏和数据竞争;

  2. 引用与借用(References & Borrowing):如何在不转移所有权的情况下访问变量,避免拷贝开销;

  3. 切片(Slices):对数组、字符串等复合类型的 "部分引用",是变量的延伸使用;

  4. 函数与参数:变量在函数间的传递方式(值传递、引用传递),与变量的可变性、所有权密切相关。

建议通过 "理论学习 + 代码实践" 结合的方式,多编写变量相关的代码(如类型转换、作用域控制、变量遮蔽),观察编译结果,加深对 Rust 变量设计理念的理解。

(注:文档部分内容可能由 AI 生成)

相关推荐
DongLi013 天前
rustlings 学习笔记 -- exercises/05_vecs
rust
番茄灭世神3 天前
Rust学习笔记第2篇
rust·编程语言
shimly1234564 天前
(done) 速通 rustlings(20) 错误处理1 --- 不涉及Traits
rust
shimly1234564 天前
(done) 速通 rustlings(19) Option
rust
@atweiwei4 天前
rust所有权机制详解
开发语言·数据结构·后端·rust·内存·所有权
shimly1234564 天前
(done) 速通 rustlings(24) 错误处理2 --- 涉及Traits
rust
shimly1234564 天前
(done) 速通 rustlings(23) 特性 Traits
rust
shimly1234564 天前
(done) 速通 rustlings(17) 哈希表
rust
shimly1234564 天前
(done) 速通 rustlings(15) 字符串
rust
shimly1234564 天前
(done) 速通 rustlings(22) 泛型
rust