Rust 字符串性能:String、&str、Cow 的选择不是随便来的

Rust字符串操作全解析:从String与&str区别到高效处理方法 在系统编程中,字符串处理是基础且关键的技术环节,涉及内存管理、编码格式和性能优化等多个核心概念。Rust语言通过String和&str两种类型的协作设计,实现了内存安全与零成本抽象的平衡。String作为可变的、拥有所有权的堆分配缓冲区,适用于需要修改或长期持有的场景;而&str作为不可变的字符串切片,提供了轻量级的文本视图,常用于函数参数传递和临时引用。这种设计不仅确保了UTF-8编码的安全性,还通过所有权机制避免了常见的内存错误。在实际开发中,理解两者的转换时机、掌握format!宏的灵活拼接、以及合理 阅读详情

Rust 字符串性能:String、&str、Cow 的选择不是随便来的

一、String vs &str:所有权决定一切

这是最基础的分水岭。String 是拥有所有权的、堆上分配的、可变的字符串;&str 是借用的、不可变的字符串切片。

// String —— 拥有数据,可以修改
let mut owned: String = "hello".to_string();
owned.push_str(" world!");     // ✅ 可以修改
println!("{}", owned);         // ✅ 可以自由使用,没有生命周期限制

// &str —— 借用数据,不能修改
let borrowed: &str = &owned;    // 借用 String 的一部分
// borrowed.push_str("!");       // ❌ 编译错误:&str 不可变
println!("{}", borrowed);       // ✅ 可以读,但受 owned 的生命周期限制

最常见的错误就是搞不清什么时候用哪个:

// ❌ 错误示范 —— 函数签名写错了
fn greet_bad(name: &str) -> String {
    let mut result = name;
    result.push_str(", 你好!");  // 编译错误:&str 没有 push_str 方法
    result
}

// ✅ 正确写法 —— 如果不需要修改,直接返回 &str 拼接
fn greet(name: &str) -> String {
    // format! 宏内部创建了一个新的 String
    format!("{}, 你好!", name)
}

// ✅ 或者明确参数为 String,拿到所有权
fn greet_owned(mut name: String) -> String {
    name.push_str(", 你好!");
    name  // 转移所有权给调用者
}

选型口诀:需要拼接/修改 → String;只需要读 → &str;静态常量 → &'static str。

二、Cow<str>:写时复制的艺术

Cow(Clone on Write)是我后来才理解的一个好东西。它的语义是:先用借用的形式持有数据,只有在真正需要修改时才克隆一份。

看一个实际的场景:写一个函数,把用户输入的 URL 统一处理成小写(URL 本来就应该大小写不敏感,但很多用户不注意):

use std::borrow::Cow;

/// 将 URL 规范化为小写 —— 如果本身就是小写,零开销返回
fn normalize_url(url: &str) -> Cow<str> {
    // 先检查是否已经是全小写(大多数情况下是的)
    if url.chars().any(|c| c.is_uppercase()) {
        // 只有存在大写字母时才分配新内存
        Cow::Owned(url.to_lowercase())
    } else {
        // 已经是小写,直接借用,零内存分配!
        Cow::Borrowed(url)
    }
}

fn main() {
    let url1 = "https://github.com/rust-lang";
    let url2 = "https://GitHub.com/Rust-Lang";
    
    // url1 已经是小写 → Cow::Borrowed,零开销
    let normalized1 = normalize_url(url1);
    // url2 有大写 → Cow::Owned,分配新 String
    let normalized2 = normalize_url(url2);
    
    // 使用时统一解引用
    println!("{}", normalized1);  // 自动 Deref 到 &str
    println!("{}", normalized2);
}

Cow 在 JSON 处理、模板渲染、HTTP header 规范化这些场景特别有用。很多时候数据根本不需要修改,但接口又要求返回可修改的类型,Cow 就是最优解。

// 实际例子:处理 HTTP 请求头(大小写不敏感)
fn normalize_header<'a>(key: &'a str, value: &'a str) -> Cow<'a, str> {
    match key.to_lowercase().as_str() {
        "content-type" => {
            // content-type 需要规范化为小写
            Cow::Owned(value.to_lowercase())
        }
        _ => {
            // 其他 header 原样返回,零拷贝
            Cow::Borrowed(value)
        }
    }
}

三、字符串拼接的性能陷阱

写 Rust 时最容易踩的性能坑之一就是循环中的字符串拼接:

// ❌ 坏写法 —— 每次 += 都可能触发 realloc
fn build_csv_bad(rows: &[Vec<String>]) -> String {
    let mut csv = String::new();
    for row in rows {
        csv.push_str(&row.join(",")); // 每行都做一次 join(分配临时 String)
        csv.push('\n');
    }
    csv
}

// ✅ 好写法 —— 预估容量,避免多次 realloc
fn build_csv_good(rows: &[Vec<String>]) -> String {
    // 预估总容量 = 行数 × 每行平均长度
    let estimated_len: usize = rows.iter().map(|r| r.join(",").len() + 1).sum();
    let mut csv = String::with_capacity(estimated_len);
    
    for row in rows {
        // write! 宏直接写入 String,不分配中间临时对象
        use std::fmt::Write;
        let _ = write!(csv, "{}\n", row.join(","));
    }
    csv
}

核心原则:如果你知道最终字符串大概多长,就用 with_capacity 预分配,避免中间不断 realloc + memcpy。

生产踩坑:with_capacity 用错了反而浪费内存。 我之前在处理用户上传的 CSV 文件时,用 rows.len() * 200 粗暴估算容量,结果遇到某行包含超长备注字段,实际需要的容量远大于估算值,依然触发了多次 realloc。更糟糕的是反向情况——处理大量短行时,估算值严重偏大,一个 50 行的 CSV 分配了 10KB,实际只用 500 字节,造成了大量内存浪费。后来学乖了,先扫描一遍数据求出精确容量:

fn build_csv_safe(rows: &[Vec<String>]) -> String {
    // 先精确计算每行的实际字节数
    let capacity: usize = rows.iter()
        .map(|r| r.join(",").len() + 1) // +1 是换行符
        .sum();
    let mut csv = String::with_capacity(capacity);
    for row in rows {
        use std::fmt::Write;
        let _ = writeln!(csv, "{}", row.join(","));
    }
    csv
}

多一次遍历的开销是 O(n),但换来的是零 realloc 和零内存浪费,对于数据量大的场景完全值得。这个教训是:预分配的前提是你真的知道精确大小,而不是瞎估。

四、零拷贝字符串:&str 切片的妙用

这是我后来在实践中特别喜欢的一个技巧——能用 &str 切片就不用 String,因为切片的开销几乎为零:

/// 解析 HTTP 请求的第一行:"GET /api/users HTTP/1.1"
fn parse_request_line(line: &str) -> (&str, &str, &str) {
    // 不分配任何新内存,全部返回原字符串的切片引用
    let mut parts = line.splitn(3, ' ');
    let method = parts.next().unwrap_or("");   // &str 切片
    let path = parts.next().unwrap_or("");     // &str 切片
    let version = parts.next().unwrap_or("");  // &str 切片
    (method, path, version)
}

// 使用场景:解析大文件中的每一行
async fn parse_large_csv(path: &str) -> Result<(), Box<dyn std::error::Error>> {
    let content = tokio::fs::read_to_string(path).await?;
    // 只分配一次内存(读入整个文件),之后所有解析都基于 &str 切片
    for line in content.lines() {
        let fields: Vec<&str> = line.split(',').collect();
        // fields 里的每个元素都指向 content 中的原始位置,零额外分配
        process_fields(&fields);
    }
    Ok(())
}

边界故障提醒:&str 切片的生命周期陷阱。 &str 是零开销,但它绑定了你到源数据的生命周期。我在一个 Web 请求处理函数中踩过这样的坑:

// ❌ 这段代码编译通不过——response_str 活得不够长
fn bad_example(query: &str) -> &str {
    let response = format!("处理结果: {}", query); // 创建临时 String
    &response[..10]  // ❌ 返回切片引用,但 response 马上就被 drop 了!
}

这类悬垂引用在编译期就会被 Rust 拦下来(borrow checker 报错),但它的变体更隐蔽:你在循环里把一个大 String 的所有 &str 切片收集起来,然后在循环外使用——这个时候 &str 指向的数据已经被下一次迭代覆盖或者 drop 了。规则很明确:&str 切片的生命不能超过被借用的源数据。如果你需要在函数外持有数据,老老实实 to_string() 复制一份。不要因为追求零拷贝而写出 UB(虽然 Rust 会在编译期保护你,减少了运行时崩溃的风险)。

五、总结

Rust 的字符串类型系统不是故意复杂,而是每种类型都对应着一种内存策略:

  1. &str:零成本引用,能用就用。解析、匹配、切片首选。
  2. String:需要拥有所有权或修改内容时用,记得 with_capacity 预分配。
  3. Cow<str>:不确定要不要改的时候用,写时复制,延迟分配,性能利器。
  4. &'static str:编译期确定的常量,直接硬编码在二进制文件中。

这些概念在我刚开始学 Rust 时确实觉得繁琐,但用熟了之后才体会到:Rust 逼迫你在写代码时就思考内存布局,而这些思考换来的就是运行时的零开销和高性能。自学出身不可怕,怕的是不求甚解。

Rust 09: 字符串详解(String、&str、内存布局、常用方法) Rust中字符串相关String、&str的内存布局和常见用法。 阅读详情

相关推荐

Unity动态创建Avatar骨骼映射

Unity Avatar骨骼映射动态创建,适配Unity通用动画,无需经过第三方建模软件,可运行时创建

Nbin_Newby的博客 1万+

Rust - 字符串:&str 与 String

总的来说,String 和 &str 在 Rust 中提供了灵活的字符串处理机制,允许你根据需要选择最合适的类型。String 提供了可变性和动态内存管理,而 &str 提供了对字符串数据的不可变引用,这两者在不同的场景下都非常有用。

日拱一卒无有尽,功不唐捐终入海 2124

国产FPGA开发实战:紫光同创编程软件中的约束文件详解与常见问题

本文详细解析了紫光同创FPGA编程软件中约束文件的关键配置技巧与常见问题。通过基础架构解析、时钟约束实践、物理约束调控及典型问题排查指南,帮助开发者高效掌握国产FPGA开发中的约束文件应用,提升开发效率与设计稳定性。

gin88的博客 908

深入解析 Rust 中的 String 与 &str:从内存布局到性能优化的全面对比​​

本文深入解析Rust中String与&str的核心差异。String是堆分配的可变字符串类型,拥有数据所有权;&str是字符串切片,作为不可变视图引用内存中的UTF-8数据。从内存模型看,String封装Vec<u8>进行动态堆分配,而&str是仅包含指针和长度的胖指针。性能上,&str创建零成本,适合高频只读场景;String的clone是深拷贝,代价较高。最佳实践包括:函数参数优先用&str,数据结构按需选择所有权,避免返回局部&str。

2501_94022820的博客 1074

基于STM32F103与AD8232的便携式心电监测系统设计与实现

本文详细介绍基于STM32F103微控制器与AD8232心电传感器模块的便携式心电监测系统设计与实现。系统通过ADC采集心电信号,结合软件滤波算法处理基线漂移和工频干扰,并利用OLED屏幕实时显示波形,具有低成本、高实用性的特点,适用于健康监测与嵌入式学习。

m5n6o7的博客 737

电芯外观及体系设计(Cell Design)-①容量平衡设计(Cell Balance Design)

全面获取并正确解读客户需求,将需求进行分解和预判,结合公司现有资源、技术,给出设计方案。一个优秀的电芯设计方案,应该是稳健的,可操作的,满足客户需求的同时,并能为公司带来较多利润。电芯设计需同时考虑周期,性能,品质,加工性,成本,及安全与稳健性。设计人员时刻要有成本控制观念,但利润则是由公司战略所决定。

FDC1303864002的博客 1352

String与\&str的内部实现差异:深入Rust字符串的核心机制

在Rust的类型系统中,String和&str是最常被讨论却也最容易被误解的两种字符串类型。它们不仅仅是"可变"与"不可变"的简单区分,而是承载着Rust内存安全哲学的深层设计理念。理解它们的内部实现差异,是掌握Rust所有权系统和零成本抽象的关键一环。

sdff11的博客 774

vivado运行时报错 license文件更新操作

vivado使用:SYNTHESIS->Run Synthesis 时报错 [Common 17-345] A valid license was not found for feature ‘Synthesis’ and/or device ‘xc7vx485t’. Please run the Vivado License Manager for assistance in determiningwhich features and devices are licensed for your sy

qq_44692266的博客 2万+

Rust 新手必看:彻底搞懂 String 和 &str,不再被坑!

Rust字符串类型速查:String与str的区别与转换 本文详细解析了Rust中String和str类型的区别: String是堆分配的可变字符串,拥有所有权;str是字符串切片,编译期大小未知,需通过引用(&str)使用 转换方式:&String可自动转&str,&str需克隆为String获得所有权 使用场景: &str适合函数参数、字面量、配置等只读场景 String适合需要所有权或修改的情况 &mut String用于整体修改,&mut st

taishanduba的专栏 1383

MFC界面库BCGControlBar v32.0 - 对话框、仪表盘控件升级

亲爱的BCGSoft用户,我们非常高兴地宣布BCGControlBar Professional for MFC和BCGSuite for MFCv32.0正式发布!新版本支持Windows 11、增强功能区简化模式、改进控件外观等,以及其他新功能和改进。需要最新版的可以点击这里【BCG下载】 BCGControlBar Pro for MFC v32.0正式版下载 对话框和表单 1. 添加了一个新的全局标志globalData.m_bAutoPrepareComboListStyles:如果您希望.

需要界面开发、IDE工具研发中文教程资料的小伙伴,记得私信我~ 1147

芒果YOLOv10改进31:特征融合Neck篇:改进特征融合网络 BiFPN 结构,融合更多有效特征

YOLOv10改进:特征融合Neck篇:改进特征融合网络 BiFPN 结构,融合更多有效特征

包括YOLOv5、YOLOv7、YOLOv8等模型改进 684

【rCore OS 开源操作系统】Rust 字符串(可变字符串String与字符串切片&str)

Rust 开源操作系统训练营:熟练掌握 Rust 的字符串类型——可变字符串 String 和字符串切片 &str

碳基蒟蒻Serio的博客 1924

备战2025数学建模国赛(算法29):博弈论在数学建模竞赛中的应用

博弈论为数学建模竞赛提供了强大的分析工具,尤其适用于涉及多方互动决策的问题。本文介绍了博弈论的核心模型、算法实现及竞赛应用案例,并提供了完整的Python代码实现。扩展阅读方向不完全信息博弈与贝叶斯均衡合作博弈与联盟形成机制设计与拍卖理论随机博弈与马尔可夫决策过程博弈论与机器学习的结合。

YOLO 442

Rustlings Rust 字符串指南:识别、创建与使用 &str 和 String 两大字符串类型

本文围绕 rustlings 练习集 `09_strings` 模块展开,系统讲解 Rust 的两种字符串类型——字符串切片(`&str`)与拥有所有权的字符串(`String`):如何识别一个值的类型、如何用 `String::from`、`.to_string()`、`format!` 等方式创建字符串、`&String` 到 `&str` 的自动类型强制转换(deref coercion),

gitblog_00336的博客 781

迈瑞BS系列全自动生化分析仪LIS协议接口手册(内含HL7和ASTM)

迈瑞BS系列全自动生化分析仪LIS协议接口手册,内含HL7和ASTM)

OV9734 DATASHEET,完整规格书

OV9734完整规格书,包括电气参数,尺寸规格,寄存器配置表等

rust 中的 String 和 &str

1.str和 &str 看下面的代码: let s = "hello"; 其中,"hello"的数据类型是str,变量 s的数据类型是 &str。有点难理解,我们可以与c语言比较,然后就容易理解了。上面的代码在c语言中是下面的样子: char *s = "hello"; 很有意思吧?在c语言中,s是指针。但是,变量s用起来很不安全,因为不知道 s指向的数据占用了多少内存空间。 于是在rust中,s是切片。切片是一个结构体,包含两个...

程序猿视角 5744

SIMATIC S7-1200, S7-1500 PID 控制[手册]

SIMATIC S7-1200, S7-1500 PID 控制[手册]

Rust字符串类型String与&str的设计原理与实践

字符串处理是编程语言的核心功能之一,其设计直接影响内存安全和性能。Rust语言通过所有权系统和借用检查器,在编译期保障内存安全。String作为可增长的、拥有所有权的UTF-8字符串类型,内部采用类似Vec<u8>的内存布局,自动管理堆内存分配与释放。&str则是轻量级的字符串切片,通过生命周期机制确保引用有效性。这种区分使Rust既能高效处理字符串操作,又能避免常见的内存错误。在实际开发中,合理选择String和&str类型对提升代码质量和性能至关重要,特别是在函数参数设计和字符串拼接等场景。Rust的字

weixin_30471561的博客 383

ssm框架搭建

这个是一个在idea上成功跑起来的ssm框架项目,保存在这里方便以后使用直接下载

Rust “xxx“.to_string()和Rust String::from(“xxx“)区别(将字符串字面量(&str类型)转换为String类型)

实现方式需要Displaytrait,所以它可以用于任何实现了Displaytrait 的类型。是专门用来将&str转换为String,是一个直接的构造方法。用途可以用于任何可以展示(Display)的类型,比如数字、结构体、字符串字面量等。主要用于将&str转换为String,它是一个更直接的方式。

Dontla的博客 1719

Rust Koans字符串篇:彻底分清String与&str的8个关键差异及避坑指南

Rust Koans 是一套通过"填空式测验"带你逐步掌握 Rust 语言的交互式教程,而字符串篇正是许多新手卡壳的第一关。不少初学者刚接触 Rust 字符串时,都会被 `String` 与 `&str` 这两个类型绕晕:为什么有的能修改、有的不能?为什么两个 `String` 拼接还要额外加一个 `&` 符号?本文基于 Rust Koans 中的 [string.rs](https://link

gitblog_01191的博客 239

一文读懂nvidia/cmd:NVIDIA因果自回归视频生成世界模型CMD完全指南

**nvidia/cmd** 是 NVIDIA 开源的 CMD(Context-Matched Distillation)模型权重仓库,提供基于 Cosmos-Predict2.5 2B 的因果自回归视频生成世界模型检查点,支持图生视频(Image-to-Video)、长时程生成与镜头运动控制,是研究和开发交互式世界模型的实用起点。 ## 什么是 CMD?🎯 CMD 全称 **Context

gitblog_01079的博客 684

【 Rust探索】字符串类型(String与&str)的转换与常用方法

/ 创建空 String// 从字符串字面量创建 Stringlet s2 = String::from("编程");// &str 字面量let slice: &str = "入门";// 拼接println!// 输出:Rust编程入门本案例全面介绍了 Rust 中最重要的两种字符串类型 ——String和&str,并通过丰富的代码示例、对比表格和学习路径,帮助读者建立起对 Rust 字符串系统的深刻理解。本质区别String是拥有所有权的堆字符串,而&str。

IRonSir 952

rust-by-practice 复合类型实战:彻底掌握 Rust 字符串(str / &str / String)的类型体系、转换与 UTF-8 操作

本指南以 [rust-by-practice](https://link.gitcode.com/i/532f950910c09cec930d8f69b67316c0) 仓库中「复合类型」章节的[字符串练习](https://link.gitcode.com/i/2760b2a31cd324449083bf09bdec9e5d)为骨架,系统讲解 Rust 中 `str`、`&str`、`Strin

gitblog_00426的博客 677

Rust 深度剖析:String 与 &str 的内存布局与实现差异

在 Rust 中,String和&str(字符串切片) 是处理文本的两个基本类型。初学者常常对何时使用它们感到困惑,而这种困惑的根源在于它们。String是所有者,而&str是借用者。这种差异体现在它们的数据结构、内存位置和核心职责上。

kelvislawyer的博客 846

《Rust语言圣经》Rust教程笔记10:2.Rust基础入门(2.4复合类型)2.4.1Rust字符串String与Rust切片&str(操作字符串、字符串转义、遍历字符串)

顾名思义,字符串是由字符组成的连续集合,但是在上一节中我们提到过,Rust 中的字符是 Unicode 类型,因此每个字符占据 4 个字节内存空间,但是在字符串中不一样,字符串是 UTF-8 编码,也就是字符串中的字符所占的字节数是变化的(1 - 4),这样有助于大幅降低字符串所占用的内存空间。Rust 在语言级别,只有一种字符串类型:str,它通常是以引用类型出现&str,也就是上文提到的字符串切片。虽然语言级别只有上述的str类型,但是在标准库里,还有多种不同用途的字符串类型,其中使用最广的即是。

Dontla的博客 1819

猫抓 cat-catch:2 分钟装好,资源嗅探 + M3U8 解析下载网页视频

猫抓(cat-catch)是一款免费、开源(GPL-3.0)的浏览器资源嗅探扩展:它把当前页面加载过的音视频列成清单,预览确认后直接做网页视频下载;对 M3U8 解析合并,能把碎片拼成完整文件。 - **资源嗅探**:文件名、格式、大小一次看全,视频支持在线预览 - **M3U8 解析**:HLS 分片自动合并,可转出单个 MP4 文件 - **数据本地**:捕获结果只写浏览器本地存储,无跟踪器

gitblog_00248的博客 478

【Rust每周一知】Rust为什么会有String和&str?!长文预警!

本文是Amos博客文章“Working with strings in Rust”的翻译。原文地址:https://fasterthanli.me/blog/2020/working-w...

Rust语言学习交流 2887

Rust常用数据结构教程 String与&str,元组和数组

String具有所有权,而&str只是切片引用·String的数据必须存储在heap上,而&str要看引用所代指可以在stack和data section上,也可以在heap上。

喜欢打篮球的普通人 1278
上一篇: Tokio I/O 性能调优:从 bufreader 到 vectored I/O 的进化路线
下一篇: 系统工具的启动时间优化:懒加载加按需初始化,冷启动从 2 秒到 200ms
第一程序员
博客等级 码龄1年 1万+粉丝 1812原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值