Rust的移动语义

在 Rust 中,默认是移动语义,而不是传统的值传递或引用传递。这是 Rust 最重要的特性之一,理解所有权系统很关键。

  1. 基本规则
rust 复制代码
fn main() {
    let s1 = String::from("hello");  // s1 拥有字符串
    let s2 = s1;                     // 所有权从 s1 移动到 s2
    
    // println!("{}", s1);  // 编译错误!s1 不再有效
    println!("{}", s2);      // 正确:s2 现在拥有字符串
}
  1. 函数参数传递

默认是移动(对于有所有权的类型):

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 仍然有效
}
  1. 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 仍然有效
}
  1. 不同类型的行为对比
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()
}
  1. 实际示例(字母异位词分组中的所有权处理)
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);
}
  1. 所有权在 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
    }
}
  1. 性能考虑
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);  // 高效:不需要克隆
}
  1. 总结表格

传递方式 语法 所有权 是否需要 clone 适用场景

移动所有权 func(s: String) 转移 不需要 函数需要拥有数据的所有权

不可变借用 func(s: &String) 不转移 通常不需要 只读访问

可变借用 func(s: &mut String) 不转移 不需要 需要修改数据

克隆 func(s.clone()) 创建新所有权 需要 需要副本同时保留原数据

  1. 在字母异位词分组中的最佳实践
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);
}

关键点:

  1. Rust 默认是移动语义,不是值传递也不是引用传递
  2. 实现了 Copy trait 的类型会被复制
  3. 使用 & 进行借用(引用传递)
  4. 函数签名决定了所有权如何转移
  5. 在性能敏感的场景,合理使用引用避免不必要的克隆
相关推荐
Peter·Pan爱编程2 小时前
cmake 升级
c++·cmake·cuda
YouEmbedded2 小时前
函数模板与类模板——泛型编程
开发语言·c++·函数模板·类模板
小此方2 小时前
Re:从零开始学C++(一)基础精讲·上篇:命名空间、输入输出、缺省参数、函数重载
开发语言·c++
行云流水20002 小时前
编程竞赛语言选择:为什么优先学C++?聚焦竞赛属性的语法突破
开发语言·c++
不穿格子的程序员2 小时前
从零开始写算法——链表篇:相交链表 + 反转链表
数据结构·算法·链表
仰泳的熊猫2 小时前
1132 Cut Integer
数据结构·c++·算法·pat考试
aini_lovee2 小时前
基于边缘图像分割算法详解与MATLAB实现
开发语言·算法·matlab
Mr_WangAndy3 小时前
C++数据结构与算法_数据结构与算法概念_时间复杂度
c++·c++数据结构与算法·时间复杂度分析
拼好饭和她皆失3 小时前
高效算法的秘诀:滑动窗口(尺取法)全解析
数据结构·算法·滑动窗口·尺取法