首页 > 其他分享 >Rust字符串类型全解析

Rust字符串类型全解析

时间:2024-09-25 16:22:52浏览次数:7  
标签:string let str 字符串 解析 my Rust

字符串是每种编程语言都绕不开的类型,

不过,在Rust中,你会看到远比其他语言更加丰富多样的字符串类型。

如下图:

为什么Rust中需要这么多种表示字符串的类型呢?

初学Rust时,可能无法理解为什么要这样设计?为什么要给使用字符串带来这么多不必要的复杂性?

其实,Rust中对于字符串的设计,优先考虑的是安全高效灵活

所以在易用性方面,感觉没有其他语言(比如python,golang)那么易于理解和掌握。

本文尝试解释Rust中的所有不同的字符串类型,以及它们各自的特点。

希望能让大家更好的理解Rust为了安全和发挥最大性能的同时,是如何处理字符串的。

1. 机器中的字符串

我们代码中的字符串或者数字,存储在机器中,都是二进制,也就是0和1组成的序列。

程序将二进制数据转换为人类可读的字符串 需要两个关键信息:

  1. 字符编码
  2. 字符串长度

常见的编码有ASCIIUTF-8等等,编码就是二进制序列对应的字符,

比如,ASCII8位二进制对应一个字符,所以它最多只能表示256种不同的字符。

UTF-8可以使用8位~32位二进制来表示一个字符,这意味着它可以编码超过一百万个字符,

包括世界上的每种语言和各种表情符号等复杂字符。

通过字符编码,我们可以将二进制和字符互相转换,

再通过字符串长度信息,我们将内存中的二进制转换为字符串时,就能知道何时停止。

Rust中的字符串,统一采用UTF-8编码,下面一一介绍各种字符串类型及其使用场景。

2. String 和 &str

String&strRust中使用最多的两种字符串类型,也是在使用中容易混淆的两种类型。

String是分配在堆上的,可增长的UTF-8字符串,

它拥有底层的数据,并且在超出其定义的范围被自动清理释放。

let my_string = String::from("databook");
println!(
    "pointer: {:p}, length: {}, capacity: {}",
    &my_string,
    my_string.len(),
    my_string.capacity()
);

对于一个String,主要部分有3个:

  1. Pointer:指向堆内存中字符串的起始位置
  2. Length:有效字符串的长度
  3. Capacity:字符串my_string总共占用的空间

注意这里LengthCapacity的区别,Lengthmy_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,使用时建议:

  1. 在运行时动态创建或修改字符串数据时,请使用 String
  2. 读取或分析字符串数据而不对其进行更改时,请使用 &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>

CowCopy-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

CStrCString是与C语言交互时用于处理字符串的两种类型。

CStr用于在Rust中安全地访问由C语言分配的字符串;

CString用于在Rust中创建和管理可以安全传递给C语言函数的字符串。

C风格的字符串与Rust中的字符串实现方式不一样,

比如,C语言中的字符串都是以null字符\0结尾的字节数组,这点就与Rust很不一样。

所以Rust单独封装了这两种类型(CStrCString),可以安全的与C语言进行字符串交互,从而实现与现有的C语言库和API无缝集成。

6. OsStr 和 OsString

OsStrOsString 是用于处理与操作系统兼容的字符串类型。

主要用于需要与操作系统API进行交互的场景,这些API一般特定于平台的字符串编码(比如Windows上的UTF-16,以及大多数Unix-like系统上的UTF-8)

OsStrOsString 也相当于strString的关系,所以OsStr 一般不直接在代码中使用,

使用比较多的是&OsStrOsString

这两个类型一般用于读取/写入操作系统环境变量或者与系统API交互时,帮助我们确保字符串以正确的格式传递。

7. Path 和 PathBuf

这两个类型看名字似乎和字符串关系不大,实际上它们是专门用来处理文件路径字符串的。

在不同的文件系统中,对于文件路径的格式,路径中允许使用的字符都不一样,比如,windows系统中文件路径甚至不区分大小写。

使用PathPathBuf,我们编码时就不用分散精力去关心具体使用的是哪种文件系统。

PathPathBuf的主要区别在于可变性和所有权,

如果需要频繁读取和查询路径信息而不修改它,Path是一个好选择;

如果需要动态构建或修改路径内容,PathBuf则更加合适。

8. 总结

总之,Rust中字符串类型之所以多,是因为根据不同的用途对字符串类型做了分类。

这也是为了处理不同的应用场景时让程序发挥最大的性能,毕竟,安全高性能一直是Rust最大的卖点。

标签:string,let,str,字符串,解析,my,Rust
From: https://www.cnblogs.com/wang_yb/p/18431588

相关文章

  • Leetcode 626-换座位题目解析
    1.题目编写解决方案来交换每两个连续的学生的座位号。如果学生的数量是奇数,则最后一个学生的id不交换。按 id 升序 返回结果表。 2.数据准备CreatetableIfNotExistsSeat(idint,studentvarchar(255));TruncatetableSeat;insertintoSeat(id,student)v......
  • RusTitW:大规模语言视觉文本识别数据集(猫脸码客 第190期)
    RusTitW:RussianLanguageVisualTextRecognition一、引言在信息爆炸的现代社会,文本作为信息传递的重要载体,扮演着不可或缺的角色。随着计算机视觉与模式识别技术的飞速发展,自动化文本识别(OCR,OpticalCharacterRecognition)技术日益成熟,极大地提升了信息处理的效率与准确性。......
  • DNS云解析和普通解析一样吗
    在当今数字化时代,网络的稳定与高效运行至关重要。域名系统(DNS)作为互联网的基础设施之一,其解析服务的质量直接影响着用户的网络体验。近年来,DNS云解析逐渐兴起,与传统的普通解析相比,它们之间存在着显著的区别。首先,在可靠性方面,DNS云解析具有明显优势。普通解析通常依赖于单一的服务......
  • Git 工作区、暂存区与修改全解析
    工作区和暂存区是Git中一个非常重要的概念,弄明白了他们,就弄明白了Git的很多操作到底干了什么。‍工作区(WorkingDirectory)工作区,就是一个目录,比如我的LearnGit​文件夹就是一个工作区:​我们平时更新版本什么的,都是在这里完成的,可以理解成是在这里工作的。‍‍版本库......
  • 如何用Rust编写一个ChatGPT桌面应用(保姆级教程)
    为什么我们需要一个桌面应用原因实在太多,我们需要便捷地导出记录,需要在回答长度超长的时候自动加上“继续”,需要收藏一些很酷很实用的prompt......(首先我假设你是一名如我一样习惯用IDEA开发的java仔)安装Rust语言工具链:首先,请确保你已安装了Rust编程语言工具链,包括Rust编译......
  • 一万字全面解析CRM的定义、分类与核心价值
    1、CRM定义与分类1.1CRM的定义CRM,英文CustomerRelationshipManagement的缩写,中文全称为客户关系管理。通常情况下,人们通常用CRM直接表达客户关系管理软件系统——一个以客户为中心的专门用于管理与客户关系的软件工具,以确保与客户在营销、销售、服务的每一环节上都能实现顺......
  • Understanding the difference between a Java keystore and a truststore
    Atruststoreisafilethatcontainsoneormorepubliccertificates,butnoprivatekeys. Akeystoreisafilethatcontainsoneormorepubliccertificatesandoneormoreprivatekeys.Thisistypicallya"chain"wherethekeystorecontain......
  • fetchEventSource使用+源码解析
    fetchEventSource使用+源码解析nonhana杭电大三学生,喜欢搞前端。​关注他创作声明:包含AI辅助创作12人赞同了该文章前言最近由于一些乱七八糟的原因,接触到了国内开发的一些类ChatGPT的API的前端调用与功能集成。概括的来说,就是有一......
  • DNS正向解析和反向解析的区别
    在网络世界中,域名系统(DNS)起着至关重要的作用,它就如同网络世界的导航地图,帮助我们在浩瀚的数字海洋中准确找到目标。而在DNS中,正向解析和反向解析是两个重要的概念,它们有着明显的区别。首先,正向解析是将域名转换为IP地址的过程。当我们在浏览器中输入一个网址,如“www.example.com”......
  • 【JUC并发编程系列】深入理解Java并发机制:Volatile从底层原理解析到高级应用技巧(六、
    文章目录【JUC并发编程系列】深入理解Java并发机制:Volatile从底层原理解析到高级应用技巧(六、Volatile关键字、JMM、重排序、双重检验锁)1.Volatile的特性2.Volatile的用法3.CPU多核硬件架构剖析4.JMM内存模型4.1主要特性4.2JMM的工作原理4.3实现机制5.JMM八......