rust语言学习笔记(指针十一)Cow<T>(写时克隆)

Cow 是一种写时克隆(Clone on Write)的智能指针,它在需要"可能修改也可能不修改"的数据时,能帮你避免不必要的内存分配。它的核心思想是:‌只读时直接借用,需要修改时才克隆一份属于自己的数据‌。

  • 内存体积 ‌:Cow<str>&str 大(需要容纳 String 的三个指针),但通常这种代价可以忽略。
  • 适用条件 ‌:Cow 只有在"读"远多于"写"时才真正带来收益。如果几乎所有调用都会触发修改,直接使用 StringVec 会更合适,因为 Cowmatch 检查会带来轻微开销。
  • 不是线程安全的 ‌:Cow 没有实现 Sync,不能在多线程中共享。若需要线程安全的写时复制,可考虑 Arc + 其他机制。
  • Cell / RefCell 的区别 ‌:Cow 解决的是"借用 vs 所有权"的选择问题,而 CellRefCell 解决的是"内部可变性"问题。它们属于不同维度的工具。

Cow 是一种‌零成本抽象‌的典范:它让你在编写 API 时,不必为了潜在的修改而预先支付克隆的代价。当数据不需要修改时,它只是一个透明的引用;当修改发生时,它才在最后时刻执行克隆。这种"延迟克隆"的策略,在处理字符串、切片、配置文件等读多写少的场景中,可以带来显著的性能提升。

如果你在设计中遇到了"可能修改也可能不修改"的矛盾,不妨考虑使用 Cow,它会让你在借用和所有权之间优雅地取得平衡。

11.1 条件修改---函数返回值的读写分离

不用修改时,直接返回 Cow::Borrowed,零开销;真正需要修改时,才返回 Cow::Owned,只在这时分配内存。

rust 复制代码
use std::borrow::Cow;

/// 去除字符串中的所有空格
fn trim_string(input: &str) -> Cow<'_, str> {
    if input.contains(" ") {
        // 如果包含空格,返回一个新字符串,并替换掉所有空格
        Cow::Owned(input.replace(" ", ""))
    } else {
        Cow::Borrowed(input) // 如果不包含空格,直接返回输入字符串的引用
    }
}
fn main() {
    let input = "hello world";
    let trimmed = trim_string(input);
    println!("{}", trimmed);
}

11.2 切片与缓冲区处理

Cow<[u8]> 在处理网络包或文件解析时非常有用

rust 复制代码
use std::borrow::Cow;

/// 去除字符串中的所有空格
fn process_packet(data: &[u8]) -> Cow<[u8]> {
    
    // 检查是否为压缩包,压缩包的前缀为0x01
    if data.len() > 0 && data.first() == Some(&0x01) {
        
        // 模拟解压过程
        // // 实际场景:调用解压库
        // let decompressed = zlib::decompress(data)?;
        // // 或者
        // let decompressed = lz4::decode(data)?;
        let d = vec![10, 20, 30];
        
        Cow::Owned(d)
    } else {
        Cow::Borrowed(data)     // 如果不是压缩包,直接返回输入数据的引用
    }
}

fn main() {
    let data = [0x01, 0x05, 0x09, 0x0f];
    let trimmed = process_packet(&data);
    println!("{:?}", trimmed);
}

11.3 结构体字段中的零拷贝反序列化

在配置解析、JSON 反序列化等场景中,结构体字段既可能是从输入缓冲区直接借用的,也可能是需要转义后重新分配的。通过 Cow,可以做到"能借用就借用,不能借用再克隆",实现零拷贝优化。

rust 复制代码
use serde::Deserialize;
use std::borrow::Cow;

#[derive(Debug, Deserialize)]
struct User<'a> {
    #[serde(borrow)]    // 优先借用,而非直接分配 String
    name: Cow<'a, str>, // 用于在运行时选择是借用还是分配,'a 表示 name 的生命周期,必须与 User 实例的生命周期相同
    age: u8,
}

fn main() {
    // ========== 情况一:无转义字符 ==========
    let json_no_escape = r#"{"name": "Alice", "age": 30}"#;

    // from_str 借用输入缓冲区的字节
    let user: User = serde_json::from_str(json_no_escape).unwrap();
    println!("[无转义] 解析结果: {:?}", user);
    match user.name {
        Cow::Borrowed(s) => println!("          -> name 是 Borrowed(\"{}\"),零分配!", s),
        Cow::Owned(_) => unreachable!(),
    }
    // json_no_escape 的生命周期必须覆盖 user,因为 user 借用了它
    drop(user);

    // ========== 情况二:有转义字符 ==========
    let json_with_escape = r#"{"name": "Al\u0069ce", "age": 30}"#;
    // \u0069 是 'i' 的 Unicode 转义,Serde 必须解码它,因此会分配 String

    let user2: User = serde_json::from_str(json_with_escape).unwrap();
    println!("\n[有转义] 解析结果: {:?}", user2);
    match user2.name {
        Cow::Owned(ref s) => println!("          -> name 是 Owned(\"{}\"),发生了克隆/分配", s),
        Cow::Borrowed(_) => unreachable!(),
    }
}

11.4 延迟修改---to_mut() 的惰性克隆

批量处理数组中的负数取绝对值:

rust 复制代码
use std::borrow::Cow;

/// 将切片中的所有负数取绝对值
/// 如果没有负数,返回 Borrowed(零拷贝)
/// 如果有负数,返回 Owned(发生一次克隆)
fn abs_all(input: &[i32]) -> Cow<[i32]> {
    // 1. 初始状态:直接借用输入数据,零成本
    let mut cow = Cow::Borrowed(input);

    // 2. 遍历数据
    for i in 0..cow.len() {
        if cow[i] < 0 {
            // 3. 【关键步骤】惰性克隆发生在这里!
            // - 第一次遇到负数时:cow 是 Borrowed,to_mut() 会克隆数据变成 Owned,并返回 &mut Vec<i32>
            // - 后续遇到负数时:cow 已经是 Owned,to_mut() 直接返回 &mut Vec<i32>,不再克隆
            cow.to_mut()[i] = -cow[i];
        }
    }

    cow
}

fn main() {
    // --- 测试案例 1:无需修改 ---
    let data_clean = vec![1, 2, 3, 4];
    let result1 = abs_all(&data_clean);
    
    match result1 {
        Cow::Borrowed(_) => println!("案例1: 状态为 Borrowed (零分配)"),
        Cow::Owned(_)    => println!("案例1: 状态为 Owned"),
    }
    // 输出: 案例1: 状态为 Borrowed (零分配)


    // --- 测试案例 2:需要修改 ---
    let data_dirty = vec![1, -2, 3, -4];
    let result2 = abs_all(&data_dirty);
    
    match result2 {
        Cow::Borrowed(_) => println!("案例2: 状态为 Borrowed"),
        Cow::Owned(ref v) => println!("案例2: 状态为 Owned, 数据: {:?}", v),
    }
    // 输出: 案例2: 状态为 Owned, 数据: [1, 2, 3, 4]
}
相关推荐
aaaameliaaa1 小时前
计算斐波那契数(递归、迭代)(1,1,2,3,5.....)
c语言·开发语言·笔记·算法·排序算法
Turbo正则2 小时前
群论学习入门 | 群论与李群的基本概念
人工智能·学习·算法·抽象代数
毛丫讲绘本2 小时前
0-3岁选绘本需要做到越早启蒙越要简单
人工智能·学习·微信·微信公众平台·微信开放平台
小c君tt2 小时前
linux学习笔记1
linux·笔记·学习
吃好睡好便好3 小时前
泰戈尔的诗歌6
学习·生活
双吉堡3 小时前
北京通州有哪些热门且专业的学画画画室?
学习
ysu_03143 小时前
高数期末复习笔记
笔记
疯狂打码的少年3 小时前
【操作系统】段式存储管理与段页式存储管理
笔记
Go-higher4 小时前
DriverTest 驾考知识卡片学习助手 —— 一款基于 Jetpack Compose 的现代 Android 学习APP
android·学习
星幻元宇VR4 小时前
公共安全主题展厅设备【防洪防汛安全科普系统】
科技·学习·安全