不灭的焱

革命尚未成功,同志仍须努力 下载Java21

作者:AlbertWen  添加时间:2026-07-05 15:41:14  修改时间:2026-07-06 19:45:17  分类:01.Rust编程  编辑

下面用一句话先概括:

String 是拥有所有权、可变、堆上的字符串;&String 是对 String 的引用;&str 是字符串切片/视图;字符串字面量 "hello" 的类型通常是 &'static str

一、四者核心区别

类型 中文理解 是否拥有数据 是否可变 常见来源
String 字符串对象 / 字符串容器 拥有 可变 String::from("abc")
&String String 的引用 不拥有 通常不可变 &s
&str 字符串切片 / 字符串视图 不拥有 不可变 "abc"s.as_str()
字符串字面量 写死在代码里的字符串 程序静态区拥有 不可变 "hello"

二、String 是什么?

String 是 Rust 标准库提供的可增长、可修改、拥有所有权的字符串类型。

它的数据通常存放在堆内存中。

fn main() {
    let mut s = String::from("hello");

    s.push_str(", rust");

    println!("{}", s);
}

输出:

hello, rust

这里的 s 是一个 String,它拥有 "hello, rust" 这段字符串数据。

三、&String 是什么?

&String 是对 String 的引用。

它本身不拥有字符串数据,只是借用别人手里的 String

fn main() {
    let s = String::from("hello");

    let r: &String = &s;

    println!("{}", r);
}

可以理解成:

s  拥有 String 数据
r  只是指向 s

也就是说:

let s = String::from("hello");
let r = &s;

r 不能随便修改 s,因为它只是不可变引用。

四、&str 是什么?

&str 叫做字符串切片,也可以理解为“字符串的一段视图”。

它不拥有数据,只是指向某段 UTF-8 字符串内容。

fn main() {
    let s = String::from("hello rust");

    let part: &str = &s[0..5];

    println!("{}", part);
}

输出:

hello

这里:

&s[0..5]

得到的是 &str,它只表示 String 中的一部分。

五、字符串字面量是什么?

字符串字面量就是代码里直接写的字符串:

let s = "hello";

很多初学者会以为 sString,但其实不是。

它的类型是:

&'static str

也就是:

let s: &'static str = "hello";

'static 表示这段字符串在程序整个运行期间都有效。

它通常存放在程序的只读静态区域,不在堆上。

fn main() {
    let s = "hello";

    println!("{}", s);
}

这里的 s&str,不是 String

六、它们之间的关系

可以这样理解:

String
  └── 可以借用成 &String
  └── 可以转换/借用成 &str

字符串字面量 "hello"
  └── 本身就是 &'static str

例如:

fn main() {
    let s1: String = String::from("hello");

    let r1: &String = &s1;

    let r2: &str = &s1;

    let literal: &str = "hello";
}

其中:

let r2: &str = &s1;

之所以能成立,是因为 String 可以自动解引用成 str,所以 &String 经常可以被当成 &str 使用。

七、函数参数中应该用哪个?

通常推荐:

fn print_msg(msg: &str) {
    println!("{}", msg);
}

而不是:

fn print_msg(msg: &String) {
    println!("{}", msg);
}

为什么?

因为 &str 更通用。

fn print_msg(msg: &str) {
    println!("{}", msg);
}

fn main() {
    let s1 = String::from("hello");
    let s2 = "world";

    print_msg(&s1);
    print_msg(s2);
}

这个函数既能接收 String,也能接收字符串字面量。

如果写成:

fn print_msg(msg: &String) {
    println!("{}", msg);
}

那它只能接收 String 的引用,不能直接接收 "hello"

八、对比示例:String&str

1. String 可以修改

fn main() {
    let mut s = String::from("hello");

    s.push_str(" rust");

    println!("{}", s);
}

输出:

hello rust

2. &str 不能修改

fn main() {
    let s: &str = "hello";

    // s.push_str(" rust"); // 错误,&str 没有 push_str 方法

    println!("{}", s);
}

&str 只是一个视图,不负责管理和扩展字符串内容。

九、String&str

常见方式有三种:

fn main() {
    let s = String::from("hello");

    let a: &str = &s;
    let b: &str = s.as_str();
    let c: &str = &s[..];

    println!("{}, {}, {}", a, b, c);
}

推荐常用:

s.as_str()

或者函数传参时直接:

print_msg(&s);

十、&strString

常见方式:

fn main() {
    let s1: &str = "hello";

    let s2: String = s1.to_string();
    let s3: String = String::from(s1);

    println!("{}", s2);
    println!("{}", s3);
}

两种都可以。

十一、内存角度理解

String

let s = String::from("hello");

大致可以理解成:

栈上:
s = {
    ptr: 指向堆内存,
    len: 5,
    capacity: 5
}

堆上:
hello

String 自己管理这块堆内存。

&str

let s = "hello";

大致可以理解成:

s = {
    ptr: 指向某段字符串数据,
    len: 5
}

它只是一个引用视图,不管理内存。

十二、为什么有时候 &String 不推荐?

例如:

fn print_name(name: &String) {
    println!("{}", name);
}

这个写法可以运行,但不够灵活。

更推荐:

fn print_name(name: &str) {
    println!("{}", name);
}

因为这样既可以传:

let name = String::from("Alice");
print_name(&name);

也可以传:

print_name("Bob");

所以在 Rust 里,函数参数能用 &str 就尽量用 &str

十三、常见写法总结

创建 String

let s1 = String::from("hello");
let s2 = "hello".to_string();
let s3 = String::new();

创建 &str

let s1 = "hello";

let s2 = String::from("hello");
let s3 = s2.as_str();

函数接收字符串

推荐:

fn show(s: &str) {
    println!("{}", s);
}

调用:

fn main() {
    let a = String::from("hello");
    let b = "world";

    show(&a);
    show(b);
}

十四、容易混淆的点

1. "hello" 不是 String

let s = "hello";

这里 s 是:

&str

不是:

String

如果要 String

let s = String::from("hello");

或者:

let s = "hello".to_string();

2. &String&str 不完全一样

let s = String::from("hello");

let a: &String = &s;
let b: &str = &s;

a 是对整个 String 对象的引用。

b 是对字符串内容的切片引用。

通常函数参数更应该使用 &str

3. String 会发生所有权移动

fn take(s: String) {
    println!("{}", s);
}

fn main() {
    let s = String::from("hello");

    take(s);

    // println!("{}", s); // 错误,s 已经被移动
}

如果不想转移所有权,就用引用:

fn take(s: &str) {
    println!("{}", s);
}

fn main() {
    let s = String::from("hello");

    take(&s);

    println!("{}", s); // 可以继续使用
}

十五、一张图理解

String
拥有字符串数据,可修改
例如:String::from("hello")

&String
借用一个 String
例如:&s

&str
借用一段字符串内容
例如:"hello"、s.as_str()、&s[0..2]

字符串字面量
代码里直接写的字符串
例如:"hello"
类型:&'static str

十六、实际开发建议

记住这几个原则:

场景 推荐类型
需要拥有字符串并修改 String
只是读取字符串参数 &str
函数参数 优先 &str
需要保存到结构体里 多数用 String
字面量常量 &'static str
不推荐作为普通函数参数 &String

例如:

struct User {
    name: String,
}

fn greet(name: &str) {
    println!("Hello, {}", name);
}

fn main() {
    let user = User {
        name: String::from("Alice"),
    };

    greet(&user.name);
    greet("Bob");
}

这个例子里:

name: String

因为结构体需要拥有用户名。

fn greet(name: &str)

因为函数只是读取,不需要拥有。