一文详解Rust中的字符串
有人可能会说,字符串这么简单还用介绍?但是很多人学习rust受到的第一个暴击就来自这浓眉大眼、看似毫无难度的字符串。
请看下面的例子。
fn main() {let my_name = "World!";greet(my_name);
}fn greet(name: String) {println!("Hello, {}!", name);
}
这段简单Hello world的代码看起来没什么问题,但是在rust里却编译不了。
error[E0308]: mismatched types--> src/main.rs:3:11|
3 | greet(my_name);| ^^^^^^^| || expected struct `std::string::String`, found `&str`| help: try using a conversion method: `my_name.to_string()`error: aborting due to previous error
报错的意思是,greet函数需要一个String类型的参数,但是提供了一个&str类型的实参。
这下不觉得字符串简单了吧?
学习Rust你必须理解&str和String的区别。别急,你还经常会在代码里看到 &'static str、&[u8]、&[u8; N]、Vec<u8> 、OsStr、OsString、CStr和CString。
这张图很好地描绘了学习Rust后再谈到字符串的情形:

本文就介绍一下这些字符串相关的类型。
先来说说&str
&str
str类型也叫字符串切片,是最基本的字符器类型,通常是借用的试出现,也就是&str。
什么是切片?
在rust里,切片是连续序列[T]的动态大小视图 ,切片是内存块的视图,表示为指针和长度。 这样的定义会让人难以理解。其实slice就是一种引用,允许你对一个连续序列中元素进行引用。
fn main() {let a = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];let slice = &a[3..7];println!("{:#?}", slice);
}
let slice = &a[3..7];这一行我们创建了一个slice。它的内容是:
[4,5,6,7,
]
slice的中文翻译切片这个词,很容易让人认为是从一个连续序列中切下来一段,很难与引用联想在一起,我认为翻译成片段可能更合适。
理解了slice,&str就好理解了,&str就是字符串的slice。Rust负责保证str是有效的UTF-8。因为通常是以借用引用(&str)的方式出现,因此是不可变的。
在其它语言中常用的字符串操作,如split、find、trim,大小写转换等操作,都是str的方法,并不是由String类型提供。
在这里要注意,在对字符串使用切片语法时需要格外小心。因为字符串的内部是[u8]数组,每个数组的元素是一个u8,所以数组的长度就是字符串的长度,跟你看到的字符串的长度可能是不一样的。
let s = "我是中国人";
println!("{}",s.len());
你以为结果会是5,但是结果是15; 为什么是15,因为这个字符串的字节数是15。
let s = "我是中国人";
println!("{:?}",s.bytes())
结果是:
Bytes(Copied { it: Iter([230, 136, 145, 230, 152, 175, 228, 184, 173, 229, 155, 189, 228, 186, 186]) })
字符串的len()返回的是字节数,不是UTF-8字符数。
let s = "我是中国人";
println!("{}",s.chars().count());
这时输出的才是5。
所以当直接对字符串对切片时,一定要注意切片的索引必须落在字符之间的边界位置。
let s = "我是中国人";
let a = &s[0..2];
println!("{}",a);
这段代码可以编译,但是在运行时会报错
Compiling playground v0.0.1 (/playground)Finished dev [unoptimized + debuginfo] target(s) in 0.39sRunning `target/debug/playground`
thread 'main' panicked at src/main.rs:4:15:
byte index 2 is not a char boundary; it is inside '我' (bytes 0..3) of `我是中国人`
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
每个汉字占用3个字节,&s[0..2]只取了前两个节点,所以报错信息告诉你,index 2是不是字符的边界。所以对字符串使用切片语法时需要格外谨慎。
注意: Rust里字符串字面量的类型是&'static str,这涉及到静态生命周期,有兴趣同学可以参考生命周期相关的文章。
String
在rust中String不是基本类型,是个复合类型,它包含了一个私有的u8的vec。
pub struct String {vec: Vec<u8>,
}
因为它的唯一字段vec是私有的,所以只能通过String类型提供的构建函数创建String,因此let my_name = "Rust";这样语句创建出来的不是String类型。
因为它的底层是一个vec,所以String支持改变它自身的一些操作,比如push、pop、clear,可以看出来都是针对vec的操作。
let mut s = String::from("abc");s.push('1');
s.push('2');
s.push('3');assert_eq!("abc123", s);let mut s = String::from("abč");assert_eq!(s.pop(), Some('č'));
assert_eq!(s.pop(), Some('b'));
assert_eq!(s.pop(), Some('a'));assert_eq!(s.pop(), None);let mut s = String::from("foo");s.clear();assert!(s.is_empty());
assert_eq!(0, s.len());
assert_eq!(3, s.capacity());
&[u8]
&[u8]是一个切片,指向一段连续的内存区域,其中存储着 u8 类型的值(字节)。它不拥有数据,只是借用了数据的引用。
由于不拥有数据,&[u8] 通常用于不可变的字符串操作。可以从 String 或其他字节数组中创建&[u8] 切片。
let mut my_string = String::from("Hello, world!");// 获取 &[u8] 切片
let my_bytes: &[u8] = my_string.as_bytes();// 将 &[u8] 转换为 String (需要确保是有效的 UTF-8 编码)
let new_string = String::from_utf8(my_bytes.to_vec()).unwrap();
&[u8;N]
&[u8; N] 表示一个指向长度为 N 的 u8 类型数组的切片。
与 &[u8]的区别是,&[u8] 是一个指向任意长度u8 类型数组的切片,可以指向不同长度的数组。&[u8; N]是一个指向固定长度为 N 的字节数组的切片,只能指向长度为 N 的数组。
一个特别常用的场景就是网络协议栈的解析,数据包头通常都是固定长度的,非常适合用&[u8; N]来保存。
Vec<u8>
Vec<u8> 是String类型的底层存储,可以通过String::from_utf8这个方法创建一个String。
&u8
&u8只是 &[u8]切片中的一个元素,也不展开介绍。
OsStr和OsString
这两个类型包含在std::ffi这个模块里,ffi 的意思是 Foreign Function Interface ,外部函数接口,用来调用其它语言(如C语言)编写的函数。因为目前主流的操作系统都是用C语言写的,所以ffi可以用来调用系统接口和处理与操作系统相关的操作。
为什么需要OsStr和OsString呢?
因为在不同的操作系统中,字符串的编码是有差异的。
在 Unix 系统上,字符串通常是非零字节的任意序列,通常情况下,这些字符串会被解释为 UTF-8 编码的文本,但并非总是如此。
在 Windows 上,字符串通常是非零 16 位值的任意序列,通常情况下,这些字符串会被解释为 UTF-16 编码的文本,也并非总是如此。
在 Rust 中,字符串始终是有效的 UTF-8 编码,可以包含零。 这意味着 Rust 字符串只能包含有效的 UTF-8 编码的字节序列,但可以包含 0 字节。
因为操作系统原生字符串与Rust字符串的这种差异,因此需要有一种类型能同时表示这两种字符串,并可以在需要时进行相互转换,这种类型就是OsString 和 OsStr。
注意, OsString 和 OsStr 内部不一定以平台原生的形式保存字符串;
use std::env;
use std::ffi::OsString;fn main() {// 获取命令行参数let args: Vec<OsString> = env::args_os().collect();// 获取第一个参数(文件名)let filename = &args[1];// 打印文件名println!("Filename: {:?}", filename);
}
Path 和PathBuf
Path 结构表示底层文件系统中的文件路径。有两种样式: Path posix::Path ,用于类 UNIX 系统,以及 windows::Path ,用于 Windows。只所以有两种形式,是因为windows和Unix的路径差别很大,比如路径分隔符就不一样,windows用\,Unix用/。
prelude.rs会根据当前平台导出相应的特定于平台 Path 的变体。
Path这个类型是一个切片,是不可变的(immutable),它的owned版本的类型是PathBuf。Path和PathBuf的关系跟str和String的关系相似。
因为Path是与操作系统相关的,因此它内部使用的是OsStr。
pub struct Path {inner: OsStr,
}
下面是Path的代码示例。
use std::path::Path;
use std::ffi::OsStr;// 注意: 下面代码不能运行在windows下
let path = Path::new("./foo/bar.txt");let parent = path.parent();
assert_eq!(parent, Some(Path::new("./foo")));let file_stem = path.file_stem();
assert_eq!(file_stem, Some(OsStr::new("bar")));let extension = path.extension();
assert_eq!(extension, Some(OsStr::new("txt")));
PathBuf是 Path的 owned版本,是可变的。
use std::path::PathBuf;let mut path = PathBuf::new();path.push(r"C:\");
path.push("windows");
path.push("system32");path.set_extension("dll");
CStr和CString
在C语言中字符串是NUL(\0)为结尾的一维字符数组。
Rust中的CStr表示对以 nul 结尾的字节数组的借用引用,也就是C语言的字符串在Rust中的对应类型。
它可以安全地从 &[u8] 切片构建,也可以不安全地(unsafely)从原始 *const c_char 构建。
因为Rust的字符串必须是UTF-8的,所以CStr要转换为String,需要通过 UTF-8 验证,以保证每个字符都是UTF-8的。
use std::ffi::CStr;
use std::os::raw::c_char;extern "C" { fn my_string() -> *const c_char; }unsafe {let slice = CStr::from_ptr(my_string());println!("string buffer size without nul terminator: {}", slice.to_bytes().len());
}
总结
在Rust语言中有几种字符串相关的类型,&str和String是Rust字符串最常用的类型,前者是一个slice,是借用引用,后者则是它的owned版本,可变。OsStr和OsString是Rust的字符串和操作系统原生字符串的桥,通过这个桥,Rust的字符串和操作系统原生字符串可以相互转换。Path和PathBuf则是Rust为不同的操作系统提供的统一的路径(Path)类型,在内部使用的是OsStr。而CStr则是C语言中以NUL(\0)为结尾的一维字符数组在Rust语言的一种表示。
本文为原创,未经同意不得转载。本文亦发表于https://www.renhl.com/posts/2024/03/17/rust-string-osstring-cstring/
相关文章:
一文详解Rust中的字符串
有人可能会说,字符串这么简单还用介绍?但是很多人学习rust受到的第一个暴击就来自这浓眉大眼、看似毫无难度的字符串。 请看下面的例子。 fn main() {let my_name "World!";greet(my_name); }fn greet(name: String) {println!("Hello…...
Mysql中用户密码修改
1、命令行修改 请确保已使用root或其他拥有足够权限的用户登录MySQL,对于MySQL 5.7.6及以上版本或者MariaDB 10.1.20及以上版本。 ALTER USER ‘root’‘localhost’ IDENTIFIED BY ‘root’; 1、使用命令 mysql -uroot -p你的密码 连接到mysql管理工具 2、使用命…...
day14-SpringBoot 原理篇
一、配置优先级 SpringBoot 中支持三种格式的配置文件: 注意事项 虽然 springboot 支持多种格式配置文件,但是在项目开发时,推荐统一使用一种格式的配置 (yml 是主流)。 配置文件优先级排名(从高到低&…...
ChatGPT论文指南|揭秘8大ChatGPT提示词研究技巧提升写作效率【建议收藏】
点击下方▼▼▼▼链接直达AIPaperPass ! AIPaperPass - AI论文写作指导平台 公众号原文▼▼▼▼: ChatGPT论文指南|揭秘8大ChatGPT提示词研究技巧提升写作效率【建议收藏】 目录 1.写作方法 2.方法设计 3.研究结果 4.讨论写作 5.总结结论 6.书…...
P1563 [NOIP2016 提高组] 玩具谜题
题目传送门 这道题实在是一道水题 话不多说,上代码 #include<iostream> #include<cstring> using namespace std; struct a{int io;//in朝里 out朝外 小人的朝向 string name;//小人的名字 int number;//角色编号 }a[100000]; int main(){int n, m…...
【数据库】数据库语言
1.4 数据库语言 数据库系统提供数据定义语言(DDL)来定义数据库模式,并提供数据操纵语言(DML)来表达数据库的查询和更新。 通过一系列特定的DDL语句来说明数据库系统所采用的存储结构和访问方式,这种特定的…...
javascript单例模式字面量定义的接口和匿名函数定义的接口;他们之间访问私有变量和私有函数之间的区别
javascript的单例模式:即只有一个实例; 模块模式是在单例模式上扩展而来的 //这种是字面量定义了单例对象的公共接口; 字面量对象调用私有变更和私有函数 var Book(function(){let name;checkBookfunction(value){namevalue;}return{setNam…...
啥是大语言模型LLM
引言: 在人工智能的世界里,有一种技术正迅速改变我们与机器交流的方式——这就是大语言模型LLM(Large Language Model)。它们像是拥有海量知识库的超级智能,能够理解和生成人类语言。那么,大语言模型LLM到底…...
vue3之路由导航故障
通常一个导航守卫函数中会发生这四件事之一: 1.通过调用 return false 中断了这次导航 2.通过返回一个新的位置,重定向到其他地方 (例如,return ‘/login’) 3.正常导航到指定路由 4.抛出了一个 Error 检测导航故障 可以使用vue-router提供的…...
Dr4g0n
信息收集 # nmap -sn 192.168.56.0/24 -oN live.nmap Starting Nmap 7.94 ( https://nmap.org ) at 2024-03-04 08:52 CST Nmap scan report for 192.168.56.2 Host is up (0.00012s latency). MAC Address: 00:50:56:FE:B1:6F (VMware) Nmap scan report …...
蓝桥杯每日一题:扫雷
题目来源:第十三届蓝桥杯软件赛省赛 B组 在一个 n n n 行 m m m 列 的方格图中有些位置有地雷, 另外一些位置为空 请为每个空位置标一个整数, 表示周围八个相邻的方格中有多少个地雷 输入 : 输入的第一行包含两个整数 n n n , m m m 第 2 行 到 第 n 1 n 1 n…...
net core API 后台系统操作日志的实现思路
net core API 后台系统操作日志的实现思路 系统操作日志的实现思路主要问题不在于写日志和表结构设计上。 主要问题在识别出哪些数据做了修改。并生成日志。 表中数据列众多,且要监控多个表。如果要监控的每个表都去写代码去监控和转换这样的工作量就会比较大。 如,用户表…...
ORACLE 知识整理
目录 一. 插入指定数量的数据二. 索引2.1 创建索引2.2 删除索引 三. 查询计划四. Oracle SQLPlus常用设置五. 增加删除字段 一. 插入指定数量的数据 ⏹当需要向表中插入若干测试数据的时候,可通过下面这种方式造数据 先从DUAL虚拟表中检索后造出10000条数据后&…...
业务服务:redisson
文章目录 前言一、配置1. 添加依赖2. 配置文件/类3. 注入redission3. 封装工具类 二、应用1. RedisUtils工具类的基本使用 三、队列1. 工具类2. 普通队列3. 有界队列(限制数据量)4. 延迟队列(延迟获取数据)5. 优先队列(…...
面试算法-100-零钱兑换
题目 给你一个整数数组 coins ,表示不同面额的硬币;以及一个整数 amount ,表示总金额。 计算并返回可以凑成总金额所需的 最少的硬币个数 。如果没有任何一种硬币组合能组成总金额,返回 -1 。 你可以认为每种硬币的数量是无限的…...
【Leetcode每日一题】 动态规划 - 使用最小花费爬楼梯(难度⭐)(41)
1. 题目解析 题目链接:746. 使用最小花费爬楼梯 这个问题的理解其实相当简单,只需看一下示例,基本就能明白其含义了。 2.算法原理 一、设定状态表 为了解决这个问题,我们首先要明确一个“状态表”。这个状态表其实就是一个记录…...
Springboot旅游管理系统设计与实现
** 🍅点赞收藏关注 → 私信领取本源代码、数据库🍅 本人在Java毕业设计领域有多年的经验,陆续会更新更多优质的Java实战项目,希望你能有所收获,少走一些弯路。🍅关注我不迷路🍅** 一、研究背景…...
c++学习笔记(10)
1. 二分答案是一种常用的算法思想,用于解决一些需要枚举所有可能答案的问题。它的基本思想是将问题的答案范围缩小到一半,然后根据一定的条件判断,再将答案范围缩小到一半,直到找到正确的答案或者确定不存在正确答案为止。 下面…...
Visual Studio - 添加快捷键图标
Visual Studio - 添加快捷键图标 1. Text Editor Toolbar Options -> Add or Remove Buttons -> Customize2. Toolbars3. Commands -> Debug4. Add Command...References 1. Text Editor Toolbar Options -> Add or Remove Buttons -> Customize 2. Toolbars B…...
突破边界:Web3开启数字化社会的新纪元
引言 随着科技的不断进步和数字化社会的发展,Web3正逐渐成为了人们关注的焦点。作为新一代互联网的演进形态,Web3具有突破传统边界、实现去中心化的特点,被认为将开启数字化社会的新纪元。本文将深入探讨Web3的概念、特点、应用场景…...
后进先出(LIFO)详解
LIFO 是 Last In, First Out 的缩写,中文译为后进先出。这是一种数据结构的工作原则,类似于一摞盘子或一叠书本: 最后放进去的元素最先出来 -想象往筒状容器里放盘子: (1)你放进的最后一个盘子(…...
超短脉冲激光自聚焦效应
前言与目录 强激光引起自聚焦效应机理 超短脉冲激光在脆性材料内部加工时引起的自聚焦效应,这是一种非线性光学现象,主要涉及光学克尔效应和材料的非线性光学特性。 自聚焦效应可以产生局部的强光场,对材料产生非线性响应,可能…...
反向工程与模型迁移:打造未来商品详情API的可持续创新体系
在电商行业蓬勃发展的当下,商品详情API作为连接电商平台与开发者、商家及用户的关键纽带,其重要性日益凸显。传统商品详情API主要聚焦于商品基本信息(如名称、价格、库存等)的获取与展示,已难以满足市场对个性化、智能…...
无法与IP建立连接,未能下载VSCode服务器
如题,在远程连接服务器的时候突然遇到了这个提示。 查阅了一圈,发现是VSCode版本自动更新惹的祸!!! 在VSCode的帮助->关于这里发现前几天VSCode自动更新了,我的版本号变成了1.100.3 才导致了远程连接出…...
CRMEB 框架中 PHP 上传扩展开发:涵盖本地上传及阿里云 OSS、腾讯云 COS、七牛云
目前已有本地上传、阿里云OSS上传、腾讯云COS上传、七牛云上传扩展 扩展入口文件 文件目录 crmeb\services\upload\Upload.php namespace crmeb\services\upload;use crmeb\basic\BaseManager; use think\facade\Config;/*** Class Upload* package crmeb\services\upload* …...
【Java学习笔记】BigInteger 和 BigDecimal 类
BigInteger 和 BigDecimal 类 二者共有的常见方法 方法功能add加subtract减multiply乘divide除 注意点:传参类型必须是类对象 一、BigInteger 1. 作用:适合保存比较大的整型数 2. 使用说明 创建BigInteger对象 传入字符串 3. 代码示例 import j…...
C++.OpenGL (14/64)多光源(Multiple Lights)
多光源(Multiple Lights) 多光源渲染技术概览 #mermaid-svg-3L5e5gGn76TNh7Lq {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-3L5e5gGn76TNh7Lq .error-icon{fill:#552222;}#mermaid-svg-3L5e5gGn76TNh7Lq .erro…...
CVE-2020-17519源码分析与漏洞复现(Flink 任意文件读取)
漏洞概览 漏洞名称:Apache Flink REST API 任意文件读取漏洞CVE编号:CVE-2020-17519CVSS评分:7.5影响版本:Apache Flink 1.11.0、1.11.1、1.11.2修复版本:≥ 1.11.3 或 ≥ 1.12.0漏洞类型:路径遍历&#x…...
2025年渗透测试面试题总结-腾讯[实习]科恩实验室-安全工程师(题目+回答)
安全领域各种资源,学习文档,以及工具分享、前沿信息分享、POC、EXP分享。不定期分享各种好玩的项目及好用的工具,欢迎关注。 目录 腾讯[实习]科恩实验室-安全工程师 一、网络与协议 1. TCP三次握手 2. SYN扫描原理 3. HTTPS证书机制 二…...
Linux 下 DMA 内存映射浅析
序 系统 I/O 设备驱动程序通常调用其特定子系统的接口为 DMA 分配内存,但最终会调到 DMA 子系统的dma_alloc_coherent()/dma_alloc_attrs() 等接口。 关于 dma_alloc_coherent 接口详细的代码讲解、调用流程,可以参考这篇文章,我觉得写的非常…...
