Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

English Original

17.1 避免过度使用 clone() 🟢

在 Rust 中,调用 .clone() 会显式地克隆数据。虽然这有时是必要的,但过度使用克隆会导致性能问题,并通常暗示所有权设计上存在隐患。

1. 为什么 clone() 的开销可能很大

克隆 StringVec<T> 或大型结构体涉及到在堆上分配内存并拷贝内容。这比传递一个引用要慢得多。

fn process_string(s: String) {
    println!("{}", s);
}

fn main() {
    let s = String::from("一段非常长的字符串...");
    
    // 不良实践:克隆以使 `s` 在 main 函数中仍可用
    process_string(s.clone());
    println!("依然持有 s:{}", s);
}

2. 优先使用借用 (Borrowing)

与其克隆,不如将函数改为接收引用 (&T)。这允许函数在不获取所有权或无需克隆的情况下使用数据。

fn process_string(s: &str) {
    println!("{}", s);
}

fn main() {
    let s = String::from("一段非常长的字符串...");
    
    // 良好实践:传递一个引用
    process_string(&s);
    println!("依然持有 s:{}", s);
}

3. 使用 Arc 用于共享所有权

如果你确实需要程序的多个部分共同拥有同一份数据(例如跨多个线程),请使用 Arc<T> (原子引用计数)。克隆 Arc 只会增加引用计数,这比克隆数据本身要廉价得多。

use std::sync::Arc;
use std::thread;

fn main() {
    let data = Arc::new(vec![1, 2, 3, 4, 5]);

    let mut handles = vec![];
    for _ in 0..3 {
        let data_ref = Arc::clone(&data); // 廉价的克隆
        let handle = thread::spawn(move || {
            println!("{:?}", data_ref);
        });
        handles.push(handle);
    }

    for handle in handles {
        handle.join().unwrap();
    }
}

4. 何时使用 clone() 是恰当的

  • 当将数据移动到需要所有权的线程或闭包中时。
  • 当你确实需要一份独立的、可供修改的数据副本时。
  • 对于小型 Copy 类型(整数、布尔值等),clone() 等同于简单的赋值,几乎没有开销。

对于 C/C++ 开发者的总结

  • 在 C++ 中:你可能会依靠“拷贝消除 (copy elision)”或“返回值优化 (RVO)”,但如果不查看生成的汇编代码或使用调试器,通常很难判断何时实际发生了拷贝。
  • 在 Rust 中:拷贝(克隆)始终是显式的。如果你在代码中看到 .clone(),这说明正在发生一个潜在开销较大的操作。这种透明性有助于你识别并消除不必要的开销。