在 Rust 中,默认是移动语义,而不是传统的值传递或引用传递。这是 Rust 最重要的特性之一,理解所有权系统很关键。
- 基本规则
rust
fn main() {
let s1 = String::from("hello"); // s1 拥有字符串
let s2 = s1; // 所有权从 s1 移动到 s2
// println!("{}", s1); // 编译错误!s1 不再有效
println!("{}", s2); // 正确:s2 现在拥有字符串
}
- 函数参数传递
默认是移动(对于有所有权的类型):
rust
fn take_ownership(s: String) { // s 进入作用域
println!("{}", s);
} // s 离开作用域,drop 被调用,内存被释放
fn main() {
let s = String::from("hello");
take_ownership(s); // s 的所有权移动到函数中
// println!("{}", s); // 编译错误!s 不再有效
}
使用引用传递(借用):
rust
fn borrow_string(s: &String) { // s 是对 String 的引用
println!("{}", s);
} // s 离开作用域,但因为它不拥有所有权,所以什么也不会发生
fn main() {
let s = String::from("hello");
borrow_string(&s); // 传递引用,不转移所有权
println!("{}", s); // 正确:s 仍然有效
}
- Copy 类型的值传递
对于实现了 Copy trait 的类型,会自动复制而不是移动:
rust
fn copy_value(x: i32) { // i32 实现了 Copy
println!("{}", x);
}
fn main() {
let x = 5;
copy_value(x); // x 被复制到函数中
println!("{}", x); // 正确:x 仍然有效
}
- 不同类型的行为对比
rust
fn test_pass_by(mut s: String, n: i32, v: Vec<i32>) {
s.push_str(" world");
println!("函数内: s={}, n={}, v={:?}", s, n, v);
}
fn main() {
let s = String::from("hello");
let n = 42;
let v = vec![1, 2, 3];
test_pass_by(s, n, v.clone()); // s 被移动,n 被复制,v 被克隆
// println!("{}", s); // 错误:s 被移动了
println!("{}", n); // 正确:n 被复制了
println!("{:?}", v); // 正确:使用了 clone()
}
- 实际示例(字母异位词分组中的所有权处理)
rust
use std::collections::HashMap;
// 版本1:移动所有权
fn group_anagrams_take(strs: Vec<String>) -> Vec<Vec<String>> {
let mut map = HashMap::new();
for s in strs { // strs 的所有权被移动到循环中
let mut chars: Vec<char> = s.chars().collect();
chars.sort();
let key: String = chars.into_iter().collect();
// 这里 s 被移动到 map 中
map.entry(key).or_insert(Vec::new()).push(s);
}
map.into_values().collect()
}
// 版本2:使用引用(借用)
fn group_anagrams_borrow(strs: &[String]) -> Vec<Vec<String>> {
let mut map = HashMap::new();
for s in strs { // s 是 &String
let mut chars: Vec<char> = s.chars().collect();
chars.sort();
let key: String = chars.into_iter().collect();
// 需要克隆字符串,因为 map 需要所有权
map.entry(key).or_insert(Vec::new()).push(s.clone());
}
map.into_values().collect()
}
// 版本3:使用字符串切片
fn group_anagrams_slice(strs: &[&str]) -> Vec<Vec<String>> {
let mut map = HashMap::new();
for &s in strs { // s 是 &str
let mut chars: Vec<char> = s.chars().collect();
chars.sort();
let key: String = chars.into_iter().collect();
// 转换为 String
map.entry(key).or_insert(Vec::new()).push(s.to_string());
}
map.into_values().collect()
}
fn main() {
// 测试版本1
let strs1 = vec![
"eat".to_string(),
"tea".to_string(),
"tan".to_string(),
];
let result1 = group_anagrams_take(strs1);
println!("版本1: {:?}", result1);
// 测试版本2
let strs2 = vec![
"eat".to_string(),
"tea".to_string(),
"tan".to_string(),
];
let result2 = group_anagrams_borrow(&strs2);
println!("版本2: {:?}", result2);
println!("原数组仍然可用: {:?}", strs2); // strs2 仍然有效
// 测试版本3
let strs3 = vec!["eat", "tea", "tan"];
let result3 = group_anagrams_slice(&strs3);
println!("版本3: {:?}", result3);
}
- 所有权在 HashMap 中的体现
rust
use std::collections::HashMap;
fn main() {
let mut map = HashMap::new();
// 插入时移动所有权
let key1 = String::from("key1");
let value1 = String::from("value1");
map.insert(key1, value1);
// key1 和 value1 不再有效
// println!("{}", key1); // 错误
// println!("{}", value1); // 错误
// 获取引用
if let Some(v) = map.get("key1") {
println!("值: {}", v); // v 是 &String
}
// 获取所有权(移除元素)
if let Some(v) = map.remove("key1") {
println!("移除的值: {}", v); // v 是 String
}
}
- 性能考虑
rust
fn expensive_to_clone() -> String {
"这是一个很长的字符串...".repeat(1000)
}
fn main() {
let s = expensive_to_clone();
// 方法1:移动所有权(高效)
fn process_owned(data: String) {
// 处理数据
}
process_owned(s); // 高效:只是移动指针
// 方法2:克隆(低效)
let s2 = expensive_to_clone();
fn process_cloned(data: &String) -> String {
// 如果需要修改并返回,可能需要克隆
data.clone() + " modified"
}
let result = process_cloned(&s2); // 需要克隆整个字符串
// 方法3:修改可变引用(高效)
let mut s3 = expensive_to_clone();
fn process_mutable(data: &mut String) {
data.push_str(" modified");
}
process_mutable(&mut s3); // 高效:不需要克隆
}
- 总结表格
传递方式 语法 所有权 是否需要 clone 适用场景
移动所有权 func(s: String) 转移 不需要 函数需要拥有数据的所有权
不可变借用 func(s: &String) 不转移 通常不需要 只读访问
可变借用 func(s: &mut String) 不转移 不需要 需要修改数据
克隆 func(s.clone()) 创建新所有权 需要 需要副本同时保留原数据
- 在字母异位词分组中的最佳实践
rust
use std::collections::HashMap;
// 最佳实践:根据需求选择合适的传递方式
pub fn group_anagrams(strs: Vec<String>) -> Vec<Vec<String>> {
// strs 的所有权被移动到函数中
// 因为我们需要在 HashMap 中存储这些字符串
let mut map: HashMap<String, Vec<String>> = HashMap::new();
for s in strs { // 移动每个字符串的所有权
let key = sort_string(&s); // 传递引用给辅助函数
map.entry(key).or_insert_with(Vec::new).push(s);
}
map.into_values().collect()
}
// 辅助函数:只需要读取字符串,不需要所有权
fn sort_string(s: &str) -> String {
let mut chars: Vec<char> = s.chars().collect();
chars.sort_unstable();
chars.into_iter().collect()
}
fn main() {
let strs = vec![
"eat".to_string(),
"tea".to_string(),
"tan".to_string(),
"ate".to_string(),
"nat".to_string(),
"bat".to_string(),
];
// 调用后,strs 的所有权被转移,不能再使用
let result = group_anagrams(strs);
println!("结果: {:?}", result);
}
关键点:
- Rust 默认是移动语义,不是值传递也不是引用传递
- 实现了 Copy trait 的类型会被复制
- 使用 & 进行借用(引用传递)
- 函数签名决定了所有权如何转移
- 在性能敏感的场景,合理使用引用避免不必要的克隆