string和&str

rust中操作字符串比较常用的是String和&str 比如:

1
2
3
4
5
6
let string = String::from("hello, world");

//以下三种都为&str类型
let s = "hello, world";        //字面常量 string literal, 存储在常量区
let hello = &string[..5];      //从string中得到&str
let world = &s[6..];          //从&str中得到&str

内存布局

以下是String与&str在内存中的结构,String占用了3个字长,&str占用两个字长。他们都有一个指向底层数组的指针。

实现源码

string的内部实现就是一个Vec[u8]

1
2
3
4
5
pub struct String {

 vec: Vec<u8>,

}

vec本身的实现如下

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
pub struct Vec<T, #[unstable(feature = "allocator_api", issue = "32838")] A: Allocator = Global> {

 buf: RawVec<T, A>,

 len: usize,

}

pub(crate) struct RawVec<T, A: Allocator = Global> {

 ptr: Unique<T>,

 cap: usize,

 alloc: A,

}

以上可以看出string的内存占用从何而来

str是一个原生类型,字面常量的完整类型是&‘static str, 虽然没有str的类型定义,但是从它的方法中也可以找到它的内部结构

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20

let story = "Once upon a time...";

let ptr = story.as_ptr();
let len = story.len();

// story has nineteen bytes
assert_eq!(19, len);

// We can re-build a str out of ptr and len. This is all unsafe because
// we are responsible for making sure the two components are valid:
let s = unsafe {
    // First, we build a &[u8]...
    let slice = slice::from_raw_parts(ptr, len);

    // ... and then convert that slice into a string slice
    str::from_utf8(slice)
};

assert_eq!(s, Ok(story));

str的其他使用方式

理论上可以通过各种方式得到str类型本身,然而编译器并不能通过, 主要是因为str类型为unsize type, 并不能够在栈中存在。比如

1
2
3
4
5
6
7
8
9
let string = String::from("hello, world");


let s = "hello, world";  

//以下三个变量都为str类型
let ss = *s;
let h = string[..5];
let w = s[6..];

Box<str>

既然str不能在栈上存在,那么能不能显式放到堆上呢?答案是可以的,那就是Box<str>,但是Box<str>只能通过以下一种方式初始化

1
2
3
4
let string = String::from("hello, world");

//除了&str,你还可以使用Box<str>,下面是可以创建Box<str>的唯一方法
let box_s = string.into_boxed_str();

Box<str>同样是占用2个字长,事实上,当一个unsize type被放在引用或者指针后面的时候它就是一个2字长胖指针,比如&[u8], &dyn Trait都是如此, *const [T]也是一样

Cow<str>

很多时候,我们希望函数接口保持兼容性与灵活性,通常使用&str而不是String或者·&String作为接口,但是作为返回,如果这个函数并没有修改这个&str,那么我们希望可以直接返回它, 但是当这个函数改变了值,我们只能复制这个str并且返回String类型以获得owned值。这个时候Cow<str>就派上了用场。以下是定义

1
2
3
4
5
6
7
pub enum Cow<'a, B>
where
    B: 'a + ToOwned + ?Sized,
 {
    Borrowed(&'a B),
    Owned(<B as ToOwned>::Owned),
}
  1. 标准库中,对于Cow<str>的使用有from_utf8_lossy返回&str
  • 不需要分配新的内存
1
2
3
4
5
6
// some bytes, in a vector
let sparkle_heart = vec![240, 159, 146, 150];

let sparkle_heart = String::from_utf8_lossy(&sparkle_heart);

assert_eq!("💖", sparkle_heart);
  • 返回String, 由于修改了字符串内容, 重新复制了内存
1
2
3
4
5
6

// some invalid bytes
let input = b"Hello \xF0\x90\x80World";
let output = String::from_utf8_lossy(input);

assert_eq!("Hello �World", output);
  1. 在crate regex中有一个replace方法,同样使用Cow<str>作为返回值
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
 pub fn replace_all<'t, R: Replacer>(

 &self,

 text: &'t str,

 rep: R,

 ) -> Cow<'t, str> {

 self.replacen(text, 0, rep)

 }
  1. 我们可以直接使用Cow<str>, 打印或者写入文件
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
//new 一个正则表达式
let regex = Regex::new(target).unwrap();
//返回一个Cow<str>
let cow = regex.replace_all(text, replacement);
//打印字符串
println("str is {}", cow);

//输出到文件, 这个需要注意&*cow得到&str类型
// &(*cow.deref())
fs::write("output.txt", &*cow)