Rust所有权详解

news2025/5/12 22:04:54

文章目录

    • Rust所有权
      • 所有权规则
        • 作用域
      • 内存和分配
        • 移动与克隆
        • 栈空间
        • 堆空间
      • 关于函数的所有权机制
        • 作为参数
        • 作为返回值
      • 引用与租借
      • 垂悬引用

Rust所有权

C/C++中我们对于堆内存通常需要自己手动管理,手动申请和释放,即便有了智能指针,对于效率的影响和安全性问题也没有完全解决

Rust为了高效的使用和管理内存,以及对安全性的考量,提出了所有权的概念以及一系列规则

所有权规则

所有权有三条核心规则

  1. Rust中的每个值都有一个隐含的变量,称为所有者
  2. 一个值 同一时刻只能有一个所有者
  3. 当所有者离开作用域时,值会被丢弃(调用drop函数释放资源)
fn main() {
    let s1 = String::from("Hello"); // s1 是 "Hello" 的所有者
    let s2 = s1; // s1 的所有权被转移给 s2,s1 失效

    // println!("{}", s1); // ❌ 编译错误:s1 已经失去所有权
    println!("{}", s2); // ✅ s2 仍然是 "Hello" 的所有者
} // s2 离开作用域,"Hello" 被释放

作用域

这里的作用域和C/C++的作用域基本类似

{
    // 在声明以前,变量 s 无效
    let s = "runoob";
    // 这里是变量 s 的可用范围
}
// 变量范围已经结束,变量 s 无效

内存和分配

Rust是静态语言,这意味着我们无法像C++那样运行时扩容,例如vector会在满时进行扩容

在C++中=是赋值的意思,理解就是将一个变量的值,赋值给一个新的变量,这是一种拷贝语义(Copy)

但是在Rust中,变量和数据交互的方式主要是移动(Move)和克隆(Clone)

移动与克隆

如果你学过C++11,那你一定知道移动语义,例如移动拷贝或者移动赋值

这实际上是一种所有权的转移,主要是避免频繁申请和释放堆空间

但是有一些情况,我们并不希望只是所有权的转移,而是真的创建一个副本进行操作,这就需要使用clone()方法

fn main() {
    let s1 = String::from("hello");
    let s2 = s1.clone(); // 深拷贝
    println!("s1: {}, s2: {}", s1, s2); // ✅ 仍然可以使用 s1
}

栈空间

在栈空间内,Rust变量“移动”的方式其实就是复制,因为栈空间内基本上都是基本数据类型的,通常占用空间和复制时间不会很久,就会是直接复制,这时候两个变量都是可以使用的

    let x = 1;
    let y = x;
    println!("{x}, {y}");

"基本数据"类型有这些:

  • 所有整数类型,例如 i32 、 u32 、 i64 等。
  • 布尔类型 bool,值为 true 或 false 。
  • 所有浮点类型,f32 和 f64。
  • 字符类型 char。
  • 仅包含以上类型数据的元组(Tuples)。
堆空间

String对象的hello存储的位置可就不是栈空间了而是堆空间

例如

let s1 = String::from("hello");
let s2 = s1;

1.png

当执行到第二步时,s1就会把自己对hello字符串对所有权移交给s2,此时s1,就会失效

image.png

因此在移动之后,继续使用s1会报错

关于函数的所有权机制

作为参数

当一个变量作为参数传递给函数时,所有权应该怎么处理

fn main() {
    let s = String::from("hello");
    // s 被声明有效

    takes_ownership(s);
    // s 的值被当作参数传入函数,相当于s的所有权移交给函数了
    // 所以可以当作 s 已经被移动,从这里开始已经无效

    let x = 5;
    // x 被声明有效

    makes_copy(x);
    // x 的值被当作参数传入函数
    // 但 x 是基本类型,依然有效
    // 在这里依然可以使用 x 却不能使用 s

} // 函数结束, x 无效, 然后是 s. 但 s 已被移动, 所以不用被释放


fn takes_ownership(some_string: String) { 
    // 一个 String 参数 some_string 传入,有效
    println!("{}", some_string);
} // 函数结束, 参数 some_string 在这里释放

fn makes_copy(some_integer: i32) { 
    // 一个 i32 参数 some_integer 传入,有效
    println!("{}", some_integer);
} // 函数结束, 参数 some_integer 是基本类型, 无需释放
作为返回值
fn main() {
    let s1 = gives_ownership();
    // gives_ownership 移动它的返回值到 s1

    let s2 = String::from("hello");
    // s2 被声明有效

    let s3 = takes_and_gives_back(s2);
    // s2 被当作参数移动, s3 获得返回值所有权
} // s3 无效被释放, s2 被移动, s1 无效被释放.

fn gives_ownership() -> String {
    let some_string = String::from("hello");
    // some_string 被声明有效

    return some_string;
    // some_string 被当作返回值移动出函数
}

fn takes_and_gives_back(a_string: String) -> String { 
    // a_string 被声明有效

    a_string  // a_string 被当作返回值移出函数
}

引用与租借

这里的引用和C++中的引用是类似的,如果不了解C++的引用,也可以认为是一种指针

例如

fn main() {
    let s1 = String::from("hello");
    let s2 = &s1;
    println!("s1 is {}, s2 is {}", s1, s2);
}

按照原先的理解,s1内部会存一个指向"hello"的指针,s2内部其实也是一个指向"hello"的指针,但是s2是后来的,我们就认为s2是s1的一个引用,也就是别名

  • 引用可以认为是单独的一种类型
  • 引用不会获得值的所有权
  • 引用只能租借(Borrow)值的所有权
  • 当一个值被移动时,原先的引用会失效,必须重新租借所有权
fn main() {
    let s1 = String::from("hello");
    let mut s2 = &s1;
    let s3 = s1;
    s2 = &s3; // 重新从 s3 租借所有权
    println!("{}", s2);
}

一般的租借引用是不允许修改数据内容的,除非原先的数据是mut的,引用时也是mut引用,才允许修改

let mut s1 = String::from("hello");
let s2 = &mut s1;

此时s2允许修改s1的内容

可变引用和不可变引用除了权限不同以外,可变引用不允许多重引用(多个变量引用同一个值),但是不可变引用是允许的

这样做主要是为了避免同时有多个使用者可以进行写操作

垂悬引用

这个其实就是对应着空指针的概念(已经释放资源的指针也算),例如

fn main() {
    let reference_to_nothing = dangle();
}

fn dangle() -> &String {
    let s = String::from("hello");

    &s
}

s是在函数里申请的,函数结束自动释放,但是返回了s的引用,被main函数接收到了,这里就相当于得到了一个空指针

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2332121.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

CExercise_07_1指针和数组_2数组元素的逆序数组逆序(指针版 reverse_by_ptr 和下标版 reverse_arr)

题目: 数组元素的逆序。要求使用[]运算符以及纯粹指针操作两种方式来完成。 关键点 arr[i] arr[len - 1 - i]; arr[0]arr[len-1]; 如果数组序列是偶数,则调换最中间一对为止;若为奇数,则单出一个不用反转. 思想就是长度取一半 eg:8/2, 9/24.5,反转一半,到5时固定…

框架PasteForm实际开发案例,换个口味显示数据,支持echarts,只需要标记几个特性即可在管理端显示(2)

PasteForm框架的主要思想就是对Dto进行标记特性,然后管理端的页面就会以不一样的UI呈现 使用PasteForm框架开发,让你免去开发管理端的烦恼,你只需要专注于业务端和用户端! 在管理端中,如果说表格是基本的显示方式,那么图表chart就是一个锦上添花的体现! 如果一个项目拥…

Starrocks的Bitmap索引和Bloom filter索引以及全局字典

写这个的主要作用是梳理一下Starrocks的索引效率以及使用场景。 Starrocks Bitmap索引 原理: Bitmap 索引是一种使用 bitmap 的特殊数据库索引。bitmap 即为一个 bit 数组,一个 bit 的取值有两种:0 或 1。 每一个 bit 对应数据表中的一行&…

QML面试笔记--UI设计篇05容器控件

1. QML中容器控件全解:构建灵活界面的基石 1.1. Item(万物容器)1.2. Rectangle(视觉容器)1.3. ListView(动态列表容器)1.4. Frame(表单容器)1.5. SwipeView(页…

VSCode运行,各类操作缓慢,如何清理

VSCode写代码,随着项目逐步进展,代码量在增加,依赖的第三方头文件也在增加, 先是发现代码提示的速度变慢, 后来格式化代码速度太慢 然后c/c代码的语法检查有时候压根就失败,来个错误提示 还有source contro…

redis(2)-mysql-锁

1.数据倾斜: 解决:虚拟节点 2.缓存穿透:缓存雪崩、击穿 3.分布式锁 多把锁控制不同节点上的一致性问题。 锁是有失效时间的。 强制回收。 4.redis 和zookeeper的区别 redis 数据支持有效期 4.1 zookeeper 分布式一致性服务框架&am…

OpenLayers:海量图形渲染之矢量切片

最近由于在工作中涉及到了海量图形渲染的问题,因此我开始研究相关的解决方案。在咨询了许多朋友之后发现矢量切片似乎是行业内最常用的一种解决方案,于是我便开始研究它该如何使用。 一、什么是矢量切片 矢量切片按照我的理解就是用栅格切片的方式把矢…

AI智算-K8s+vLLM Ray:DeepSeek-r1 671B 满血版分布式推理部署实践

K8s + vLLM & Ray:DeepSeek-r1 671B 满血版分布式推理部署实践 前言环境准备1. 模型下载2. 软硬件环境介绍正式部署1. 模型切分2. 整体部署架构3. 安装 LeaderWorkerSet4. 通过 LWS 部署DeepSeek-r1模型5. 查看显存使用率6. 服务对外暴露7. 测试调用API7.1 通过 curl7.2 通…

深入浅出SPI通信协议与STM32实战应用(W25Q128驱动)(实战部分)

1. W25Q128简介 W25Q128 是Winbond推出的128M-bit(16MB)SPI接口Flash存储器,支持标准SPI、Dual-SPI和Quad-SPI模式。关键特性: 工作电压:2.7V~3.6V分页结构:256页/块,每块16KB,共1…

前端知识点---闭包(javascript)

文章目录 1.怎么理解闭包?2.闭包的特点3.闭包的作用?4 闭包注意事项&#xff1a;5 形象理解6 闭包的应用 1.怎么理解闭包? 函数里面包着另一个函数&#xff0c;并且内部函数可以访问外部函数的变量。 <script> function box() {//周围状态&#xff08;外部函数中定义的…

Java 泛型的逆变与协变:深入理解类型安全与灵活性

泛型是 Java 中强大的特性之一&#xff0c;它提供了类型安全的集合操作。然而&#xff0c;泛型的类型关系&#xff08;如逆变与协变&#xff09;常常让人感到困惑。 本文将深入探讨 Java 泛型中的逆变与协变&#xff0c;帮助你更好地理解其原理和应用场景。 一、什么是协变与…

多线程(进阶)(内涵面试题)

目录 一、常见的锁策略 1. 悲观锁 vs 乐观锁 2. 重量级锁 vs 轻量级锁 3. 挂起等待锁 vs 自旋锁 4. 普通互斥锁 vs 读写锁 5. 可重入锁 vs 不可重入锁 6. 公平锁 vs 非公平锁 7. 面试题 二、synchronized的原理 1. 基本特点 2. 加锁工作过程 1&#xff09;偏向锁&am…

蓝桥杯补题

方法技巧&#xff1a; 1.进行循环暴力骗分&#xff0c;然后每一层的初始进行判断&#xff0c;如果已经不满足题意了&#xff0c;那么久直接continue&#xff0c;后面的循环就不用浪费时间了。我们可以把题目所给的等式&#xff0c;比如说有四个未知量&#xff0c;那么我们可以用…

【MySQL篇】mysqlpump和mysqldump参数区别总汇

&#x1f4ab;《博主主页》&#xff1a;奈斯DB-CSDN博客 &#x1f525;《擅长领域》&#xff1a;擅长阿里云AnalyticDB for MySQL(分布式数据仓库)、Oracle、MySQL、Linux、prometheus监控&#xff1b;并对SQLserver、NoSQL(MongoDB)有了解 &#x1f496;如果觉得文章对你有所帮…

SQL:DDL(数据定义语言)和DML(数据操作语言)

目录 什么是SQL&#xff1f; 1. DDL&#xff08;Data Definition Language&#xff0c;数据定义语言&#xff09; 2. DML&#xff08;Data Manipulation Language&#xff0c;数据操作语言&#xff09; DDL和DML的区别 什么是SQL&#xff1f; SQL&#xff08;Structured …

神舟平板电脑怎么样?平板电脑能当电脑用吗?

在如今的数码产品市场上&#xff0c;神舟平板电脑会拥有独特的优势&#xff0c;其中比较受到大家关注的就是神舟PCpad为例&#xff0c;无论是设计还是规格也会有很多的亮点&#xff0c;那么是不是可以直接当成电脑一起来使用呢&#xff1f; 这款平板电脑就会配备10.1英寸显示屏…

【力扣hot100题】(075)数据流的中位数

一开始只建立了一个优先队列&#xff0c;每次查询中位数时都要遍历一遍于是喜提时间超限&#xff0c;看了答案才恍然大悟原来还有这么聪明的办法。 方法是建立两个优先队列&#xff0c;一个大根堆一个小根堆&#xff0c;大根堆记录较小的数&#xff0c;小根堆记录较大的数。 …

Java——pdf增加水印

文章目录 前言方式一 itextpdf项目依赖引入编写PDF添加水印工具类测试效果展示 方式二 pdfbox依赖引入编写实现类效果展示 扩展1、将inputstream流信息添加水印并导出zip2、部署出现找不到指定字体文件 资料参考 前言 近期为了知识库文件导出&#xff0c;文件数据安全处理&…

leetcode_19. 删除链表的倒数第 N 个结点_java

19. 删除链表的倒数第 N 个结点https://leetcode.cn/problems/remove-nth-node-from-end-of-list/ 1、题目 给你一个链表&#xff0c;删除链表的倒数第 n 个结点&#xff0c;并且返回链表的头结点。 示例 1&#xff1a; 输入&#xff1a;head [1,2,3,4,5], n 2 输出&#…

41、web前端开发之Vue3保姆教程(五 实战案例)

一、项目简介和需求概述 1、项目目标 1.能够基于Vue3创建项目 2.能够基本Vue3相关的技术栈进行项目开发 3.能够使用Vue的第三方组件进行项目开发 4.能够理解前后端分离的开发模式 2、项目概述 使用Vue3结合ElementPlus,ECharts工具实现后台管理系统页面,包含登录功能,…