一文解决Rust字符串:String,str,&String,&str,CString,CStr

一、str和&str和String的区别

1.存放位置,可变不可变:

str来源于Rust本身的数据类型,而String类型来自于标准库。首先看一下str 和 String之间的区别:String是一个可变的、堆上分配的UTF-8的字节缓冲区。而str是一个不可变的固定长度的字符串,如果是从String解引用而来的,则指向堆上,如果是字面值,则指向静态内存。

2.来看一个比较奇怪的例子
let s:&str="abcd";
char *s="abcd";

在C语言中,s是一个指针类型,指向一个字符串,但是它不知道长度,操作不安全。
在Rust中,s的类型是&str,而“abcd”是str类型,这里的“abcd”就是字符串字面值,存放在Stack上的。s 是切片,切片是一个结构体,包含两个字段,一个是指向数据的指针,另一个是数据的长度。因此,我们有可能采用必要的措施,安全的使用变量 s,这就是比C语言更安全的地方。

3.String
#[derive(PartialOrd, Eq, Ord)]
pub struct String {
    vec: Vec<u8>,
}

切片 &str 虽然可以安全使用,但是,我们很难动态修改其内容 —— 其地址、长度都是固定的。于是 Rust 提供了数据类型 String。String 包含了数据指针、数组容量、数据长度等三个字段。如果新修改的数据长度在其容量范围内,数据可以原地修改。如果新修改的数据长度超出了容量范围,它可以重新申请更大的内存。于是我们看到,String 和 &str 是两个完全不一样的结构体。为什么字符串要保留这两种形式?原因就是效率。Rust 希望在数组容量不会变化的时候,用 &str。在数组长度可能发生变化的情况下,使用 String。
补充几点关于String的:String 总是 “有效的” UTF-8,Rust字符串只能是UTF-8的类型。另外,不能用索引访问 String,因为有些字符的编码可能是多个字节,取到中间就是没有意义的。

4.&str或str转String
let s: String = "hello".to_string();
let t: String = String::from("hello");
5.String转&str或str

在 Rust 中,凡是需要用 &str 的地方,都可以直接用 &String 类型的数据。&String和&str其实也是差不多的。

fn greet(s: &str) {
    ...
}
fn main() {
    let s: String = String::from("hello");
    greet(&s);
}

那么如何选择使用&str还是String呢?
规则很简单,一般情况下,&str 用于只读数据,String 用于可修改的数据。

二、CString和CStr或&CStr

1.CString和CStr

对于C语言来说,字符串有两种,一种是共享的只读字符串 char * ,不能修改。另一种是动态分配的可变字符串 char [],可以修改。
而在Rust里面,字符串是由字符的 UTF-8 编码组成的字节序列。表示的类型有很多种。
字符串则比较复杂,Rust 中的字符串,是一组u8组成的 UTF-8 编码的字节序列,字符串内部允许NULL字节;但在 C 中,字符串只是指向一个char的指针,用一个NULL字节作为终止。
我们需要做一些特殊的转换,在 Rust FFI 中使用std::ffi::CStr,它表示一个NULL字节作为终止的字节数组,可以通过 UTF-8 验证转换成 Rust 中的&str。
CStr:表示以空字符终止的 C 字符串或字节数组的借用,属于引用类型。一般用于和 C 语言交互,由 C 分配并被 Rust 借用的字符串。
CString:表示拥有所有权的,中间没有空字节,以空字符终止的字符串类型。一般用于和 C 语言交互时,由 Rust 分配并传递给 C 的字符串。
下面这段代码,在这里get_string使用CStr::from_ptr从C的char*获取一个字符串,并且转化成了一个String。

fn get_string() -> String {
unsafe {
let raw_string: *mut c_char = char_func();
let cstr = CStr::from_ptr(raw_string);
cstr.to_string_lossy().into_owned()
}
}

和CStr表示从C中来,rust不拥有归属权的字符串相反,CString表示由rust分配,Rust拥有所有权,可以进行修改,用以传给C程序的字符串。

use std::ffi::CString;
use std::os::raw::c_char;
extern {
fn my_printer(s: *const c_char);
}

let c_to_print = CString::new("Hello, world!").unwrap();
unsafe {
my_printer(c_to_print.as_ptr()); // 使用 as_ptr 将CString转化成char指针传给c函数
}
2.CString 是一种类型,表示一个拥有的、C兼容的、以null结尾的字符串,中间没有null字节。

这种数据类型的目的是基于 Rust 的字节切片或 vector 数组生成 C 语言兼容的字符串。这种类型的实例需要确保字节数据中间不包含内部 0 字节(“null字符”),最后一个字节为0(“null终止符”)。
CString 与 &CStr 的关系就像 String 和 &str 的关系一样:CString、String 自身拥有字符串数据,而 &CStr、&str 只是借用数据而已。

3.输出指向 C 字符串的裸指针

CString 基于 Deref trait 实现了as_ptr方法。该方法给出一个 *const c_char 类型的指针,可以把这个指针传递给外部能够处理 null结尾的字符串的函数,例如 C 语言的 strdup() 函数。如果 C 语言代码往该指针所知的内存写入数据,将导致无法预测的结果。因为 C 语言所接受的这样的裸指针不包含字符串长度信息。

4.输出 C 字符串的切片

也可以使用 CString::as_bytes 方法从 CString 获取 &[u8] 切片。以这种方式生成的切片不包含尾部 null 终止符。这在调用一个外部函数时非常有用,该函数接受一个不一定以 null结尾的 *const u8参数,再加上另一个字符串长度的参数,比如 C 的 strndup()。当然,您可以使用 len 方法获得切片的长度。
如果想得到一个以 null 结尾的 &[u8] 切片,可以使用 CString::as_bytes_with_nul 方法。
无论获得 null 结尾的,还是没有 null 结尾的切片,都可以调用切片的 as_ptr 方法获得只读的裸指针,以便传递给外部函数使用。

三、综合比较

一文解决Rust字符串:String,str,&String,&str,CString,CStr_第1张图片

你可能感兴趣的:(Rust学习笔记,rust,开发语言,后端)