从Python到Rust - 1
第1章:环境搭建与第一个程序
1.1 开发环境对比
作为Python开发者,你可能已经习惯了使用pip
、conda
或poetry
来管理Python环境和依赖。Rust生态系统有自己的一套工具链,让我们来对比了解:
Python环境管理
# Python环境管理
python --version # 检查版本
pip install package # 安装包
pip freeze > requirements.txt # 导出依赖
python -m venv venv # 创建虚拟环境
Rust环境管理
# Rust环境管理
rustc --version # 检查编译器版本
cargo --version # 检查包管理器版本
cargo new project_name # 创建新项目
cargo build # 编译项目
cargo run # 编译并运行
1.2 安装Rust
在macOS/Linux上安装
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
source ~/.cargo/env
在Windows上安装
下载并运行 rustup-init.exe
from https://rustup.rs/
验证安装
rustc --version
cargo --version
rustup --version
与Python对比:
- Python通常需要分别安装解释器和包管理器
- Rust的rustup同时管理编译器、标准库和工具链
- rustup类似于pyenv,可以管理多个Rust版本
1.3 包管理器对比
特性 | Python (pip/poetry) | Rust (cargo) |
---|---|---|
配置文件 | requirements.txt / pyproject.toml | Cargo.toml |
锁定文件 | poetry.lock | Cargo.lock |
安装依赖 | pip install | cargo add |
运行项目 | python main.py | cargo run |
构建项目 | N/A (解释型) | cargo build |
测试 | pytest | cargo test |
发布包 | pip upload | cargo publish |
1.4 创建第一个Rust项目
使用Cargo创建项目
cargo new hello_rust
cd hello_rust
这会创建以下目录结构:
hello_rust/
├── Cargo.toml # 项目配置文件(类似Python的pyproject.toml)
└── src/
└── main.rs # 主程序文件
Cargo.toml文件解析
[package]
name = "hello_rust" # 项目名称
version = "0.1.0" # 版本号
edition = "2021" # Rust版本
[dependencies]
# 这里添加依赖,类似于requirements.txt
与Python对比:
# Python的pyproject.toml
[project]
name = "hello_python"
version = "0.1.0"
dependencies = [
"requests>=2.25.0",
]
1.5 第一个程序:Hello World
Rust版本
// src/main.rs
fn main() {
println!("Hello, world!");
}
Python版本对比
# main.py
print("Hello, world!")
运行程序
Rust:
cargo run
Python:
python main.py
1.6 编译 vs 解释执行
这是Rust和Python最重要的区别之一:
Python(解释型语言)
# main.py
def greet(name):
return f"Hello, {name}!"
if __name__ == "__main__":
print(greet("Python"))
运行过程:
- Python解释器逐行读取代码
- 实时解释执行
- 错误在运行时发现
Rust(编译型语言)
// src/main.rs
fn greet(name: &str) -> String {
format!("Hello, {}!", name)
}
fn main() {
println!("{}", greet("Rust"));
}
运行过程:
cargo build
- 编译成机器码- 生成可执行文件
./target/debug/hello_rust
- 错误在编译时发现
- 运行时性能更高
对比分析
特性 | Python | Rust |
---|---|---|
执行方式 | 解释执行 | 编译执行 |
启动速度 | 快 | 慢(需要编译) |
运行速度 | 慢 | 快 |
错误发现 | 运行时 | 编译时 |
部署 | 需要Python环境 | 独立可执行文件 |
开发迭代 | 快 | 慢(编译时间) |
1.7 基本项目结构
标准Rust项目结构
my_project/
├── Cargo.toml # 项目配置
├── Cargo.lock # 依赖锁定文件
├── src/
│ ├── main.rs # 二进制程序入口
│ ├── lib.rs # 库入口(可选)
│ └── bin/ # 额外的二进制文件
├── tests/ # 集成测试
├── examples/ # 示例代码
└── benches/ # 性能测试
对比Python项目结构
my_project/
├── pyproject.toml # 项目配置
├── requirements.txt # 依赖列表
├── src/
│ ├── __init__.py
│ └── main.py
├── tests/
│ └── test_main.py
└── docs/
1.8 开发环境推荐
IDE和编辑器
- VS Code + rust-analyzer - 推荐给Python开发者
- IntelliJ IDEA + Rust Plugin
- Vim/Neovim + rust-analyzer
有用的Cargo命令
cargo new project_name # 创建新项目
cargo build # 编译项目
cargo run # 编译并运行
cargo test # 运行测试
cargo check # 快速检查代码(不生成可执行文件)
cargo clippy # 代码检查(类似于pylint)
cargo fmt # 代码格式化(类似于black)
cargo doc --open # 生成并打开文档
Python等价命令对比
# Python # Rust
python main.py cargo run
python -m pytest cargo test
pylint main.py cargo clippy
black main.py cargo fmt
pip install package cargo add package
1.9 实践练习
练习1:创建一个简单的计算器
// src/main.rs
use std::io;
fn main() {
println!("简单计算器");
println!("输入第一个数字:");
let mut input = String::new();
io::stdin().read_line(&mut input).expect("读取输入失败");
let num1: f64 = input.trim().parse().expect("无效数字");
println!("输入第二个数字:");
input.clear();
io::stdin().read_line(&mut input).expect("读取输入失败");
let num2: f64 = input.trim().parse().expect("无效数字");
println!("{} + {} = {}", num1, num2, num1 + num2);
println!("{} - {} = {}", num1, num2, num1 - num2);
println!("{} * {} = {}", num1, num2, num1 * num2);
println!("{} / {} = {}", num1, num2, num1 / num2);
}
对比Python版本:
# main.py
print("简单计算器")
num1 = float(input("输入第一个数字:"))
num2 = float(input("输入第二个数字:"))
print(f"{num1} + {num2} = {num1 + num2}")
print(f"{num1} - {num2} = {num1 - num2}")
print(f"{num1} * {num2} = {num1 * num2}")
print(f"{num1} / {num2} = {num1 / num2}")
关键差异分析:
- 类型声明:Rust需要显式类型转换
- 错误处理:Rust强制处理可能的错误
- 内存管理:Rust的
String::new()
和.clear()
- 模式匹配:Rust的
expect()
vs Python的异常处理
1.10 常见问题和解决方案
问题1:编译错误 vs 运行时错误
Python中的运行时错误:
def divide(a, b):
return a / b # 如果b=0,运行时才报错
result = divide(10, 0) # ZeroDivisionError
Rust中的编译时检查:
fn divide(a: f64, b: f64) -> Option<f64> {
if b == 0.0 {
None // 编译时就要求处理这种情况
} else {
Some(a / b)
}
}
fn main() {
match divide(10.0, 0.0) {
Some(result) => println!("结果: {}", result),
None => println!("不能除以零"),
}
}
问题2:包管理混乱
在Python中,我们经常遇到依赖冲突:
pip install package_a # 需要 requests==2.25.0
pip install package_b # 需要 requests==2.28.0 # 冲突!
Rust的Cargo.lock确保版本一致性:
# Cargo.toml
[dependencies]
serde = "1.0"
tokio = "1.0"
1.11 性能对比示例
让我们通过一个简单的斐波那契数列计算来对比性能:
Python版本
import time
def fibonacci(n):
if n <= 1:
return n
return fibonacci(n - 1) + fibonacci(n - 2)
start = time.time()
result = fibonacci(35)
end = time.time()
print(f"结果: {result}, 耗时: {end - start:.2f}秒")
Rust版本
use std::time::Instant;
fn fibonacci(n: u32) -> u32 {
if n <= 1 {
return n;
}
fibonacci(n - 1) + fibonacci(n - 2)
}
fn main() {
let start = Instant::now();
let result = fibonacci(35);
let duration = start.elapsed();
println!("结果: {}, 耗时: {:.2?}", result, duration);
}
典型性能差异:
- Python: ~3-5秒
- Rust: ~0.1-0.3秒
这展示了编译型语言的性能优势。
1.12 本章小结
通过本章学习,你应该掌握:
- 环境搭建:rustup, cargo的使用
- 项目结构:理解Cargo.toml和标准目录结构
- 基本概念:编译vs解释的区别
- 开发流程:从编写代码到运行程序的完整流程
- 工具对比:Rust工具链vs Python工具链
关键要点:
- Rust是编译型语言,提供更好的性能和类型安全
- Cargo是强大的包管理器和构建工具
- 错误在编译时就被发现,提高代码质量
- 学习曲线较Python陡峭,但带来更多控制权
下一章预告: 我们将深入学习Rust的基础语法和数据类型,重点对比与Python的差异。
第2章:基础语法与数据类型
2.1 变量声明与可变性
这是Rust与Python最重要的区别之一:Rust默认变量不可变,而Python变量默认可变。
Python变量(默认可变)
# Python中变量默认可变
name = "Alice"
name = "Bob" # ✓ 可以重新赋值
age = 25
age = age + 1 # ✓ 可以修改
Rust变量(默认不可变)
// Rust中变量默认不可变
let name = "Alice";
// name = "Bob"; // ✗ 编译错误:cannot assign twice to immutable variable
let age = 25;
// age = age + 1; // ✗ 编译错误
// 使用 mut 关键字声明可变变量
let mut mutable_age = 25;
mutable_age = mutable_age + 1; // ✓ 正确
变量遮蔽 (Shadowing)
Rust允许变量遮蔽,这在Python中没有直接对应概念:
let x = 5;
let x = x + 1; // 创建新变量,遮蔽旧变量
let x = x * 2; // 再次遮蔽
println!("x = {}", x); // 输出:x = 12
// 可以改变类型
let spaces = " ";
let spaces = spaces.len(); // 从&str变为usize
对比Python:
x = 5
x = x + 1 # 直接修改变量
x = x * 2
print(f"x = {x}") # 输出:x = 12
# Python中改变类型需要注意
spaces = " "
spaces = len(spaces) # 类型从str变为int,但运行时才知道
2.2 基础数据类型对比
整数类型
Rust类型 | 大小 | Python等价 | 范围 |
---|---|---|---|
i8 | 8位 | int | -128 到 127 |
i16 | 16位 | int | -32,768 到 32,767 |
i32 | 32位 | int | -2^31 到 2^31-1 |
i64 | 64位 | int | -2^63 到 2^63-1 |
i128 | 128位 | int | -2^127 到 2^127-1 |
isize | 指针大小 | int | 取决于架构 |
u8 | 8位无符号 | int | 0 到 255 |
u16 | 16位无符号 | int | 0 到 65,535 |
u32 | 32位无符号 | int | 0 到 2^32-1 |
u64 | 64位无符号 | int | 0 到 2^64-1 |
u128 | 128位无符号 | int | 0 到 2^128-1 |
usize | 指针大小无符号 | int | 取决于架构 |
// Rust - 显式类型
let small_number: i8 = 127;
let big_number: i64 = 1_000_000;
let hex_number = 0xff; // 255
let octal_number = 0o77; // 63
let binary_number = 0b1111_0000; // 240
// 溢出在debug模式下会panic
let mut overflow: u8 = 255;
// overflow += 1; // panic in debug mode
# Python - 动态类型,自动大整数
small_number = 127
big_number = 1_000_000
hex_number = 0xff
octal_number = 0o77
binary_number = 0b11110000
# Python整数可以任意大
huge_number = 10**100 # 没问题
浮点类型
// Rust有两种浮点类型
let float32: f32 = 3.14;
let float64: f64 = 2.718281828; // 默认类型
// 科学计数法
let scientific = 1e6; // f64类型
# Python只有一种浮点类型(类似f64)
float32 = 3.14
float64 = 2.718281828
# 科学计数法
scientific = 1e6
布尔类型
// Rust布尔类型
let is_true: bool = true;
let is_false = false;
// Rust布尔值不能隐式转换为整数
// let number = is_true + 1; // ✗ 编译错误
let number = is_true as i32 + 1; // ✓ 显式转换
# Python布尔类型
is_true = True
is_false = False
# Python布尔值可以当作整数使用
number = is_true + 1 # 结果为2,因为True == 1
字符类型
// Rust字符类型(4字节Unicode)
let char_a = 'a';
let char_unicode = '中';
let char_emoji = '😀';
// 字符串字面量
let string_literal = "Hello, world!";
# Python没有单独的字符类型
char_a = 'a' # 这是长度为1的字符串
char_unicode = '中'
char_emoji = '😀'
string_literal = "Hello, world!"
2.3 字符串类型详解
这是Rust最复杂的部分之一,因为Rust有多种字符串类型:
Rust字符串类型
// 1. 字符串字面量 &str(不可变,借用)
let string_literal: &str = "Hello, world!";
// 2. String类型(可变,拥有所有权)
let mut owned_string = String::new();
owned_string.push_str("Hello");
owned_string.push(' ');
owned_string.push_str("world!");
// 3. 从字面量创建String
let another_string = String::from("Hello, Rust!");
let yet_another = "Hello, Rust!".to_string();
// 4. 字符串切片
let slice = &another_string[0..5]; // "Hello"
Python字符串(简单统一)
# Python只有一种字符串类型
string_literal = "Hello, world!"
# 字符串操作
owned_string = ""
owned_string += "Hello"
owned_string += " "
owned_string += "world!"
another_string = "Hello, Python!"
# 字符串切片
slice_str = another_string[0:5] # "Hello"
字符串操作对比
// Rust字符串操作
let s1 = String::from("Hello");
let s2 = String::from("World");
// 连接字符串
let s3 = s1 + &s2; // s1被移动,不能再使用
let s4 = format!("{} {}", s2, "Rust"); // 推荐方式
// 字符串比较
let are_equal = s2 == "World";
// 字符串长度
let length = s2.len(); // 字节长度
let char_count = s2.chars().count(); // 字符数量
// 遍历字符
for ch in s2.chars() {
println!("{}", ch);
}
# Python字符串操作
s1 = "Hello"
s2 = "World"
# 连接字符串
s3 = s1 + s2
s4 = f"{s2} Python" # f-string
# 字符串比较
are_equal = s2 == "World"
# 字符串长度
length = len(s2) # 字符数量
# 遍历字符
for ch in s2:
print(ch)
2.4 类型推断与显式类型标注
Rust类型推断
// Rust有强大的类型推断
let number = 42; // 推断为i32
let pi = 3.14; // 推断为f64
let text = "hello"; // 推断为&str
// 但有时需要显式标注
let numbers: Vec<i32> = vec![1, 2, 3];
let parsed: i32 = "42".parse().expect("Not a number");
// 类型后缀
let explicit_float = 3.14f32;
let explicit_int = 42u64;
Python类型提示(可选)
# Python类型提示是可选的
number = 42 # 运行时是int
pi = 3.14 # 运行时是float
text = "hello" # 运行时是str
# 类型提示(仅用于静态检查)
from typing import List
numbers: List[int] = [1, 2, 3]
parsed: int = int("42")
# Python没有类型后缀
explicit_float = float(3.14)
explicit_int = int(42)
2.5 常量与静态变量
Rust常量
// 编译时常量
const MAX_POINTS: u32 = 100_000;
const PI: f64 = 3.14159265359;
// 静态变量(全局)
static HELLO_WORLD: &str = "Hello, world!";
static mut COUNTER: u32 = 0; // 可变静态变量(unsafe)
fn main() {
println!("{}", MAX_POINTS);
println!("{}", HELLO_WORLD);
// 访问可变静态变量需要unsafe
unsafe {
COUNTER += 1;
println!("Counter: {}", COUNTER);
}
}
Python常量(约定)
# Python没有真正的常量,只是约定
MAX_POINTS = 100_000 # 约定不修改
PI = 3.14159265359
# 全局变量
hello_world = "Hello, world!"
counter = 0
def main():
global counter
print(MAX_POINTS)
print(hello_world)
counter += 1 # 可以修改
print(f"Counter: {counter}")
2.6 类型转换
Rust类型转换(显式)
// Rust要求显式类型转换
let integer = 42;
let float = integer as f64; // 数值转换
let back_to_int = float as i32;
// 字符串转换
let number_str = "42";
let parsed_number: i32 = number_str.parse().unwrap();
let number_to_str = parsed_number.to_string();
// TryFrom/TryInto trait(安全转换)
use std::convert::TryFrom;
let big_number: i64 = 1000;
let small_number = i32::try_from(big_number).unwrap();
// From/Into trait(无损转换)
let string_from_str = String::from("hello");
let vec_from_array: Vec<i32> = vec![1, 2, 3];
Python类型转换(隐式+显式)
# Python有更多隐式转换
integer = 42
float_val = float(integer) # 显式转换
result = integer + 3.14 # 隐式转换为float
# 字符串转换
number_str = "42"
parsed_number = int(number_str)
number_to_str = str(parsed_number)
# Python的动态类型转换
big_number = 1000
small_number = big_number # 没有溢出问题
# 自动类型转换
string_from_int = str(42)
list_from_tuple = list((1, 2, 3))
2.7 数组与集合类型入门
固定长度数组
// Rust固定长度数组
let array: [i32; 5] = [1, 2, 3, 4, 5];
let same_value = [3; 5]; // [3, 3, 3, 3, 3]
// 数组访问
let first = array[0];
let slice = &array[1..3]; // 切片:[2, 3]
// 数组长度在编译时确定
println!("Array length: {}", array.len());
# Python没有固定长度数组概念,都是动态列表
array = [1, 2, 3, 4, 5]
same_value = [3] * 5 # [3, 3, 3, 3, 3]
# 列表访问
first = array[0]
slice_list = array[1:3] # [2, 3]
# 列表长度在运行时确定
print(f"List length: {len(array)}")
动态数组Vector
// Rust动态数组
let mut vec = Vec::new();
vec.push(1);
vec.push(2);
vec.push(3);
// 宏创建
let vec2 = vec![1, 2, 3, 4, 5];
// Vector操作
vec.pop(); // 返回Option<T>
let length = vec.len();
vec.clear();
# Python列表(类似Vector)
vec = []
vec.append(1)
vec.append(2)
vec.append(3)
# 列表字面量
vec2 = [1, 2, 3, 4, 5]
# 列表操作
vec.pop() # 返回元素或抛出异常
length = len(vec)
vec.clear()
2.8 元组类型
// Rust元组
let tuple: (i32, f64, &str) = (42, 3.14, "hello");
// 解构
let (x, y, z) = tuple;
println!("x: {}, y: {}, z: {}", x, y, z);
// 访问元素
let first = tuple.0;
let second = tuple.1;
// 单元元组(空元组)
let unit: () = ();
# Python元组
tuple_val = (42, 3.14, "hello")
# 解构
x, y, z = tuple_val
print(f"x: {x}, y: {y}, z: {z}")
# 访问元素
first = tuple_val[0]
second = tuple_val[1]
# 空元组
unit = ()
2.9 实践练习
练习1:温度转换器
// Rust版本
use std::io;
fn celsius_to_fahrenheit(celsius: f64) -> f64 {
celsius * 9.0 / 5.0 + 32.0
}
fn fahrenheit_to_celsius(fahrenheit: f64) -> f64 {
(fahrenheit - 32.0) * 5.0 / 9.0
}
fn main() {
println!("温度转换器");
println!("1. 摄氏度转华氏度");
println!("2. 华氏度转摄氏度");
let mut input = String::new();
println!("请选择转换类型 (1 或 2):");
io::stdin().read_line(&mut input).expect("读取输入失败");
let choice: u32 = input.trim().parse().expect("无效选择");
input.clear();
println!("请输入温度值:");
io::stdin().read_line(&mut input).expect("读取输入失败");
let temperature: f64 = input.trim().parse().expect("无效温度");
match choice {
1 => {
let result = celsius_to_fahrenheit(temperature);
println!("{}°C = {:.2}°F", temperature, result);
},
2 => {
let result = fahrenheit_to_celsius(temperature);
println!("{}°F = {:.2}°C", temperature, result);
},
_ => println!("无效选择"),
}
}
# Python版本
def celsius_to_fahrenheit(celsius):
return celsius * 9 / 5 + 32
def fahrenheit_to_celsius(fahrenheit):
return (fahrenheit - 32) * 5 / 9
def main():
print("温度转换器")
print("1. 摄氏度转华氏度")
print("2. 华氏度转摄氏度")
choice = int(input("请选择转换类型 (1 或 2): "))
temperature = float(input("请输入温度值: "))
if choice == 1:
result = celsius_to_fahrenheit(temperature)
print(f"{temperature}°C = {result:.2f}°F")
elif choice == 2:
result = fahrenheit_to_celsius(temperature)
print(f"{temperature}°F = {result:.2f}°C")
else:
print("无效选择")
if __name__ == "__main__":
main()
练习2:数据类型探索
// Rust版本 - 展示不同数据类型
fn main() {
// 整数类型
let small: i8 = 127;
let medium: i32 = 1_000_000;
let large: i64 = 9_223_372_036_854_775_807;
println!("整数类型:");
println!("i8: {}", small);
println!("i32: {}", medium);
println!("i64: {}", large);
// 浮点类型
let pi: f32 = 3.14159;
let e: f64 = 2.718281828459045;
println!("\n浮点类型:");
println!("f32: {}", pi);
println!("f64: {}", e);
// 字符和字符串
let character = 'R';
let string_slice = "Rust";
let owned_string = String::from("Programming");
println!("\n字符和字符串:");
println!("char: {}", character);
println!("&str: {}", string_slice);
println!("String: {}", owned_string);
// 布尔值
let is_rust_awesome = true;
println!("\n布尔值: {}", is_rust_awesome);
// 数组和元组
let array = [1, 2, 3, 4, 5];
let tuple = (42, "answer", true);
println!("\n集合类型:");
println!("数组: {:?}", array);
println!("元组: {:?}", tuple);
// 类型大小
use std::mem;
println!("\n类型大小(字节):");
println!("i32: {}", mem::size_of::<i32>());
println!("f64: {}", mem::size_of::<f64>());
println!("bool: {}", mem::size_of::<bool>());
println!("char: {}", mem::size_of::<char>());
}
# Python版本 - 展示不同数据类型
import sys
def main():
# 整数类型(Python只有一种int)
small = 127
medium = 1_000_000
large = 9_223_372_036_854_775_807
print("整数类型:")
print(f"small int: {small}")
print(f"medium int: {medium}")
print(f"large int: {large}")
# 浮点类型
pi = 3.14159
e = 2.718281828459045
print("\n浮点类型:")
print(f"float: {pi}")
print(f"float: {e}")
# 字符和字符串
character = 'P'
string_val = "Python"
print("\n字符和字符串:")
print(f"character: {character}")
print(f"string: {string_val}")
# 布尔值
is_python_awesome = True
print(f"\n布尔值: {is_python_awesome}")
# 列表和元组
list_val = [1, 2, 3, 4, 5]
tuple_val = (42, "answer", True)
print("\n集合类型:")
print(f"列表: {list_val}")
print(f"元组: {tuple_val}")
# 对象大小
print("\n对象大小(字节):")
print(f"int: {sys.getsizeof(42)}")
print(f"float: {sys.getsizeof(3.14)}")
print(f"bool: {sys.getsizeof(True)}")
print(f"str: {sys.getsizeof('a')}")
if __name__ == "__main__":
main()
2.10 类型系统对比总结
特性 | Rust | Python |
---|---|---|
类型检查 | 静态,编译时 | 动态,运行时 |
类型推断 | 强大的推断 | 无(解释器推断) |
变量可变性 | 默认不可变 | 默认可变 |
类型转换 | 显式转换 | 隐式+显式转换 |
整数溢出 | 编译时/运行时检查 | 自动大整数 |
字符串类型 | 多种类型 | 单一类型 |
内存安全 | 编译时保证 | 垃圾回收器 |
2.11 常见陷阱与最佳实践
陷阱1:整数溢出
// Rust中要注意溢出
let mut x: u8 = 255;
// x += 1; // Debug模式下panic,Release模式下环绕
// 安全的方式
x = x.saturating_add(1); // 饱和加法,结果为255
// 或者
match x.checked_add(1) {
Some(result) => x = result,
None => println!("溢出了!"),
}
# Python自动处理大整数
x = 255
x += 1 # 没问题,自动变为256
陷阱2:字符串所有权
// 理解字符串所有权
let s1 = String::from("hello");
let s2 = s1; // s1被移动到s2
// println!("{}", s1); // ✗ 编译错误
// 正确的方式
let s1 = String::from("hello");
let s2 = s1.clone(); // 深拷贝
println!("{} {}", s1, s2); // ✓ 正确
最佳实践
- 优先使用类型推断,除非需要明确指定
- 默认使用不可变变量,只在必要时使用
mut
- 优先使用
&str
而不是String
,除非需要所有权 - 使用
checked_*
方法处理可能溢出的算术运算 - 理解borrowing和ownership(下一章详细讲解)
2.12 本章小结
通过本章学习,你应该掌握:
- 变量声明:
let
关键字和可变性概念 - 基础类型:整数、浮点、布尔、字符类型
- 字符串类型:
String
vs&str
的区别 - 类型转换:显式转换的必要性
- 集合类型:数组、Vector、元组的基础使用
关键差异总结:
- Rust是静态类型语言,Python是动态类型
- Rust变量默认不可变,Python变量默认可变
- Rust要求显式类型转换,Python允许更多隐式转换
- Rust有更细粒度的数值类型控制
下一章预告: 我们将学习Rust最核心的概念——所有权系统,这是Rust实现内存安全的关键机制。
第3章:所有权系统 - Rust的核心概念
3.1 什么是所有权系统
所有权(Ownership)是Rust最独特且重要的特性,它使得Rust能够在不使用垃圾回收器的情况下保证内存安全。对于Python开发者来说,这是一个全新的概念,因为Python依靠垃圾回收器自动管理内存。
Python的内存管理
# Python通过引用计数和垃圾回收管理内存
def create_data():
data = [1, 2, 3, 4, 5] # 创建列表
return data # 返回引用
my_data = create_data() # 数据仍然存在,引用计数管理
another_ref = my_data # 增加引用计数
# 当所有引用都消失时,垃圾回收器清理内存
Rust的所有权系统
// Rust通过所有权系统在编译时管理内存
fn create_data() -> Vec<i32> {
let data = vec![1, 2, 3, 4, 5]; // 创建向量
data // 转移所有权给调用者
}
fn main() {
let my_data = create_data(); // 获得所有权
// let another_ref = my_data; // 这会转移所有权,my_data不再可用
// println!("{:?}", my_data); // 编译错误!
// 正确的方式:借用
let another_ref = &my_data; // 借用引用
println!("{:?}", my_data); // 正确,所有权仍在my_data
println!("{:?}", another_ref); // 正确,这是借用
}
3.2 所有权规则
Rust的所有权系统有三个基本规则:
- 每个值都有一个唯一的所有者
- 同一时间只能有一个所有者
- 当所有者离开作用域时,值被自动清理
让我们通过例子来理解这些规则:
规则1:每个值都有唯一所有者
// Rust
let s = String::from("hello"); // s是字符串的所有者
# Python - 没有所有权概念,只有引用
s = "hello" # s是对字符串对象的引用
规则2:同一时间只能有一个所有者
// Rust - 所有权转移
let s1 = String::from("hello");
let s2 = s1; // 所有权从s1转移到s2
// println!("{}", s1); // 编译错误:s1不再拥有所有权
println!("{}", s2); // 正确:s2现在是所有者
# Python - 多个引用可以指向同一对象
s1 = "hello"
s2 = s1 # s1和s2都引用同一个字符串对象
print(s1) # 正常
print(s2) # 正常
规则3:所有者离开作用域时自动清理
// Rust - 自动清理
{
let s = String::from("hello"); // s进入作用域
// 使用s
} // s离开作用域,内存自动被清理(调用drop)
# Python - 垃圾回收器管理
{
s = "hello" # 创建字符串对象
# 使用s
} # s超出作用域,但对象何时被清理由垃圾回收器决定
3.3 移动 (Move) 语义
Rust中的移动
// 移动语义示例
fn take_ownership(s: String) {
println!("Got: {}", s);
// s在函数结束时被清理
}
fn main() {
let s = String::from("hello");
take_ownership(s); // s被移动到函数中
// println!("{}", s); // 编译错误:s已经被移动
}
Python中的引用传递
# Python中传递的是引用
def take_reference(s):
print(f"Got: {s}")
# 对象仍然存在于内存中
def main():
s = "hello"
take_reference(s) # 传递引用
print(s) # 正常:s仍然可用
main()
避免移动:Clone
// 使用Clone避免移动
fn main() {
let s1 = String::from("hello");
let s2 = s1.clone(); // 深拷贝,s1仍然可用
println!("s1: {}", s1); // 正确
println!("s2: {}", s2); // 正确
}
# Python中的浅拷贝和深拷贝
import copy
def main():
s1 = ["hello", "world"]
s2 = s1.copy() # 浅拷贝
s3 = copy.deepcopy(s1) # 深拷贝
print(f"s1: {s1}")
print(f"s2: {s2}")
print(f"s3: {s3}")
main()
3.4 借用 (Borrowing) 和引用
借用允许你使用值而不获取其所有权:
不可变借用
// Rust不可变借用
fn calculate_length(s: &String) -> usize {
s.len() // 可以读取,但不能修改
}
fn main() {
let s1 = String::from("hello");
let len = calculate_length(&s1); // 借用s1的引用
println!("'{}' has length {}", s1, len); // s1仍然可用
}
# Python中函数参数传递对象引用
def calculate_length(s):
return len(s) # 可以读取
def main():
s1 = "hello"
length = calculate_length(s1) # 传递引用
print(f"'{s1}' has length {length}") # s1仍然可用
main()
可变借用
// Rust可变借用
fn append_world(s: &mut String) {
s.push_str(", world!");
}
fn main() {
let mut s = String::from("hello");
append_world(&mut s); // 可变借用
println!("{}", s); // 输出:hello, world!
}
# Python中可变对象的修改
def append_world(s):
s.append(", world!") # 直接修改列表
def main():
s = ["hello"]
append_world(s) # 传递引用,函数内可以修改
print(s) # 输出:['hello', ', world!']
main()
3.5 借用规则
Rust的借用有严格的规则来防止数据竞争:
- 可以有任意数量的不可变引用
- 只能有一个可变引用
- 不可变引用和可变引用不能同时存在
规则演示
// Rust借用规则
fn main() {
let mut s = String::from("hello");
// 多个不可变引用 - 正确
let r1 = &s;
let r2 = &s;
println!("{} and {}", r1, r2);
// 可变引用 - 正确(不可变引用已经不再使用)
let r3 = &mut s;
r3.push_str(", world!");
println!("{}", r3);
// 以下会编译错误:
// let r4 = &s; // 不能在可变引用存在时创建不可变引用
// let r5 = &mut s; // 不能有两个可变引用
}
# Python没有这种借用限制
def main():
s = ["hello"]
# 可以有多个引用指向同一对象
r1 = s
r2 = s
r3 = s
# 所有引用都可以修改对象(如果对象是可变的)
r1.append("world")
r2.append("from")
r3.append("Python")
print(s) # 输出:['hello', 'world', 'from', 'Python']
main()
3.6 生命周期 (Lifetimes)
生命周期确保引用在需要时一直有效:
基本生命周期概念
// Rust生命周期
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() {
x
} else {
y
}
}
fn main() {
let string1 = String::from("long string is long");
{
let string2 = String::from("xyz");
let result = longest(string1.as_str(), string2.as_str());
println!("The longest string is {}", result);
}
// string2在这里已经被清理,但result不能引用它
}
# Python不需要显式的生命周期管理
def longest(x, y):
if len(x) > len(y):
return x
else:
return y
def main():
string1 = "long string is long"
# 这个作用域在Python中不重要
string2 = "xyz"
result = longest(string1, string2)
print(f"The longest string is {result}")
# 即使string2"超出作用域",在Python中对象仍然存在
# 因为result仍然引用它
main()
3.7 切片 (Slices)
切片是对集合中一段连续元素的引用:
字符串切片
// Rust字符串切片
fn first_word(s: &String) -> &str {
let bytes = s.as_bytes();
for (i, &item) in bytes.iter().enumerate() {
if item == b' ' {
return &s[0..i];
}
}
&s[..] // 返回整个字符串的切片
}
fn main() {
let s = String::from("hello world");
let word = first_word(&s);
println!("First word: {}", word);
// 字符串切片语法
let hello = &s[0..5]; // "hello"
let world = &s[6..11]; // "world"
let full = &s[..]; // "hello world"
}
# Python字符串切片
def first_word(s):
words = s.split(' ')
return words[0] if words else ""
def main():
s = "hello world"
word = first_word(s)
print(f"First word: {word}")
# Python切片语法
hello = s[0:5] # "hello"
world = s[6:11] # "world"
full = s[:] # "hello world"
main()
数组切片
// Rust数组切片
fn main() {
let a = [1, 2, 3, 4, 5];
let slice = &a[1..3]; // [2, 3]
println!("Array: {:?}", a);
println!("Slice: {:?}", slice);
// 切片是借用,不拥有数据
for element in slice {
println!("Element: {}", element);
}
}
# Python列表切片
def main():
a = [1, 2, 3, 4, 5]
slice_list = a[1:3] # [2, 3]
print(f"List: {a}")
print(f"Slice: {slice_list}")
# Python切片创建新列表
for element in slice_list:
print(f"Element: {element}")
main()
3.8 所有权与函数
函数参数的所有权转移
// Rust函数所有权示例
fn takes_ownership(some_string: String) {
println!("{}", some_string);
} // some_string离开作用域并被drop
fn makes_copy(some_integer: i32) {
println!("{}", some_integer);
} // some_integer离开作用域,但i32是Copy类型,所以原值仍然有效
fn gives_ownership() -> String {
let some_string = String::from("hello");
some_string // 返回some_string,将所有权转移给调用者
}
fn takes_and_gives_back(a_string: String) -> String {
a_string // a_string被返回,所有权转移给调用者
}
fn main() {
let s = gives_ownership(); // gives_ownership将所有权转移给s
let s2 = String::from("hello"); // s2进入作用域
let s3 = takes_and_gives_back(s2); // s2被移动到函数中,函数返回值移动给s3
// println!("{}", s2); // 错误:s2的值已经被移动
println!("{}", s3); // 正确:s3拥有所有权
let x = 5; // x进入作用域
makes_copy(x); // x被传递给函数,但i32是Copy类型
println!("{}", x); // 正确:x仍然有效
}
# Python函数参数传递
def takes_reference(some_string):
print(some_string)
# 对象仍然存在,只是引用被传递
def makes_copy(some_integer):
print(some_integer)
# 整数是不可变的,实际上是传递值
def gives_reference():
some_string = "hello"
return some_string # 返回对字符串的引用
def takes_and_gives_back(a_string):
return a_string # 返回相同的引用
def main():
s = gives_reference() # s获得对字符串的引用
s2 = "hello" # s2引用字符串
s3 = takes_and_gives_back(s2) # s2仍然有效,s3引用相同字符串
print(s2) # 正确:s2仍然有效
print(s3) # 正确:s3引用字符串
x = 5 # x绑定到整数5
makes_copy(x) # 传递整数值
print(x) # 正确:x仍然有效
main()
3.9 引用与借用的高级用法
引用的引用
// Rust引用的引用
fn main() {
let s = String::from("hello");
let r1 = &s; // &String
let r2 = &r1; // &&String
println!("s: {}", s);
println!("r1: {}", r1);
println!("r2: {}", r2); // 自动解引用
}
# Python中所有的都是引用
def main():
s = "hello"
r1 = s # 引用同一个字符串对象
r2 = r1 # 引用同一个字符串对象
print(f"s: {s}")
print(f"r1: {r1}")
print(f"r2: {r2}")
main()
解引用操作符
// Rust解引用
fn main() {
let x = 5;
let y = &x; // y是x的引用
assert_eq!(5, x);
assert_eq!(5, *y); // 解引用y来获取值
// 以下会编译错误:
// assert_eq!(5, y); // 不能比较i32和&i32
}
# Python不需要显式解引用
def main():
x = 5
y = x # y引用相同的整数对象
assert x == 5
assert y == 5 # 直接比较,不需要解引用
# Python中变量总是引用,不需要显式解引用操作
main()
3.10 Copy trait 和 Clone trait
Copy trait(自动拷贝)
// Copy trait - 栈上数据的按位复制
fn main() {
let x = 5; // i32实现了Copy
let y = x; // x被复制到y,x仍然有效
println!("x: {}, y: {}", x, y); // 都可以使用
// 实现Copy的类型:
// - 所有整数类型(i32, u32等)
// - 布尔类型(bool)
// - 所有浮点类型(f32, f64)
// - 字符类型(char)
// - 仅包含Copy类型的元组
}
Clone trait(显式拷贝)
// Clone trait - 可能开销较大的深拷贝
fn main() {
let s1 = String::from("hello");
let s2 = s1.clone(); // 显式拷贝
println!("s1: {}, s2: {}", s1, s2); // 都可以使用
// 自定义类型的Clone实现
#[derive(Clone)]
struct Point {
x: i32,
y: i32,
}
let p1 = Point { x: 1, y: 2 };
let p2 = p1.clone(); // 克隆Point
println!("p1: ({}, {})", p1.x, p1.y);
println!("p2: ({}, {})", p2.x, p2.y);
}
# Python中的浅拷贝和深拷贝
import copy
def main():
# 不可变对象(整数、字符串)的"拷贝"
x = 5
y = x # 实际上引用同一对象(因为整数不可变)
print(f"x: {x}, y: {y}")
# 可变对象的拷贝
s1 = ["hello"]
s2 = s1.copy() # 浅拷贝
s3 = copy.deepcopy(s1) # 深拷贝
print(f"s1: {s1}, s2: {s2}, s3: {s3}")
# 自定义类的拷贝
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
p1 = Point(1, 2)
p2 = copy.copy(p1) # 浅拷贝
p3 = copy.deepcopy(p1) # 深拷贝
print(f"p1: ({p1.x}, {p1.y})")
print(f"p2: ({p2.x}, {p2.y})")
print(f"p3: ({p3.x}, {p3.y})")
main()
3.11 实践练习
练习1:所有权和借用
// Rust练习:字符串处理
fn process_string(s: &str) -> String {
let mut result = String::new();
for ch in s.chars() {
if ch.is_alphabetic() {
result.push(ch.to_uppercase().next().unwrap());
}
}
result
}
fn main() {
let original = String::from("hello, world! 123");
let processed = process_string(&original); // 借用
println!("Original: {}", original); // original仍然可用
println!("Processed: {}", processed);
// 练习:修改函数以接受String而不是&str
fn take_ownership(s: String) -> String {
s.to_uppercase()
}
let another = String::from("rust programming");
let result = take_ownership(another);
// println!("{}", another); // 错误:another已被移动
println!("Result: {}", result);
}
# Python练习:字符串处理
def process_string(s):
result = ""
for ch in s:
if ch.isalpha():
result += ch.upper()
return result
def main():
original = "hello, world! 123"
processed = process_string(original) # 传递引用
print(f"Original: {original}") # original仍然可用
print(f"Processed: {processed}")
# Python版本不需要考虑所有权
def take_reference(s):
return s.upper()
another = "rust programming"
result = take_reference(another)
print(another) # another仍然可用
print(f"Result: {result}")
main()
练习2:Vector操作
// Rust练习:Vector所有权
fn analyze_numbers(numbers: &[i32]) -> (i32, i32, f64) {
let min = *numbers.iter().min().unwrap();
let max = *numbers.iter().max().unwrap();
let avg = numbers.iter().sum::<i32>() as f64 / numbers.len() as f64;
(min, max, avg)
}
fn take_and_modify(mut numbers: Vec<i32>) -> Vec<i32> {
numbers.push(100);
numbers.sort();
numbers
}
fn main() {
let numbers = vec![3, 1, 4, 1, 5, 9, 2, 6];
// 借用进行分析
let (min, max, avg) = analyze_numbers(&numbers);
println!("Min: {}, Max: {}, Average: {:.2}", min, max, avg);
println!("Original: {:?}", numbers); // numbers仍然可用
// 转移所有权进行修改
let modified = take_and_modify(numbers);
// println!("{:?}", numbers); // 错误:numbers已被移动
println!("Modified: {:?}", modified);
}
# Python练习:列表操作
def analyze_numbers(numbers):
min_val = min(numbers)
max_val = max(numbers)
avg = sum(numbers) / len(numbers)
return min_val, max_val, avg
def take_and_modify(numbers):
numbers = numbers.copy() # 手动拷贝以避免修改原列表
numbers.append(100)
numbers.sort()
return numbers
def main():
numbers = [3, 1, 4, 1, 5, 9, 2, 6]
# 传递引用进行分析
min_val, max_val, avg = analyze_numbers(numbers)
print(f"Min: {min_val}, Max: {max_val}, Average: {avg:.2f}")
print(f"Original: {numbers}") # numbers仍然可用
# Python中需要手动决定是否拷贝
modified = take_and_modify(numbers)
print(f"Original after: {numbers}") # numbers未被修改
print(f"Modified: {modified}")
main()
3.12 所有权系统的优势
内存安全
// Rust在编译时防止内存错误
fn rust_memory_safety() {
let s = String::from("hello");
let r = &s;
// drop(s); // 编译错误:不能在有借用时释放
println!("{}", r); // 安全:借用仍然有效
}
# Python在运行时可能出现问题
import weakref
def python_memory_issues():
class MyClass:
def __init__(self, value):
self.value = value
obj = MyClass("hello")
weak_ref = weakref.ref(obj)
del obj # 删除强引用
if weak_ref() is not None:
print(weak_ref().value) # 可能出错:对象可能已被回收
else:
print("Object was garbage collected")
零成本抽象
// Rust的所有权检查发生在编译时,运行时无额外开销
fn zero_cost_abstractions() {
let v = vec![1, 2, 3, 4, 5];
// 这些借用在运行时没有额外开销
let slice1 = &v[0..2];
let slice2 = &v[2..];
println!("Slice1: {:?}", slice1);
println!("Slice2: {:?}", slice2);
}
3.13 常见错误和解决方案
错误1:使用已移动的值
// 常见错误
fn common_mistake() {
let s = String::from("hello");
let s2 = s; // s被移动
// println!("{}", s); // 编译错误
}
// 解决方案1:克隆
fn solution1() {
let s = String::from("hello");
let s2 = s.clone(); // 克隆而不是移动
println!("s: {}, s2: {}", s, s2);
}
// 解决方案2:借用
fn solution2() {
let s = String::from("hello");
let s2 = &s; // 借用而不是移动
println!("s: {}, s2: {}", s, s2);
}
错误2:生命周期不匹配
// 常见错误
fn lifetime_error() -> &str {
let s = String::from("hello");
// &s[..] // 编译错误:返回局部变量的引用
"hello" // 字符串字面量有'static生命周期,所以这样是可以的
}
// 解决方案:返回拥有所有权的值
fn lifetime_solution() -> String {
let s = String::from("hello");
s // 返回所有权
}
3.14 本章小结
通过本章学习,你应该掌握:
- 所有权概念:每个值有唯一所有者,所有者离开作用域时值被清理
- 移动语义:值的所有权可以转移,原所有者不再可用
- 借用系统:可以借用值的引用而不获取所有权
- 生命周期:确保引用在需要时始终有效
- Copy和Clone:理解何时发生拷贝,何时需要显式克隆
与Python的关键差异:
特性 | Rust | Python |
---|---|---|
内存管理 | 编译时所有权系统 | 运行时垃圾回收 |
变量赋值 | 可能移动或拷贝 | 总是引用赋值 |
函数参数 | 所有权转移或借用 | 引用传递 |
内存安全 | 编译时保证 | 运行时检查 |
性能开销 | 零运行时开销 | 垃圾回收开销 |
关键要点:
- 所有权系统在编译时就能防止内存错误
- 借用允许安全地共享数据而不转移所有权
- 理解何时使用移动、借用或克隆是关键
- 生命周期确保引用的安全性
下一章预告: 我们将学习Rust的控制流和函数,包括模式匹配等强大特性。