Rust字符串类型全解析
字符串是每种编程语言都绕不开的类型,
不过,在Rust中,你会看到远比其他语言更加丰富多样的字符串类型。
如下图:

为什么Rust中需要这么多种表示字符串的类型呢?
初学Rust时,可能无法理解为什么要这样设计?为什么要给使用字符串带来这么多不必要的复杂性?
其实,Rust中对于字符串的设计,优先考虑的是安全,高效和灵活,
所以在易用性方面,感觉没有其他语言(比如python,golang)那么易于理解和掌握。
本文尝试解释Rust中的所有不同的字符串类型,以及它们各自的特点。
希望能让大家更好的理解Rust为了安全和发挥最大性能的同时,是如何处理字符串的。
1. 机器中的字符串
我们代码中的字符串或者数字,存储在机器中,都是二进制,也就是0和1组成的序列。

程序将二进制数据转换为人类可读的字符串 需要两个关键信息:
- 字符编码
- 字符串长度
常见的编码有ASCII,UTF-8等等,编码就是二进制序列对应的字符,
比如,ASCII是8位二进制对应一个字符,所以它最多只能表示256种不同的字符。
而UTF-8可以使用8位~32位二进制来表示一个字符,这意味着它可以编码超过一百万个字符,
包括世界上的每种语言和各种表情符号等复杂字符。
通过字符编码,我们可以将二进制和字符互相转换,
再通过字符串长度信息,我们将内存中的二进制转换为字符串时,就能知道何时停止。
Rust中的字符串,统一采用UTF-8编码,下面一一介绍各种字符串类型及其使用场景。
2. String 和 &str
String和&str是Rust中使用最多的两种字符串类型,也是在使用中容易混淆的两种类型。
String是分配在堆上的,可增长的UTF-8字符串,
它拥有底层的数据,并且在超出其定义的范围被自动清理释放。
let my_string = String::from("databook");
println!(
"pointer: {:p}, length: {}, capacity: {}",
&my_string,
my_string.len(),
my_string.capacity()
);

对于一个String,主要部分有3个:
Pointer:指向堆内存中字符串的起始位置Length:有效字符串的长度Capacity:字符串my_string总共占用的空间
注意这里Length和Capacity的区别,Length是my_string中有效字符的长度,也就是字符串实际的长度;
Capacity表示系统为my_string分配的内存空间,一般来说,Capacity >= Length。
通常不需要直接处理Capacity,但它的存在对于编写高效且资源敏感的Rust代码时很重要。
特别是,当你知道即将向String添加大量内容时,可能会事先手动保留足够的Capacity以避免多次内存重新分配。
&str则是一个字符串的切片,它表示一个连续的字符序列,
它是一个借用类型,并不拥有字符串数据,只包含指向切片开头的指针和切片长度。
let my_str: &str = "databook";
println!("pointer: {:p}, length: {}", &my_str, my_str.len());

注意,&str没有Capacity方法,因为它只是一个借用,内容不可能增加。
最后,对于String和&str,使用时建议:
- 在运行时动态创建或修改字符串数据时,请使用
String - 读取或分析字符串数据而不对其进行更改时,请使用
&str
3. Vec[u8] 和 &[u8]
这两种形式是将字符串表示位字节的形式,其中Vec[u8]是字节向量,&[u8]是字节切片。
它们只是将字符串中的各个字符转换成字节形式。
as_bytes方法可将&str转换为&[u8];
into_bytes方法可将String转换为Vec<u8>。
let my_str: &str = "databook";
let my_string = String::from("databook");
let s: &[u8] = my_str.as_bytes();
let ss: Vec<u8> = my_string.into_bytes();
println!("s: {:?}", s);
println!("ss: {:?}", ss);
/* 运行结果
s: [100, 97, 116, 97, 98, 111, 111, 107]
ss: [100, 97, 116, 97, 98, 111, 111, 107]
*/

在UTF-8编码中,每个英文字母对应1个字节,而一个中文汉字对应3个字节。
let my_str: &str = "中文";
let my_string = String::from("中文");
let s: &[u8] = my_str.as_bytes();
let ss: Vec<u8> = my_string.into_bytes();
println!("s: {:?}", s);
println!("ss: {:?}", ss);
/* 运行结果
s: [228, 184, 173, 230, 150, 135]
ss: [228, 184, 173, 230, 150, 135]
*/

Vec[u8]和&[u8]以字节的形式存储字符串,不用关心字符串的具体编码,
这在网络中传输二进制文件或者数据包时非常有用,可以有效每次传输多少个字节。
4. str 系列
str类型本身是不能直接使用的,因为它的大小在编译期无法确定,不符合Rust的安全规则。
但是,它可以与其他具有特殊用途的指针类型一起使用。
4.1. Box<str>
如果需要一个字符串切片的所有权(&str是借用的,没有所有权),那么可以使用Box智能指针。
当你想要冻结字符串以防止进一步修改或通过删除额外容量来节省内存时,它非常有用。
比如,下面的代码,我们将一个String转换为Box<str>,
这样,可以确保它不会在其他地方被修改,也可以删除它,因为Box<str>拥有字符串的所有权。
let my_string = String::from("databook");
let my_box_str = my_string.into_boxed_str();
println!("{}", my_box_str);
// 这一步会报错,因为所有权已经转移
// 这是 Box<str> 和 &str 的区别
// println!("{}", my_string);
4.2. Rc<str>
当你想要在多个地方共享一个不可变的字符串的所有权,但是又不克隆实际的字符串数据时,
可以尝试使用Rc<str>智能指针。
比如,我们有一个非常大的文本,想在多个地方使用,又不想复制多份占用内存,可以用Rc<str>。
let my_str: &str = "very long text ....";
let rc_str1: Rc<str> = Rc::from(my_str);
let rc_str2 = Rc::clone(&rc_str1);
let rc_str3 = Rc::clone(&rc_str1);
println!("rc_str1: {}", rc_str1);
println!("rc_str2: {}", rc_str2);
println!("rc_str3: {}", rc_str3);
/* 运行结果
rc_str1: very long text ....
rc_str2: very long text ....
rc_str3: very long text ....
*/
这样,在不实际克隆字符串数据的情况下,让多个变量拥有其所有权。
4.3. Arc<str>
Arc<str>与Rc<str>的功能类似,主要的区别在于Arc<str>是线程安全的。
如果在多线程环境下,请使用Arc<str>。
let my_str: &str = "very long text ....";
let arc_str: Arc<str> = Arc::from(my_str);
let mut threads = vec![];
let mut cnt = 0;
while cnt < 5 {
let s = Arc::clone(&arc_str);
let t = thread::spawn(move || {
println!("thread-{}: {}", cnt, s);
});
threads.push(t);
cnt += 1;
}
for t in threads {
t.join().unwrap();
}
/* 运行结果
thread-0: very long text ....
thread-3: very long text ....
thread-2: very long text ....
thread-1: very long text ....
thread-4: very long text ....
*/
上面的代码中,在5个线程中共享了字符串数据。
上面运行结果中,线程顺序是不固定的,多执行几遍会有不一样的顺序。
4.4. Cow<str>
Cow是Copy-on-Write(写入时复制)的缩写,
当你需要实现一个功能,根据字符串的内容来决定是否需要修改它,使用Cow就很合适。
比如,过滤敏感词汇时,我们把敏感词汇替换成xx。
fn filter_words(input: &str) -> Cow<str> {
if input.contains("sb") {
let output = input.replace("sb", "xx");
return Cow::Owned(output);
}
Cow::Borrowed(input)
}
当输入字符串input中含有敏感词sb时,会重新分配内存,生成新字符串;
否则直接使用原字符串,提高内存效率。
5. CStr 和 CString
CStr和CString是与C语言交互时用于处理字符串的两种类型。
CStr用于在Rust中安全地访问由C语言分配的字符串;
而CString用于在Rust中创建和管理可以安全传递给C语言函数的字符串。
C风格的字符串与Rust中的字符串实现方式不一样,
比如,C语言中的字符串都是以null字符\0结尾的字节数组,这点就与Rust很不一样。
所以Rust单独封装了这两种类型(CStr和CString),可以安全的与C语言进行字符串交互,从而实现与现有的C语言库和API无缝集成。
6. OsStr 和 OsString
OsStr 和 OsString 是用于处理与操作系统兼容的字符串类型。
主要用于需要与操作系统API进行交互的场景,这些API一般特定于平台的字符串编码(比如Windows上的UTF-16,以及大多数Unix-like系统上的UTF-8)。
OsStr 和OsString 也相当于str和String的关系,所以OsStr 一般不直接在代码中使用,
使用比较多的是&OsStr和OsString。
这两个类型一般用于读取/写入操作系统环境变量或者与系统API交互时,帮助我们确保字符串以正确的格式传递。
7. Path 和 PathBuf
这两个类型看名字似乎和字符串关系不大,实际上它们是专门用来处理文件路径字符串的。
在不同的文件系统中,对于文件路径的格式,路径中允许使用的字符都不一样,比如,windows系统中文件路径甚至不区分大小写。
使用Path 和 PathBuf,我们编码时就不用分散精力去关心具体使用的是哪种文件系统。
Path和PathBuf的主要区别在于可变性和所有权,
如果需要频繁读取和查询路径信息而不修改它,Path是一个好选择;
如果需要动态构建或修改路径内容,PathBuf则更加合适。
8. 总结
总之,Rust中字符串类型之所以多,是因为根据不同的用途对字符串类型做了分类。
这也是为了处理不同的应用场景时让程序发挥最大的性能,毕竟,安全和高性能一直是Rust最大的卖点。
Rust字符串类型全解析的更多相关文章
- PHP入门基础(一)——标记风格、注释、表单获取、字符串类型、变量解析
PHP标记风格: //XML风格//推荐的标记风格,可以在XML文档中使用 <?php echo '<p>XML Style</p>'; ?> //简短风格——需启 ...
- MySQL字段类型最全解析
前言: 要了解一个数据库,我们必须了解其支持的数据类型.MySQL 支持大量的字段类型,其中常用的也有很多.前面文章我们也讲过 int 及 varchar 类型的用法,但一直没有全面讲过字段类型,本篇 ...
- Oracle中Clob类型处理解析:ORA-01461:仅可以插入LONG列的LONG值赋值
感谢原作者:破剑冰-Oracle中Clob类型处理解析 上一篇分析:ORA-01461: 仅能绑定要插入 LONG 列的 LONG 值 最近为Clob字段在插入数据时发现当字符的字节数(一个半角字符一 ...
- jQuery Ajax 实例 全解析
jQuery Ajax 实例 全解析 jQuery确实是一个挺好的轻量级的JS框架,能帮助我们快速的开发JS应用,并在一定程度上改变了我们写JavaScript代码的习惯. 废话少说,直接进入正题,我 ...
- jQuery Ajax 全解析
转自:http://www.cnblogs.com/qleelulu/archive/2008/04/21/1163021.html 本文地址: jQuery Ajax 全解析 本文作者:QLeelu ...
- javascript ajax 脚本跨域调用全解析
javascript ajax 脚本跨域调用全解析 今天终于有点时间研究了一下javsscript ajax 脚本跨域调用的问题,先在网上随便搜了一下找到一些解决的办法,但是都比较复杂.由是转到jqu ...
- 你不知道的JavaScript--Item22 Date对象全解析
本篇主要介绍 Date 日期和时间对象的操作. 1. 介绍 1.1 说明 Date对象,是操作日期和时间的对象.Date对象对日期和时间的操作只能通过方法. 1.2 属性 无: Date对象对日期和时 ...
- oracle提高查询效率的34个方面全解析
oracle提高查询效率的34个方面全解析 在一个数据库中进行操作的时候,效率是很重要的,那么,如何提高oracle的查询效率呢?笔者将从以下几个方面进行详细解析: 1.选择最有效率的表名顺序(只 ...
- Android图片载入框架最全解析(一),Glide的基本使用方法
转载请注明出处:http://blog.csdn.net/guolin_blog/article/details/53759439 本文同步发表于我的微信公众号.扫一扫文章底部的二维码或在微信搜索 郭 ...
- PHP 类与对象 全解析(三)
目录 PHP 类与对象 全解析( 一) PHP 类与对象 全解析( 二) PHP 类与对象 全解析(三 ) 13.魔术方法 定义:PHP把所有以__(两个下划线)开头的类方法当成魔术方法 __ ...
随机推荐
- C# WinForm自制项目模板入坑记
1. 创建模板配置 1.1 在项目目录中创建.template.config文件夹 1.2 创建一个名为"template.json" 的新文件 { "author&qu ...
- Python 代码中的 yield 到底是什么?
在Python编程中,有一个强大而神秘的关键字,那就是yield.初学者常常被它搞得晕头转向,而高级开发者则借助它实现高效的代码.到底yield是什么?它又是如何在Python代码中发挥作用的呢?让我 ...
- keycloak~为微信二维码添加动态kc认可的动态state
本实例将通过keycloak社区登录实现微信二维码的登录,并且二微码不是keycloak动态生成,而是通过微信提供的js生成的,在页面上直接输出的方式实现的. 动态state 在Keycloak中使用 ...
- python中基于tcp协议与udp的通信
python中基于tcp协议与udp的通信(数据传输) 一.TCP协议介绍 流式协议(以数据流的形式通信传输) 安全协议(收发信息都需收到确认信息才能完成收发,是一种双向通道的通信) tcp协议在 ...
- 【SpringBoot】15 数据访问P3 整合Mybatis
重新建立一个SpringBoot工程 选择依赖组件 然后删除不需要的Maven&Git文件 还是先查看我们的POM文件 整合Mybatis的组件多了这一个,默认的版本是3.5.4 然后再看看整 ...
- 决定了,今日起开始准备弃用京东JD
估计京东是为了节约开支,然后开始大比例的把快递物流业务进行外包了,这直接导致服务质量的直线下滑,10多年前我选择弃用当当网而选择京东JD就是因为当时当地的当当网快递是用沈阳晚报的快递上门的,快递员连P ...
- mybatis-plus系统化学习之配置精讲
1.背景 mybatis-plus给出了很多配置, 大部分的配置使用默认的就可以了, 但是还是有很多需要的配置比如: # mybatis-plus相关配置 mybatis-plus: # xml扫描, ...
- 用DolphinScheduler轻松实现Flume数据采集任务自动化!
转载自天地风雷水火山泽 目的 因为我们的数仓数据源是Kafka,离线数仓需要用Flume采集Kafka中的数据到HDFS中. 在实际项目中,我们不可能一直在Xshell中启动Flume任务,一是因为项 ...
- OSI 七层网络模型和 TCP/IP 四层网络模型
OSI 七层网络模型 网络的七层架构从下到上主要分为:物理层.数据链路层.网络层.传输层.会话层.表示层和应用层 物理层主要定义物理设备标准,它的主要作用是传输比特流,具体做法是在发送端将 1.0 码 ...
- Dapr v1.14 版本已发布
Dapr是一套开源.可移植的事件驱动型运行时,允许开发人员轻松立足云端与边缘位置运行弹性.微服务.无状态以及有状态等应用程序类型.Dapr能够确保开发人员专注于编写业务逻辑,而不必分神于解决分布式系统 ...