Rust 中的String与所有权机制

本文介绍了Rust中String数据类型和所有权概念,包括如何创建String,所有权与作用域的关系,转移与拷贝的区别,以及引用和可变引用的使用。Rust通过所有权系统提供内存安全,避免了手动内存管理的常见问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、string

之前学习过 Rust 只有几种基础的数据类型,但是没有常用的字符串也就是String,今天来学习一下 String;
Rust 中 String 是标准库的一部分,也就是 std::String , 但是这个 String 与其他语言中的 String 稍有不同,比如:
在这里插入图片描述

可以看到,当我想要以一种为指定字符串类型的方式定义了一字符串变量时,编译器自动显示出的类型是 &srr 而非 String, 而我指定了 String 类型后仍然有错;而从编译器给出的提示不难看出,“hello” 这样定义得到的是一个 “&str” 类型的值而非是个字符串,那么我们先假定这是一种未知的类型,后续再处理它,先去想办法定义出我们的字符串,打开官方文档: https://doc.rust-lang.org/std/string/struct.String.html

可以看到官方文档第一个示例告诉我们要像这样创建字符串:
在这里插入图片描述

但是官方没说为什么要这样定义,幸好 Rust 的源码是可以点进去的, 从 String::from 向里一步步执行:
在这里插入图片描述

from 执行的操作只有一个就是调用 to_owned 函数,但是需要注意,这里传进来的参数类型仍然是"&str"; 然后 to_owned 下一步是调用 as_bytes().to_owned() ,最后这个 to_owned() 则是调用了 to_vec ;
那么至此也就明白了,对于这一行代码:

    let hello = String::from("Hello, ");

我们传入的 “Hello,” 这会被编译器认为是一个字符数组,也就是一个字符串常量,即无法对其造成改变的一个对象;但是我们需要的是一个可变的字符串而非一组固定的字符,因此编译器将这个字符数组扩展为了一个 vector , 也就变成了一个可变的字符数组,也就是我最终想要的字符串;

其实这和C++差不多,只不过C++string底层应该直接是个指针而不是个vector

这样一来 “&str” 也就理解了,就是一个常量字符数组,所以是不可变的。在官方文档搜一下,果然也有,并且还有个好听的名字,字符串切片(string slice),具体参考: https://doc.rust-lang.org/std/primitive.str.html

官方文档中还介绍了两种类型的互转方法:
在这里插入图片描述

剩下的使用就是这两种类型自带的一些接口了,具体请参阅官方文档,这里不再细述;

二、所有权

2.1 所有权与作用域

很多小伙伴开始学习 Rust 是因为听说这是一种比C++更安全的语言,所以来了解一下,那么它安全在哪里?就安全在所有权机制,不再需要开发者像C++一样的去人工管理内存。

首先,所有程序都必须管理其运行时使用计算机内存的方式。一些语言中具有垃圾回收机制,在程序运行时有规律地寻找不再使用的内存,比如Java;另一些语言中,程序员必须亲自分配和释放内存,比如C/C++。Rust 则选择了第三种方式:通过所有权系统管理内存,编译器在编译时会根据一系列的规则进行检查。如果违反了任何这些规则,程序都不能编译。在运行时,所有权系统的任何功能都不会减慢程序。所有权有以下规则:

* Rust 中的每一个值都有一个 所有者(owner)。
* 值在任一时刻有且只有一个所有者。
* 当所有者(变量)离开作用域,这个值将被丢弃。

举个例子,有以下一段代码:

fn main() {
    let s1 = "hello";
    {                
        let s = "hello s"; 
    }  
    let s2 = "hello";
}

在上面的代码中, s 被一个括号圈住了,那么在这个括号里"hello s" 的所有者就是 s,而 s 的生命周期也只在括号范围内,也就是 s1 出现时 s 未出现, s2 出现时 s 已经死去。

有过C++ 经验的小伙伴看到这肯定很熟悉,这不就是 RAII 吗,或者说 Rust 中的每个变量都是个智能指针。

2.2 对所有权的操作

2.2.1 转移

刚刚说到这种所有权机制与C++的 RAII 很像,变量也和智能指针很像,那么是不是就和智能指针一样呢,测试一下:
在这里插入图片描述

图上可以看到,先定义了 s1 ,然后定义 s2 ,然后将 s1 传递给 s2 ,此时再使用 s1 会报错,提醒你s1 的值已经被转移出去了, 这时 s1 就已经被清空了。因为 Rust 变量离开作用域时会回收,所以如果这里不清空,在程序结束时s1 s2都会被回收,那么一块内存就会回收了两次,因此 Rust 的机制中 = 操作是转移而非拷贝。

看到这里,还是觉得是智能指针,只不过是unique_ptr,QAQ

2.2.3 拷贝

= 是转移而不是拷贝,那么想要使用拷贝该怎么写呢?比如字符串这样:
在这里插入图片描述

调用 clone 函数就行了。

然后有一个很有意思的事情,比如:
在这里插入图片描述

i32 类型变量不需要使用 clone 之类的函数,= 居然就是拷贝而不是转移;
官方给出的解释是 “像整型这样的在编译时已知大小的类型被整个存储在栈上,所以拷贝其实际的值是快速的。这意味着没有理由在创建变量 y 后使 x 无效。” 恕我直言,这不就是Rust中所有用到堆的变量就用的是智能指针吗。。。。。当然也可能是我理解不够深,反正我目前为止的感受就是这样。。。。

2.2.3 传递

之前提到过 “值在任一时刻有且只有一个所有者” ,那么如果将值传递给函数会怎么样呢:
在这里插入图片描述

很明显,作为参数传递给函数之后就失去了所有权;
但这样会带来一个问题,这个值如果不只一个函数再用后续怎么办?用函数返回值返回!
但是如果参数不只有一个呢?为每个函数的返回值都定一个结构体?
这样太麻烦了,万幸的是 Rust 提供了 引用。

2.3 引用

2.3.1 借用

官方说明中“引用(reference)像一个指针,因为它是一个地址,我们可以由此访问储存于该地址的属于其他变量的数据。 与指针不同,引用确保指向某个特定类型的有效值。”
很好理解,改一下刚才的代码:
在这里插入图片描述

可以用了,修改就是函数声明参数时加了一个&,传参时也加了一个&;

变量 str 有效的作用域与函数参数的作用域一样,不过当 str 停止使用时并不丢弃引用指向的数据,因为 str 并没有所有权。当函数使用引用而不是实际值作为参数,无需返回值来交还所有权,因为就不曾拥有所有权。

Rust 将创建一个引用的行为称为 借用(borrowing)。正如现实生活中,如果一个人拥有某样东西,你可以从他那里借来。当你使用完毕,必须还回去,并不拥有它,因此借用的值无法修改。
在这里插入图片描述

2.3.2 可变引用

正如不可变变量与可变变量一样,引用也可以变为可变引用,加个 mut 关键试试:
在这里插入图片描述

当然,像这段代码所写的,想要成为可变引用的前提是自身就是可变的。
另外,由于 Rust 的三条基础规则之“值在任一时刻有且只有一个所有者”,那么对于一个可变变量在同一个时刻也就不可以有多个引用;换个角度理解比如用一个 s1 作为 s 的可变引用后,那么 s 将不再可用,也自然不能再对一个无法使用的变量创建引用,如果这样使用编译器就会报错:
在这里插入图片描述

<think>好的,用户现在让我把Rust所有权机制整理成思维导图。首先,我需要回顾一下之前的历史对话。用户之前问过数据湖架构,我给出了详细的层次结构回答,可能用户喜欢结构化的信息,所以这次他们希望用思维导图的形式来整理Rust所有权机制。 接下来,我需要确定用户的需求。他们可能正在学习Rust,特别是所有权机制,这是Rust的核心概念之一。作为新手,可能对所有权、借用、生命周期这些概念感到困惑,所以思维导图可以帮助他们理清各个部分的关系。用户可能希望有一个清晰的框架,方便记忆和理解。 我需要考虑如何将所有权机制分解成几个主要部分。通常,所有权机制包括三个主要规则:所有权转移、借用(引用)和生命周期。此外,堆栈堆的区别、Copy trait、作用域、移动语义和克隆这些相关概念也需要涵盖进去。还有可能用户想了解常见问题和解决方案,比如数据竞争、悬垂引用等。 接下来,我需要确定每个部分的子主题。例如,所有权规则下的三个核心规则,移动语义中的所有权转移和函数传参,借用中的可变不可变引用,生命周期的显式标注和省略规则等。同时,堆栈和堆的区别也是基础,需要简要说明。 另外,用户可能对实际中的问题感兴趣,比如如何避免数据竞争,如何处理悬垂引用,所以需要给出对应的解决策略。比如使用引用计数Rc或Arc,或者利用生命周期标注来确保引用的有效性。 在整理结构时,要保持逻辑清晰,层次分明,便于用户理解。可能需要先概述所有权机制,然后分块详细展开,每个块下再细分。同时,结合之前回答数据湖架构的结构,用户可能习惯使用层级分明的列表,所以用类似的格式呈现思维导图的内容。 需要检查是否有遗漏的重要概念,比如Copy trait的作用,哪些类型默认实现了Copy,以及移动和克隆的区别。此外,作用域对所有权的影响,变量离开作用域时所有权如何转移或释放,这也是关键点。 最后,要确保用中文准确表达,术语正确,例子恰当。例如,在说明移动语义时,可以提到变量赋值或函数传参导致所有权转移,原来的变量不再可用,而克隆则创建了数据的深拷贝,所有权独立。 总结下来,整个思维导图的结构应该覆盖基本概念、核心规则、相关机制、常见问题及解决方案,以及示例和注意事项。这样用户可以通过导图快速掌握所有权机制的全貌和细节。</think>以下是Rust所有权机制的**结构化整理**,可基于此构建思维导图: --- ### Rust所有权机制核心框架 (思维导图主干) ``` 所有权机制 ├── 三大核心规则 ├── 堆栈(Stack) vs 堆(Heap) ├── 移动(Move)语义 ├── 借用(Borrowing)机制 ├── 生命周期(Lifetime) ├── 作用域(Scope)释放 └── 常见问题解决方案 ``` --- #### **1. 三大核心规则** ``` 1. 每个值有且仅有一个所有者(Owner) 2. 所有者离开作用域时值被自动丢弃(Drop) 3. 值的所有权可通过移动(Move)转移,不可被隐式复制(除非实现Copy trait) ``` --- #### **2. 堆栈(Stack) vs 堆(Heap)** ``` ├── 堆栈 │ ├── 固定大小类型 │ └── 自动内存管理(FILO) └── 堆 ├── 动态大小类型(需显式分配) └── 通过指针访问(Box<T>, String, Vec<T>等) ``` --- #### **3. 移动(Move)语义** ``` ├── 所有权转移场景 │ ├── 变量赋值(未实现Copy的类型) │ ├── 函数传参 │ └── 函数返回值 ├── 复制(Copy)克隆(Clone)区别 │ ├── Copy:栈数据按位复制(如i32, bool) │ └── Clone:显式深拷贝(如String::clone()) ``` --- #### **4. 借用(Borrowing)机制** ``` ├── 引用类型 │ ├── 不可变引用(&T) │ └── 可变引用(&mut T) ├── 借用规则 │ ├── 同一作用域内: │ │ ├── 任意数量不可变引用 │ │ └── 或唯一一个可变引用(互斥) │ └── 引用必须始终有效(无悬垂指针) ``` --- #### **5. 生命周期(Lifetime)** ``` ├── 显式标注 │ ├── 语法:`'a` │ └── 示例:`fn longest<'a>(x: &'a str, y: &'a str) -> &'a str` ├── 生命周期省略规则 │ ├── 每个输入引用自动分配生命周期 │ ├── 若只有一个输入生命周期,赋予所有输出 │ └── 方法中&self/&mut self生命周期赋予输出 ``` --- #### **6. 作用域释放** ``` ├── 变量离开作用域时: │ ├── 调用drop()方法释放资源 │ └── 所有权链自动清理(如Vec释放其堆内存) ``` --- #### **7. 常见问题解决方案** ``` ├── 数据竞争 │ └── 强制通过借用规则避免(编译期检查) ├── 悬垂引用 │ └── 生命周期标注确保引用有效性 ├── 需要多所有者场景 │ └── 使用Rc<T>/Arc<T>(引用计数智能指针) ``` --- ### 思维导图关键连接点 - **所有权转移** → 触发**移动语义** → 影响**堆内存管理** - **借用规则** ↔ **生命周期标注** → 共同保障内存安全 - **作用域结束** → 触发**Drop** → 释放资源 --- ### 示例代码片段(辅助理解) ```rust // 移动语义示例 let s1 = String::from("hello"); let s2 = s1; // s1所有权转移至s2,s1失效 // 借用示例 fn calculate_length(s: &String) -> usize { s.len() } // 不获取所有权,仅借用 // 生命周期标注示例 fn longest<'a>(x: &'a str, y: &'a str) -> &'a str { if x.len() > y.len() { x } else { y } } ``` --- 通过此框架可快速定位所有权机制的核心要点,实际应用中需注意**编译器错误提示**(如`borrow checker`报错),逐步掌握所有权系统的设计哲学。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值