旅游建设网站目的及功能定位,简单的网站开发的软件有哪些,嘉兴网站推广优化费用,网络销售技巧和话术有人可能会说#xff0c;字符串这么简单还用介绍#xff1f;但是很多人学习rust受到的第一个暴击就来自这浓眉大眼、看似毫无难度的字符串。
请看下面的例子。
fn main() {let my_name World!;greet(my_name);
}fn greet(name: String) {println!(Hello…有人可能会说字符串这么简单还用介绍但是很多人学习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]、Vecu8 、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的中文翻译切片这个词很容易让人认为是从一个连续序列中切下来一段很难与引用联想在一起我认为翻译成片段可能更合适。
理解了slicestr就好理解了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_BACKTRACE1 environment variable to display a backtrace每个汉字占用3个字节s[0..2]只取了前两个节点所以报错信息告诉你index 2是不是字符的边界。所以对字符串使用切片语法时需要格外谨慎。
注意 Rust里字符串字面量的类型是static str这涉及到静态生命周期有兴趣同学可以参考生命周期相关的文章。
String
在rust中String不是基本类型是个复合类型它包含了一个私有的u8的vec。
pub struct String {vec: Vecu8,
}因为它的唯一字段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]来保存。
Vecu8
Vecu8 是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: VecOsString 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(rC:\);
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/