Rust 开发环境 + 变量与可变性 + 基本类型
安装 Rust 工具链,理解 Cargo 项目结构,掌握变量/可变性/常量,学习基本数据类型
日期: 2026-04-11 方向: Rust 阶段: 第一阶段:基础构建 标签: #rust #rustup #cargo #variables #mutability #types
今日目标
| 类型 | 内容 |
|---|---|
| 学习 | 安装 Rust 工具链,理解 Cargo 项目结构,掌握变量/可变性/常量,学习基本数据类型 |
| 实操 | 创建第一个 Rust 项目,完成 rustlings 的 intro 和 variables 练习 |
| 产出 | 一个完整的 Rust 项目 + rustlings 练习通过记录 |
一、Rust 安装与环境配置
1.1 为什么学 Rust?
在区块链领域,Rust 的地位日益重要:
- Solana 的链上程序用 Rust 编写
- Polkadot/Substrate 框架基于 Rust
- Aptos/Sui 的 Move 语言受 Rust 影响深远
- Near Protocol 支持 Rust 编写智能合约
- Cosmos SDK 的 CosmWasm 智能合约用 Rust
- Rust 的内存安全和零成本抽象让它成为区块链底层开发首选
1.2 安装 Rustup
Rustup 是 Rust 的官方工具链管理器,类似于 Node.js 的 nvm。
Windows 安装:
# 访问 https://rustup.rs 下载 rustup-init.exe
# 或使用 PowerShell:
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
# 安装完成后验证
rustc --version # 应显示 rustc 1.7x.0 或更高
cargo --version # Cargo 是 Rust 的包管理器和构建工具
rustup --version # 工具链管理器
macOS/Linux 安装:
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
source $HOME/.cargo/env
1.3 核心工具介绍
| 工具 | 作用 | 类比 |
|---|---|---|
rustc | Rust 编译器 | solc (Solidity编译器) |
cargo | 包管理器+构建工具 | npm + webpack |
rustup | 工具链版本管理 | nvm |
rustfmt | 代码格式化 | prettier |
clippy | 代码静态检查 | eslint |
1.4 IDE 推荐
- VS Code + rust-analyzer 扩展(推荐):自动补全、类型推导、错误提示
- RustRover(JetBrains):商业 IDE,功能更全但更重
二、Cargo 项目结构
2.1 创建项目
# 创建新项目(二进制可执行文件)
cargo new my_first_rust
cd my_first_rust
# 或创建库项目
cargo new my_lib --lib
2.2 项目目录结构
my_first_rust/
├── Cargo.toml # 项目配置文件(类似 package.json)
├── Cargo.lock # 依赖锁定文件(类似 package-lock.json)
├── src/
│ └── main.rs # 入口文件
├── tests/ # 集成测试目录
├── benches/ # 性能基准测试
├── examples/ # 示例代码
└── target/ # 编译输出(.gitignore 中)
2.3 Cargo.toml 详解
[package]
name = "my_first_rust" # 项目名
version = "0.1.0" # 语义化版本
edition = "2021" # Rust 版本年份(2015/2018/2021)
authors = ["Your Name <email>"]
description = "My first Rust project for blockchain learning"
[dependencies]
# 外部依赖写在这里,类似 package.json 的 dependencies
# serde = "1.0" # JSON 序列化/反序列化
# tokio = { version = "1", features = ["full"] } # 异步运行时
2.4 常用 Cargo 命令
cargo new project_name # 创建新项目
cargo build # 编译(debug 模式)
cargo build --release # 编译(release 优化模式)
cargo run # 编译并运行
cargo test # 运行测试
cargo check # 快速检查代码(不生成二进制)
cargo clippy # 运行 lint 检查
cargo fmt # 格式化代码
cargo doc --open # 生成并打开文档
cargo add serde # 添加依赖(需要 cargo-edit)
三、变量与可变性
这是 Rust 最与众不同的第一个特性:变量默认不可变(immutable)。
3.1 let 声明(不可变变量)
fn main() {
let x = 5;
println!("x = {}", x);
// x = 6; // ❌ 编译错误!cannot assign twice to immutable variable
// error[E0384]: cannot assign twice to immutable variable `x`
}
为什么默认不可变? 这是 Rust 的核心设计哲学——如果一个值不需要改变,就不应该能被改变。这能:
- 防止意外修改导致的 bug
- 让编译器做更好的优化
- 让并发编程更安全
3.2 let mut 声明(可变变量)
fn main() {
let mut count = 0;
println!("count = {}", count); // 0
count += 1;
println!("count = {}", count); // 1
count = 100;
println!("count = {}", count); // 100
}
3.3 const 常量
// 常量:必须注明类型,必须是编译时常量表达式
const MAX_SUPPLY: u64 = 21_000_000;
const PI: f64 = 3.141592653589793;
const CONTRACT_NAME: &str = "MyToken";
fn main() {
println!("Max supply: {}", MAX_SUPPLY);
// MAX_SUPPLY = 100; // ❌ 常量永远不可变
}
const vs let 的区别:
| 特性 | const | let | let mut |
|---|---|---|---|
| 可变性 | 永远不可变 | 不可变 | 可变 |
| 类型注解 | 必须 | 可选(类型推导) | 可选 |
| 编译时求值 | 是 | 否 | 否 |
| 作用域 | 任意(常在全局) | 函数内 | 函数内 |
| 可以用函数返回值? | 否 | 是 | 是 |
3.4 Shadowing(遮蔽)
Rust 的一个独特特性——用同名变量"遮蔽"前一个变量:
fn main() {
let x = 5;
let x = x + 1; // 新的 x 遮蔽了旧的 x
let x = x * 2; // 再次遮蔽
println!("x = {}", x); // 12
// Shadowing 甚至可以改变类型!
let spaces = " "; // &str 类型
let spaces = spaces.len(); // usize 类型,这是合法的!
println!("spaces = {}", spaces); // 3
// 但 mut 不能改变类型:
// let mut spaces = " ";
// spaces = spaces.len(); // ❌ 类型不匹配!
}
Shadowing vs mut 的选择:
- 需要改变类型时,用 Shadowing
- 需要频繁修改同一个值时,用 mut
- 只是做一次转换时,用 Shadowing
四、基本数据类型
4.1 整数类型
| 长度 | 有符号 | 无符号 | 范围 |
|---|---|---|---|
| 8-bit | i8 | u8 | i8: -128 ~ 127, u8: 0 ~ 255 |
| 16-bit | i16 | u16 | ... |
| 32-bit | i32 | u32 | i32 是默认整数类型 |
| 64-bit | i64 | u64 | ... |
| 128-bit | i128 | u128 | 适合大数计算 |
| arch | isize | usize | 与平台位数相同(32/64位) |
fn main() {
// 默认整数类型是 i32
let age = 25; // i32
let balance: u64 = 1_000_000_000; // 下划线提高可读性
// 不同进制
let hex = 0xff; // 十六进制: 255
let octal = 0o77; // 八进制: 63
let binary = 0b1111_0000; // 二进制: 240
let byte_val = b'A'; // 字节(u8): 65
// 类型后缀
let x = 42u8; // 明确指定 u8
let y = 100_i64; // 明确指定 i64
// 溢出行为
// Debug 模式:溢出会 panic
// Release 模式:溢出会 wrapping(环绕)
// 推荐使用明确的方法:
let a: u8 = 250;
let b = a.wrapping_add(10); // 环绕: 4
let c = a.checked_add(10); // 返回 Option: None
let d = a.saturating_add(10); // 饱和: 255
let (e, overflowed) = a.overflowing_add(10); // (4, true)
println!("wrapping: {}, checked: {:?}, saturating: {}, overflowing: ({}, {})",
b, c, d, e, overflowed);
}
与 Solidity 对比:
| 特性 | Solidity | Rust |
|---|---|---|
| 默认整数 | uint256 (256位) | i32 (32位) |
| 溢出保护 | 0.8.0+ 自动 revert | Debug: panic, Release: wrap |
| 最大整数 | uint256 (2^256-1) | u128 (2^128-1) |
| 无符号默认 | uint (无符号) | i32 (有符号) |
4.2 浮点类型
fn main() {
let x = 2.0; // f64(默认)
let y: f32 = 3.0; // f32
// 浮点运算
let sum = 5.0 + 10.0;
let difference = 95.5 - 4.3;
let product = 4.0 * 30.0;
let quotient = 56.7 / 32.2;
let truncated = -5.0_f64 / 3.0; // -1.6666...
println!("sum={}, diff={}, prod={}, quot={}, trunc={}",
sum, difference, product, quotient, truncated);
}
注意:Solidity 没有浮点数!在区块链世界中,精度问题可能导致资产损失,所以使用整数 + 放大倍数。Rust 虽然支持浮点数,但在链上程序(如 Solana)中也推荐使用整数。
4.3 布尔类型
fn main() {
let t = true;
let f: bool = false;
// 布尔运算
let and = t && f; // false
let or = t || f; // true
let not = !t; // false
println!("and={}, or={}, not={}", and, or, not);
}
4.4 字符类型
fn main() {
let c = 'z';
let z: char = 'ℤ';
let heart = '❤';
let chinese = '中';
// char 在 Rust 中是 4 字节(Unicode 标量值)
// 与 Solidity 不同,Solidity 没有单独的字符类型
println!("Size of char: {} bytes", std::mem::size_of::<char>()); // 4
}
4.5 元组 (Tuple)
fn main() {
// 元组:固定长度,可以包含不同类型
let tup: (i32, f64, u8) = (500, 6.4, 1);
// 解构
let (x, y, z) = tup;
println!("x={}, y={}, z={}", x, y, z);
// 通过索引访问
let first = tup.0; // 500
let second = tup.1; // 6.4
let third = tup.2; // 1
// 空元组 = unit type
let unit: () = ();
// 类似于其他语言的 void,函数没有返回值时隐式返回 ()
}
4.6 数组 (Array)
fn main() {
// 数组:固定长度,相同类型
let arr = [1, 2, 3, 4, 5];
let arr_typed: [i32; 5] = [1, 2, 3, 4, 5];
// 初始化所有元素为同一个值
let zeros = [0; 10]; // [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
let ones = [1u8; 32]; // 32 个 1,类型 [u8; 32](类似 bytes32)
// 访问元素
let first = arr[0]; // 1
let second = arr[1]; // 2
// 越界访问会 panic(运行时检查)
// let out_of_bounds = arr[10]; // ❌ 运行时 panic!
// 遍历
for element in arr.iter() {
print!("{} ", element);
}
println!();
// 数组长度
println!("Length: {}", arr.len()); // 5
// 切片
let slice = &arr[1..3]; // [2, 3]
println!("Slice: {:?}", slice);
}
Rust 数组 vs Solidity 数组:
| 特性 | Rust Array | Solidity Fixed Array | Solidity Dynamic Array |
|---|---|---|---|
| 语法 | [T; N] | T[N] | T[] |
| 长度 | 编译时固定 | 编译时固定 | 运行时动态 |
| 存储位置 | 栈 | storage/memory | storage/memory |
| 越界检查 | 运行时 panic | 运行时 revert | 运行时 revert |
4.7 Vector(动态数组)
fn main() {
// Vec<T> - Rust 的动态数组(类似 Solidity 的动态数组)
let mut v: Vec<i32> = Vec::new();
v.push(1);
v.push(2);
v.push(3);
// 使用宏快速创建
let v2 = vec![10, 20, 30];
// 访问
let third = &v[2]; // 直接索引(可能 panic)
let third_safe = v.get(2); // 返回 Option<&i32>(安全)
match v.get(100) {
Some(val) => println!("Got: {}", val),
None => println!("Index out of bounds!"),
}
// 遍历
for i in &v {
print!("{} ", i);
}
println!();
// 修改遍历
for i in &mut v {
*i += 100;
}
println!("{:?}", v); // [101, 102, 103]
}
五、代码实战:综合练习
/// Rust Day 2 综合练习:模拟代币基本信息
/// 对标 Solidity Day 1 的 DataTypes 合约
const MAX_SUPPLY: u64 = 21_000_000;
const TOKEN_NAME: &str = "MomoToken";
const TOKEN_SYMBOL: &str = "MOMO";
const DECIMALS: u8 = 18;
fn main() {
println!("=== Token Info ===");
println!("Name: {}", TOKEN_NAME);
println!("Symbol: {}", TOKEN_SYMBOL);
println!("Decimals: {}", DECIMALS);
println!("Max Supply: {}", MAX_SUPPLY);
// 模拟余额(使用 HashMap 类似 Solidity 的 mapping)
use std::collections::HashMap;
let mut balances: HashMap<&str, u64> = HashMap::new();
// 模拟 mint
let alice = "0xAlice";
let bob = "0xBob";
balances.insert(alice, 1_000_000);
balances.insert(bob, 500_000);
println!("\n=== Balances ===");
for (addr, balance) in &balances {
println!("{}: {} {}", addr, balance, TOKEN_SYMBOL);
}
// 模拟 transfer
let transfer_amount: u64 = 100_000;
println!("\n=== Transfer {} {} from Alice to Bob ===", transfer_amount, TOKEN_SYMBOL);
// 使用 checked 算术防止溢出
if let Some(alice_balance) = balances.get(alice).copied() {
if alice_balance >= transfer_amount {
let new_alice = alice_balance.checked_sub(transfer_amount).unwrap();
let bob_balance = balances.get(bob).copied().unwrap_or(0);
let new_bob = bob_balance.checked_add(transfer_amount).unwrap();
balances.insert(alice, new_alice);
balances.insert(bob, new_bob);
println!("Transfer successful!");
} else {
println!("Insufficient balance!");
}
}
println!("\n=== Updated Balances ===");
for (addr, balance) in &balances {
println!("{}: {} {}", addr, balance, TOKEN_SYMBOL);
}
// 类型演示
println!("\n=== Type Demonstration ===");
demonstrate_types();
// 可变性演示
println!("\n=== Mutability Demonstration ===");
demonstrate_mutability();
}
fn demonstrate_types() {
// 整数
let small: u8 = 255;
let default_int = 42; // i32
let big: u128 = 340_282_366_920_938_463_463_374_607_431_768_211_455;
let negative: i32 = -100;
// 浮点
let pi: f64 = 3.14159;
// 布尔
let is_active = true;
// 字符
let emoji = '🦀';
// 元组
let token_info: (&str, &str, u8) = ("MomoToken", "MOMO", 18);
// 数组
let top_holders: [u64; 3] = [1_000_000, 500_000, 250_000];
println!("u8 max: {}", small);
println!("default int: {} (i32)", default_int);
println!("u128 max: {}", big);
println!("negative: {}", negative);
println!("pi: {}", pi);
println!("is_active: {}", is_active);
println!("emoji: {}", emoji);
println!("token name: {}", token_info.0);
println!("top holder #1: {}", top_holders[0]);
}
fn demonstrate_mutability() {
// 不可变(默认)
let x = 5;
println!("x = {}", x);
// 可变
let mut counter = 0;
counter += 1;
counter += 1;
println!("counter = {}", counter);
// Shadowing
let value = "123"; // &str
let value = value.parse::<i32>().unwrap(); // i32,类型改变
let value = value * 2; // i32
println!("value = {}", value); // 246
// const
const GENESIS_BLOCK: u64 = 0;
println!("Genesis block: {}", GENESIS_BLOCK);
}
六、Rustlings 练习指南
6.1 安装 Rustlings
# 安装 rustlings
cargo install rustlings
rustlings init
cd rustlings
# 开始练习
rustlings
6.2 intro 练习
// intro1.rs - 你的第一个 Rust 程序
// 只需要输出 "Hello and welcome to Rust!"
fn main() {
println!("Hello and welcome to Rust!");
}
// intro2.rs - 格式化输出
fn main() {
let x = 5;
let y = 10;
println!("x = {} and y = {}", x, y);
// {} 是占位符,类似 Solidity event 中的参数
}
6.3 variables 练习
// variables1.rs - 变量绑定需要 let
fn main() {
let x = 5;
println!("x has the value {}", x);
}
// variables2.rs - 类型注解
fn main() {
let x: i32 = 10;
println!("x = {}", x);
}
// variables3.rs - 可变性
fn main() {
let mut x = 3;
println!("x = {}", x);
x = 5;
println!("x = {}", x);
}
// variables4.rs - 常量
const NUMBER: i32 = 3;
fn main() {
println!("Number: {}", NUMBER);
}
// variables5.rs - Shadowing
fn main() {
let number = "T-H-R-E-E";
println!("Spell: {}", number);
let number = 3;
println!("Number: {}", number);
}
// variables6.rs - 类型推断
fn main() {
let number: i32 = 5;
println!("Number: {}", number);
}
七、关键要点总结
| 要点 | 说明 |
|---|---|
| Rust 变量默认不可变 | 需要 mut 关键字才能修改 |
| Shadowing 可以改变类型 | let x = "5"; let x = x.parse::<i32>(); |
| 默认整数是 i32 | 不同于 Solidity 的 uint256 |
| 没有 null | 用 Option<T> 代替(Some/None) |
| 数组固定长度 | 动态用 Vec<T> |
| checked 算术 | checked_add 防止溢出 |
| Cargo 是一切 | 构建、测试、依赖管理、文档 |
八、常见误区
误区 1:忘记加 mut
let x = 5;
// x = 6; // ❌ 编译器会给出非常友好的错误提示
// 建议:help: consider making this binding mutable: `let mut x`
误区 2:类型不匹配
let x: i32 = 5;
let y: u32 = 10;
// let z = x + y; // ❌ 不同整数类型不能直接运算
let z = x + y as i32; // ✅ 需要显式转换
误区 3:数组越界
let arr = [1, 2, 3];
// let val = arr[5]; // ❌ 编译通过但运行时 panic!
// 推荐使用 .get() 方法返回 Option
let val = arr.get(5); // ✅ 返回 None
误区 4:把 Rust 的 String 当成其他语言的 string
// Rust 有两种字符串:
let s1: &str = "hello"; // 字符串切片(不可变引用)
let s2: String = String::from("hello"); // 堆分配的字符串
// Day 6 会详细讲解它们的区别
九、Solidity vs Rust 类型对照表
| 概念 | Solidity | Rust | 备注 |
|---|---|---|---|
| 无符号整数 | uint256 | u64 / u128 | Solidity 默认 256 位 |
| 有符号整数 | int256 | i32 / i64 | Rust 默认 32 位 |
| 布尔 | bool | bool | 相同 |
| 地址 | address (20 bytes) | [u8; 32] (Solana) | 链依赖 |
| 字符串 | string | String / &str | Rust 区分所有权 |
| 固定字节 | bytes32 | [u8; 32] | 类似 |
| 动态数组 | uint[] | Vec<u32> | 类似 |
| 映射 | mapping(K => V) | HashMap<K, V> | 标准库 |
| 枚举 | enum Status {} | enum Status {} | Rust 枚举更强大 |
| 常量 | constant | const | 编译时确定 |
| 不可变 | immutable | 默认行为 | Rust 默认不可变 |
十、面试关联
Q: Rust 为什么选择变量默认不可变?
A: 这是 Rust 安全性设计的基石。不可变性让编译器可以保证数据不会被意外修改,在并发场景下尤其重要——多个线程可以安全地共享不可变数据而无需锁。在区块链上下文中,这有助于防止状态被意外修改导致的安全漏洞。
Q: Rust 的 u128 够用吗?区块链常需要 uint256。
A: Rust 原生最大支持 u128(2^128-1),不如 Solidity 的 uint256。但实际中:Solana 使用 u64 表示代币数量(足够表示天文数字的 lamports),大数运算可以用 uint crate 或自定义实现。Move 语言同样使用 u128 作为最大原生整数。
Q: Cargo 和 npm 有什么关键区别?
A: Cargo 不仅是包管理器(类似 npm),还是构建系统(类似 webpack)、测试运行器(类似 jest)和文档生成器。它的依赖解析更严格,编译是增量的,且内置了 cargo clippy(lint)和 cargo fmt(格式化)。这种一体化工具链设计让 Rust 的开发体验非常统一。
十一、参考资源
| 资源 | 链接 | 说明 |
|---|---|---|
| The Rust Book | https://doc.rust-lang.org/book/ | 官方教程,必读 |
| Rustlings | https://github.com/rust-lang/rustlings | 交互式练习 |
| Rust by Example | https://doc.rust-lang.org/rust-by-example/ | 代码示例大全 |
| Rust Playground | https://play.rust-lang.org/ | 在线运行 Rust |
| Cargo Book | https://doc.rust-lang.org/cargo/ | Cargo 详细指南 |
| Solana Cookbook | https://solanacookbook.com/ | Solana 开发参考 |