Rust语言字符串类型String和str的对比学习随笔

String 是Rust语言中最常见的字符串类型,其数据存储于堆分配的缓冲区中,拥有数据的所有权,支持修改扩容,采用UTF-8编码,即可动态增长的字符串类型,它的只读借用的对应类型是基础类型 &str ,准确地说,str是一个切片类型,即一个DST(动态大小类型),Rust语言要求栈上定义的变量必须是编译期已知大小的类型,即实现Sized trait, 所以我们以固定大小的指针(引用)类型来借用他(指向他),即&str形式!以此形式来定义栈变量。

对于堆上数据、可以是?Sized的,即可以是固定大小类型,也可以是动态大小类型,一般通过指针(引用)等固定大小类型间接指向,列如:Box<T> , String等。

对于泛型可以是&Sized的,而Trait Object,即dyn Trait是DST类型,所以只允许通过指针(引用)来使用。

&str是一个不可变的引用类型,不拥有数据的所有权,仅允许只读操作,仅指向字符串切片或字符串字面量,用于函数传参、无需修改字符串的只读操作,避免不必要的字符串复制。而String很显然适用于动态修改字符串的情况。

String的三个元数据:指针、长度、容量,分别为指向堆上的字符串,字符串长度,以及堆上分配的内存容量。而&str的二个元数据,分别为:指向字符串的指针以及字符串长度。

&str容易混淆的点:

//(1) 不可变字符串引用(即引用指向的字符串不可修改)
let s = "hello"; // s: &str //
//s[0] = 'H'; // ❌ 编译错误!不能修改 &str 的内容

//(2) 引用(指针本身)可修改指向,但是其指向的字符串依旧不可修改。
let mut s: &str = "hello";
s = "world"; // ✅ 可以,只是改变了引用的目标(指向新的额字符串)
//s[0] = 'W'; // ❌ 依旧不能修改内容

//(3) &mut str 是什么意思???
//答:他极不常见,极少使用,不提议使用。
//他只允许你在不改变字符串长度的限制下,修改其内容。
//注意&str不支持数组的index访问方式,如astr[0].
//由于Rust的字符串String和&str都是utf-8编码,任意修改某个字节,会导致utf-8失效。
let mut s = String::from("hello");
let slice: &mut str = &mut s[..]; // 整个字符串的可变切片
// 修改第一个字符(注意:必须是单个 UTF-8 字节,这里 'h' 和 'H' 都是 1 字节)
//slice[0] = 'H'; // 错误❌️!无法直接通过数组索引的方式访问字符串切片;
//可以通过下面方式,先转化为字节序列,然后在修改,当然前提是不破坏utf-8编码。
unsafe{
    let bytes:&mut[u8] = slice.as_bytes_mut();
    bytes[0] = b'H';
}
println!("{}", s); // 输出 "Hello"

//(4) &mut str不允许修改字符串的长度。
let mut s = String::from("hi");
let slice: &mut str = &mut s[..];
//slice.push('!'); // ❌ 没有 push 方法!&mut str 没有提供修改长度的 API
// &mut str  几乎没有提供修改方法。
//要修改字符串内容(如拼接、插入、替换),
//你一般需要用  String  或  &mut String才可以。

小总结:

(1) &str 是不可变的:你不能通过它修改字符串内容。

(2) &mut str 是可变引用,但只能在不改变长度的前提下修改内容,且极少见。

(3) 日常开发中,修改字符串一般用 mut String ,&mut String,而不是 &mut str 。

(4) &str 主要用于只读访问和函数参数(由于它更通用,可以接受 String 的切片、字面量等)。

对于上面(4)的深入讨论:在函数定义中,优先使用 &str 作为字符串参数类型,是 Rust 的一个最佳实践,由于它更通用,更宽泛,优点归纳如下:

  • 可以接受&str.
  • 可以接受字符串字面量,如: let a = “hello world”;
  • 可以接受&String和&String[..]形式的切片。
fn greet(name: &str) { //若换成String,则每次函数调用都需要将字符串先转化成String才可以。
    println!("Hello, {}!", name);
}

fn main() {
    let s1 = "Alice";          // 类型:&str
    let s2 = String::from("Bob"); // 类型:String

    greet(s1);   // ✅ 直接传 &str
    greet(&s2);  // ✅ &String 自动转为 &str
}

如果你的函数写成 fn greet(name: String) ,那调用者必须传一个 String ,哪怕只是想传一个字面量,也得先 to_string() ,效率低且麻烦。所以:用 &str 让函数更灵活、更高效、更符合 Rust 的“零成本抽象”哲学。

但是如果函数需要修改字符串,该如何定义参数呢?&str是只读的,不可以!&mut str虽然是可修改的,但是不可以改变字符串长度,只允许修改其内容,并且不可以破坏utf-8编码,而且他们也几乎没有提供可修改的API,除非都转换为字节序列:&mut [u8] , 所以推荐使用:&mut String , 当然mut String也可以,只不过需要获取字符串的所有权。小栗子:

fn append_gantan(s: &mut String) {
    s.push('!'); // ✅ 可以安全修改字符串,伸缩字符串,从而改变长度。
}

fn main() {
    let mut name = String::from("Hi");
    append_gantan(&mut name);
    println!("{}", name); // 输出 "Hi!"
}

最后再啰嗦几句:&mut str 只允许在不改变字符串长度的前提下,原地修改字符串内容,所以最好不用,实则他也没什么用处,而且一不小心就会破坏UTF-8编码。

只读提议用&str, 可变提议用&mut String或mut String ,完全够用了!

对于String和mut String大家也不要想当然,以为直接传参会导致字符串复制,从而降低性能,实则不必担心,由于String默认是Move语义,转移所有权的,不会复制字符串本身!

若必定要死扣,追根到底剖析Move语义的类型存不存在复制?!那还是有一点的,好比一个指针,我们复制指针,也只是复制它记录的目标地址值而已,不会复制他所指向的目标对象!!!

对于String的Move语义,Rust只会复制他的元数据:指针(指向字符串)、字符串长度、预分配的内存大小而已,不会复制它拥有的字符串本身!Move语义好比是一个剪切粘贴操作Copy语义好比是一个复制粘贴操作!所以原来的String变量存储记录的元数据被Rust清空了,或标记失效了,这些元数据被Rust复制到了新String变量中,所后来者拥有了这个字符串的所有权。

好了就聊到此处,天马行空,不求逻辑严谨,随意探讨罢了!

后记:

我是一个普通的c++老码农,Rust语言爱好者,如今已四十不惑,青丝白发生,人谈不上机智,然多年来敏而好学,不敢懈怠, 随处学随处记,笔耕不辍,坚信好脑瓜不如烂笔头!如今赋闲家中,翻腾出来这些粗鄙之文,分享出来,抛砖引玉!不过本人水平有限,许多时候也是不求甚解,匆促行文,故而文中难免存有谬误,望诸君海涵指正!

帮忙点个赞吧!鼓励我坚持写下去!谢谢啦!

© 版权声明
THE END
如果内容对您有所帮助,就支持一下吧!
点赞0 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容