Rust编程的作用域与所有权

news2024/9/20 13:08:32

【图书介绍】《Rust编程与项目实战》-CSDN博客

《Rust编程与项目实战》(朱文伟,李建英)【摘要 书评 试读】- 京东图书 (jd.com)

Rust编程与项目实战_夏天又到了的博客-CSDN博客

3.8  作  用  域

Rust的所有权系统和作用域息息相关,因此有必要先理解Rust的作用域规则。在Rust中,任何一个可用来包含代码的大括号都是一个单独的作用域。类似于Struct{}这样用来定义数据类型的大括号,不在讨论范围之内,本章后面所说的大括号也都不考虑这种大括号。以下几种结构中的大括号都有自己的作用域:

(1)if、while等流程控制语句中的大括号。

(2)match模式匹配的大括号。

(3)单独的大括号。

(4)函数定义的大括号。

(5)mod定义模块的大括号。

例如,可以单独使用一个大括号来开启一个作用域:

{                                         // s在这一行无效,因为它尚未声明

  let s = "hello";                     // 从这行起,s是有效的

  println!("{}", s);                   // 使用s

  println!("hello,world");            // 这行没有用到s,但s依然是有效的

}                                         // 到了这行,此作用域已结束,s不再有效

上面的代码中,变量s绑定了字符串字面值,在跳出作用域后,变量s失效,变量s所绑定的值会自动被销毁。

实际上,变量跳出作用域失效时,会自动调用Drop trait的drop函数来销毁该变量绑定在内存中的数据,这里特指销毁堆和栈上的数据,而字符串字面量是存放在全局内存中的,它会在程序启动到程序终止期间一直存在,不会被销毁。可通过如下代码验证:

fn main(){

    {

      let s = "hello";

      println!("{:p}", s);  // 0x7ff6ce0cd3f8

    }



    let s = "hello";

    println!("{:p}", s);  // 0x7ff6ce0cd3f8

}

因此,上面的示例中只是让变量s失效了,仅此而已,并没有销毁s所绑定的字符串字面量。但一般情况下不考虑这些细节,而是照常描述为跳出作用域时,会自动销毁变量所绑定的值。

任意大括号之间都可以嵌套。例如,可以在函数定义的内部再定义函数,在函数内部使用单独的大括号,在函数内部使用mod定义模块,等等。示例如下:

fn main(){

  fn ff(){

    println!("hello world");

  }

  ff();



  let mut a = 33;

  {

    a += 1;

  }

  println!("{}", a);  // 结果输出:34

}

虽然任何一种大括号都有自己的作用域,但函数作用域比较特别。在函数作用域内无法访问函数外部的变量,而其他大括号的作用域可以访问大括号外部的变量。比如:

fn main() {

  let x = 32;

  fn f(){

    // 编译错误,不能访问函数外面的变量x和y

    // println!("{}, {}", x, y); 

  }

  let y = 33;

  f();



  let mut a = 33;

  {

    // 可以访问大括号外面的变量a

    a += 1;

  }

  println!("{}", a);  //结果输出:34

}

在Rust中,能否访问外部变量称为捕获环境。比如,函数是不能捕获环境的,而大括号可以捕获环境。对于可捕获环境的大括号作用域,要注意Rust的变量遮盖行为。分析下面的代码:

fn main(){

  let mut a = 33;

  {

    a += 1;           // 访问并修改外部变量a的值



    // 又声明变量a,这会发生变量遮盖现象

    // 从此开始,大括号内访问的变量a都是该变量

    let mut a = 44;

    a += 2;

    println!("{}", a);             // 输出46

  }                                 // 大括号内声明的变量a失效

  println!("{}", a);               // 输出34

}

这种行为和其他语言不太一样,因此这种行为需要引起注意。

3.9  所  有  权

3.9.1  让我们回忆栈和堆

在学习C/C++时,老师经常出某变量被分配在栈上还是堆上的题目,几乎每次都有很大一批同学在这种题目上失手。栈和堆都是代码在运行时可供使用的内存,但是它们的结构不同。栈是一种后进先出(Last In First Out,LIFO)的数据结构,栈中的所有数据都必须占用已知且固定大小的内存。堆就好理解了,它是一个没有组织的结构,想怎么使用就怎么使用,只要堆够大,就可以申请一段内存空间,然后把这段内存标记为已使用,并得到指向这段内存开头的指针;当不再使用时,再将这段内存标记为未使用。当声明一个指针但并没有分配空间时,这个指针是空指针;当内存已经标记为未使用,而指针依然指向这段空间时,这个指针就是野指针。

C++中的堆和栈定义如下。

  • 堆:由程序员手动分配和释放,完全不同于数据结构中的堆,分配方式类似于链表。由malloc或者new来分配,由free和delete来释放。若程序员不释放,则程序结束时由系统释放。
  • 栈:由编译器自动分配和释放,存放函数的参数值、局部变量的值等。栈里面变量的内存必须是已知且固定大小的。函数调用时,参数、本地变量、指向堆的指针都压入一个栈,函数完成时退出。操作方式类似于数据结构中的栈(C和Python中也有,只要基于C的都有这个概念)。

其实分辨起来很容易,动态分配的变量就是在堆上,其他的都在栈上。

栈是一个成熟的结构,基本不会引发内存的问题,而没有组织的堆却很容易引发内存问题。垃圾回收和所有权都是为了解决堆的内存管理问题。

这就是C++相比于垃圾回收机制语言的优势,灵活高效,但是也会带来内存安全问题。

3.9.2  什么是所有权

计算机程序必须在运行时管理它们所使用的内存资源。大多数编程语言都有管理内存的功能:C/C++这样的语言主要通过手动方式管理内存,开发者需要手动申请和释放内存资源。但为了提高开发效率,只要不影响程序功能的实现,许多开发者没有及时释放内存的习惯。所以手动管理内存的方式常常造成资源浪费。

Java语言编写的程序在Java虚拟机(Java Virtual Machine,JVM)中运行,JVM具备自动回收内存资源的功能。但这种方式常常会降低运行时效率,所以JVM会尽可能少地回收资源,这样也会使程序占用较大的内存资源。

所有权对大多数开发者而言是一个新颖的概念,它是Rust语言为高效使用内存而设计的语法机制。所有权概念是为了让Rust在编译阶段更有效地分析内存资源的有用性以实现内存管理而诞生的概念。

Rust的所有权是一个跨时代的理念,是内存管理的第二次革命。较低级的语言依赖程序员分配和释放内存,一不小心就会出现空指针、野指针破坏内存;较高级的语言使用垃圾回收机制管理内存,在程序运行时不断地寻找不再使用的内存,虽然安全,却加重了程序的负担。Rust的所有权理念横空出世,通过所有权系统管理内存,编译器在编译时会根据一系列的规则进行检查,在运行时,所有权系统的任何功能都不会减慢程序,把安全的内存管理推向了零开销的新时代。

所有权概念是Rust语言的一个重要特性,因为通过它才使得Rust的“安全”“高并发”得以发挥出优势。因为它让Rust无须垃圾回收,即可保障内存安全。对于C/C++程序员来说,可能一直在跟内存安全打交道,如内存泄露、智能指针等。对于别的语言来说,会有垃圾回收机制。例如Python的垃圾回收机制,有“标记清除”“分代回收”等方式。这两种方式各有优缺点。Rust则是通过所有权和借用来保证内存安全的。很多人不理解为什么Rust是内存安全的,其实就是在默认情况下,是写不出内存不安全的代码的。

Rust的所有权并不难理解,它有且只有如下三条规则:

(1)Rust中的每个值都有一个被称为其所有者的变量(即值的所有者是某个变量)。

(2)值在任一时刻有且只有一个所有者。

(3)当所有者(变量)离开作用域时,这个值将被销毁。

这里对第三点做一些补充性的解释,所有者离开作用域会导致值被销毁,这个过程实际上是调用一个名为drop的函数来销毁数据释放内存的。在前面解释作用域规则时曾提到过,销毁的数据特指堆栈中的数据,如果变量绑定的值是全局内存区内的数据,则数据不会被销毁。例如:

fn main(){

  {

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

  } // 跳出作用域,栈中的变量s将被销毁,其指向的堆中的数据也被销毁

    // 但全局内存区的字符串字面量仍被保留

}

Rust中的每个值都有一个所有者,但这个说法比较容易产生误会。例如:

#![allow(unused)]

fn main() {

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

}

很多人可能会误以为变量s是堆中字符串数据hello的所有者,但实际上不是。String字符串的实际数据在堆中,但是String大小不确定,所以在栈中使用一个胖指针结构来表示这个String类型的数据,这个胖指针中的指针指向堆中的String的实际数据。也就是说,变量s的值是那个胖指针,而不是堆中的实际数据。因此,变量s是那个胖指针的所有者,而不是堆中实际数据的所有者。但是,由于胖指针是指向堆中数据的,很多时候为了简化理解,简化描述方式,经常会说s是哪个堆中实际数据的所有者。但无论如何描述,都需要理解所有者和值之间的真相。

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

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

相关文章

C++和OpenGL实现3D游戏编程【连载9】——纹理的镂空显示

1、本节实现的内容 前面的课程中,我们学会了加载纹理并显示纹理图案,但是纹理的图案都是长方形的图片,图片就会有白色或黑色背景,那么在游戏设计过程中,我们经常不需要显示图片的背景部分,那么这节课我们就来讨论一下如何实现剔除白色或黑色背景后的镂空图像,下图就是将…

算法打卡:第十一章 图论part01

今日收获:图论理论基础,深搜理论基础,所有可达路径,广搜理论基础(理论来自代码随想录) 1. 图论理论基础 (1)邻接矩阵 邻接矩阵存储图,x和y轴的坐标表示节点的个数 优点…

PFC理论基础与Matlab仿真模型学习笔记(1)--PFC电路概述

一、整流器滤波电路简介 整流器滤波电路的主要功能是将交流电(AC)转换为直流电(DC),并通过滤波器减少波动以输出稳定的直流电。其工作原理主要分为两个部分: 1.整流部分 整流器的核心器件是二极管&#…

Spring Boot从0到1 -day02

目录 学习目标Spring Boot 的基本配置启动类与核心注解SpringBootApplicationSpring Boot 的全局配置文件1. application.properties2. application.ymlSpring 中Spring Boot Application注解的作用 自动配置原理1. 自动配置类2. 自动配置的发现示例3. 自定义自动配置 条件注解…

手把手教你用Ollama AnythingLLM搭建AI知识库,无需编程,跟着做就行!

在本地电脑上跑大语言模型(LLM),已经不是什么高科技操作了。随着技术的迭代,现在利用Ollam和AnythingLLM就可以轻松构建自己的本地知识库,人人皆可上手,有手就行。过往要达成这一目标,可是需要有…

令人拍案叫绝的Python条件控制技巧

目录 1. 条件控制的重要性 2. 理解条件语句的基础 3. 使用 elif 增加更多选择 4. 利用 in 和 not in 进行集合匹配 5. 利用 and 与 or 连接条件 6. 高级技巧:列表推导式中的条件表达式 7. 实战案例:自动评分系统 8. 总结 文末福利 1. 条件控制的…

计算机网络34——Windows内存管理

1、计算机体系结构 2、内存管理 分为连续分配管理和非连续分配管理 在块内存在的未使用空间叫内部碎片,在块外存在的未使用空间叫外部碎片 固定分区分配可能出现内部碎片,动态分区分配可能出现外部碎片 3、逻辑地址和实际地址的互相转换 4、缺页中断 …

渗透测试常用工具(非常详细)从零基础入门到精通,看完这一篇就够了。

对于白帽子来说,在进行渗透测试、代码审计、逆向工程等一系列工作中,都离不开安全工具的支撑,这些工具像一把把利剑,可以大大提高渗透效率。 在本篇中,我总结了超多网络安全工具,涉及暴力破解、渗透字典、…

Linux入门学习:make/Makefile(Linux项目自动化构建工具)

文章目录 1. makefile文件语法2. make clean工程清理3. 细节语法4. make原理 ⭕背景: 会不会写makefile,从一个侧面说明了一个人是否具备完成大型工程的能力。一个工程中的源文件不计数,其按类型、功能、模块分别放在若干个目录中&#xff0c…

Electron 安装包 asar 解压定位问题实战

背景 在开发 Electron 过程中,我们想知道 Electron 打包的最终形态是什么样的,以便我们能更好的理解 Electron 打包的过程,以及逆向来快速追踪一些问题,例如下面这个报错,以前这类报错都是靠猜,现在则可以…

算法设计与分析(最长公共子序列

目录 最长公共子序列问题描述代码实现输出结果注意事项 小结: 最长公共子序列 最长公共子序列(Longest Common Subsequence, LCS)问题是计算给定两个序列的最长子序列的长度,这个子序列不要求连续,但需要保持相同的相…

如何在SpringCloud中使用Consul进行服务发现与配置管理

Spring Cloud是一个用于构建分布式系统的开发工具包。它提供了一系列解决方案,用于在分布式系统中管理和协调服务发现、配置管理、负载均衡、容错机制等功能。Consul是一种用于服务发现、配置管理和分布式一致性的工具,与Spring Cloud可以很好地集成在一…

程序员常用开发软件集合

文本编辑器 Sublime Text 编程工具 Visual Studio Code IntelliJ IDEA 数据连接客户端 Navicat DBeaver 远程连接客户端 WinSCP xshell WindTerm 流程图工具 draw.io 远程连接电脑工具 ToDesk 向日葵 teamviewer

在数据开发、消费中,如何科学治理重复数据难题?

解决这个问题,还得从技术系统架构和数据开发、消费的流程管理上来找原因: 一、数据集成或同步过程中有一些技术挑战,如多源异构数据集成时缺失去重策略、数据同步机制的不完善或配置错误,导致重复数据被多次引入系统;…

stable diffusion 神经网络插件 controlnet 的安装,很详细

stable diffusion 神经网络插件 controlnet 的安装,很详细 一、前言二、下载1、方式一2、方式二 一、前言 学到 stable diffusion 的 controlnet 插件,安装也略微曲折,这里做个记录。 下载前保证 github 能正常访问。 二、下载 1、方式一…

Mybatis续

步骤 爆红 点了右上角还是爆红不要着急,右下角正在下载 new 如果new的是package,用com.zhang,能事项分级 如果new的是文件夹,用com/zhang,就能实现分级。如果用com.zhang,则创建的文件夹名是com.zhang …

开源笔记Joplin本地Docker部署结合内网穿透实现多设备端同步笔记

文章目录 前言1. 安装Docker2. 自建Joplin服务器3. 搭建Joplin Sever4. 安装cpolar内网穿透5. 创建远程连接的固定公网地址 前言 本文主要介绍如何在自己的服务器上利用docker搭建 Joplin Server,并对同步进行配置,再结合cpolar内网穿透工具实现公网远程…

神经网络拟合离散标签值

神经网络拟合离散标签值 1. 数据预处理1.1 添加参数解析1.2 数据预处理逻辑1.3 标签处理逻辑1.4 构建特征和标签1.5 数据归一化、转torch1.6 实现Dataset类 2. 定义model3. 定义train脚本3.1 loss和optimizer3.2 train3.3 predict 1. 数据预处理 1.1 添加参数解析 为了方便管…

第二证券:金价涨了!创一历史之最!

当地时间周四,金融商场进一步消化美联储大幅降息50个基点的利率抉择,认为这是为了完结美国经济“软着陆”的一次防备式降息,而非紧急应对阑珊风险的降息,加之当天公布的上星期初度申请赋闲救助人数低于预期,投资者对美…

B站前端错误监控实践

前言 从23年开始,我们团队开始前端错误监控方向的开发。经历了一些列的迭代和发展,从监控SDK、上报、数据治理、看板集成、APM自研可视化初步完成了一条完整且适合B站前端监控。 截止目前(2024.08.01),前端监控在B站85%以上的业务线&#xf…