Rust 所有权

news2024/11/16 22:32:03

所有权

  • Rust的核心特性就是所有权
  • 所有程序在运行时都必须管理他们使用计算机内存的方式
    • 有些语言有垃圾收集机制,在程序运行时,他们会不断地寻找不再使用的内存
    • 在其他语言中,程序员必须显式的分配和释放内存
  • Rust采用了第三种方式:
    • 内存是通过一个所有权系统来管理的,其中包含一组编译器在编译时检查的规则
    • 在程序运行时,所有权特性不会减慢程序的运行速度。

Stack VS Heap

  • 在像Rust这样的系统级编程语言里,一个值是在还是在heap上对语言的行为和你为什么要做某些决定是有更大的影响的
  • 在你的代码运行的时时候,Stack和Heap都是你可用的内存,但他们的结构很不相同

存储数据

  • Stack按值的接收顺序来存储,按相反的顺序将他们移除(后进先出)
    • 添加数据叫压入栈
    • 移除数据叫弹出栈
  • 所有存储在stack上的数据必须拥有已知的固定的大小
    • 编译时大小位置的数据或运行时大小可能发生变化的数据必须存放在heap上
  • Heap内存组织性差一些
    • 当你把数据放入heap时,你会请求一定数量的空间
    • 操作系统在heap里找到一块足够大的空间,把它标记为在用,并返回一个指针,也就是这个空间的地址
    • 这个过程叫做在heap上进行分配,有时仅仅称为"分配"
  • 把值压到stack上不叫分配
  • 因为指针是已知固定大小的,可以把指针放在stack上
    • 但如果想要实际数据,你必须使用指针来定位
  • 把数据压到stack上比在heap上分配快得多
    • 因为操作系统不需要寻找用来存储新数据的空间,那个位置永远都是在stack的顶端
  • 在heap上分配空间需要做更多的工作
    • 操作系统首先需要找到一个足够大的空间来存放数据,然后要做号记录方便下次分配

访问数据

  • 访问heap中的数据要比访问stack中的数据慢,因为需要通过指针才能找到heap中的数据
    • 对于现代的处理器来说,由于缓存的缘故,如果指针在内存中跳转的次数越少,那么速度就越快
  • 如果数据存放的距离比较近,那么处理器的处理速度就会快一些(stack上)
  • 如果数据之间存放的距离比较远,那么处理速度就会慢一些(heap上)
    • 在heap上分配大量的空间也是需要时间的

函数调用

  • 当你的代码调用函数时,值被传入函数(也包括指向heap的指针)。函数本地的变量被压到stack上。当函数结束后,这些值会从stack上弹出

所有权存在的原因

  • 所有权存在的原因
    • 追踪代码的那些部分正在使用heap的哪些数据
    • 最小化heap上的重复数据量
    • 清理heap上未使用的数据以避免空间不足

所有权规则

  • 每个值都有一个变量,这个变量是该值的所有者
  • 每个值同时只能有一个所有者
  • 当所有者超出作用域(scope)时,该值将被删除

变量作用域

  • Scope就是程序中一个项目的有效范围
fn main(){
    // s不可用
    let s = "hello"; // s可用
    // 可以对s进行相关操作
}// s作用域到此结束,s不再可用

String类型

  • String比那些基础标量数据类型更复杂
  • 字符串字面值:程序里手写的那些字符串值。他们是不可变的
  • Rust还有第二种字符串类型:String。
    • 在heap上分配。能够存储在编译时未知数量的文本

创建String类型的值

  • 可以使用from函数从字符串字面值创建出String类型
fn main(){
    let mut s = String::from("Hello"); // ::表示from是String类型下的函数
    
    s.push_str(",world");
    
    println!("{}",s);
}
  • 这类字符串是可以被修改的
    • 因为他们处理内存的方式不同,

内存和分配

  • 字符串字面值,在编译时就知道他的内容了,其文本内容直接被硬编码到最终的可执行文件里
    • 速度快,高效。是因为其不可变性
  • String类型:为了支持可变性,需要在heap上分配内存来保存编译时未知的文本内容:
    • 操作系统必须在运行时来请求内存
      • 这步通过调用String::from来实现
    • 当用完String后,需要使用某种方式将内存返回给操作系统
      • 这步,在拥有GC的语言中,GC会跟踪并清理不再使用的内存
      • 没有GC就需要我们识别内存何时不再使用,并调用代码将他返回
        • 如果忘了,就会浪费内存
        • 如果提前做了,变量就会非法
        • 如果做了两次,也是Bug。必须一次分配对应一次释放
  • Rust采用了不同的方式:对于某个值来说,当拥有它的变量走出作用范围时,内存会立即自动的交换给操作系统。

变量和数据交互的方式:移动(Move)

  • 多个变量可以与同一个数据使用一种独特的方式来交互
fn main(){
    let x = 5;
    let y = x;
    // 整数是已知且固定大小的简单的值,这两个5被压到了stack中
}

String版本

fn main(){
    let s1 = String::from("hello");
    let s2 = s1;
}
  • 一个String由三部分组成
    • 一个指向存放字符串内容的内存的指针
    • 一个长度
    • 一个容量
  • 上面这些东西放在stack上
  • 存放字符串内容的部分在heap上
  • 长度len,就是存放字符串内容所需的字节数
  • 容量capacity是指String从操作系统总共获得的内存的总字节数

在这里插入图片描述

  • 当把s1赋给s2,String的数据被复制了一份
    • 在stack上复制了一份指针,长度,容量
    • 并没有复制指针所指向的heap上的数据
  • 当变量离开作用域时,Rust会自动调用drop函数,并将变量使用的heap内促释放
  • 当s1,s2离开作用域时,他们都会尝试释放相同的内存
    • 二次释放(double free)bug
  • 为了保证内存安全
    • Rust没有尝试复制被分配的内存
    • Rust让s1失效
      • 当s1离开作用域的时候,Rust不需要释放任何东西
fn main(){
    let s1 = String::from("hello");
    let s2 = s1;
    // 此时我们去使用s1
    println!("{}",s1);
}

会报错

在这里插入图片描述

  • 浅拷贝(shallow copy)
  • 深拷贝(deep copy)
  • 你也许会将复制指针,长度,容量视为浅拷贝,但由于Rust让s1失效了,所以我们用一个新的术语:移动(Move)
  • 隐含的一个设计原则:Rust不会自动创建数据的深拷贝
    • 就运行时性能而言,任何自动赋值的操作都是廉价的

变量和数据交互的方式:克隆(Clone)

  • 如果真想对heap上面的String数据进行深度拷贝,而不仅仅是stack上的数据,可以使用clone方法
fn main(){
    let s1 = String::from("hello");
    let s2 = s1.clone();
    println!!("{},{}",s1,s2);
}

在这里插入图片描述

Stack上的数据:复制

fn main(){
    let x = 5;
    let y = x;
    println!("{},{}",x,y);
}
  • Copy trait,可以用于像整数这样完全存放在stack上面的类型
  • 如果一个类型实现了Copy这个trait,那么旧的变量在赋值后仍然可用
  • 如果一个类型或者该类型的一部分实现了drop trait,那么Rust不允许让他再去实现Copy trait了

一些拥有Copy trait的类型

  • 任何简单标量的组合类型都可以是Copy的
  • 任何需要分配内存或某种资源的都不是Copy的
  • 一些拥有Copy trait的类型:
    • 所有的整数类型,例如u32
    • bool
    • char
    • 所有的浮点类型,例如f64
    • tuple(元组),如果其所有的字段都是Copy的

所有权与函数

  • 在语义上,将值传递给函数和把值赋给变量是类似的:

    • 将值传递给函数将发生移动复制
    fn main(){
        let s = String::from("Hello World");
        take_ownership(s); // s失效,因为函数结束后会调用drop方法
        let x = 5;
        makes_copy(x); // 因为分配在stack中,无事发生
        println!("x:{}",x)
    }
    
    fn take_ownership(some_string:String){
        println!("{}",some_string);
    }
    
    fn makes_copy(some_number:i32){
        println!("{}",some_number);
    }
    

返回值与作用域

  • 函数在返回值的过程中同样也会发生所有权的转移
fn main() {
    let s1 = gives_ownership();
    let s2 = String::from("hello");
    let s3 = takes_and_gives_back(s2); 
}

fn gives_ownership() -> String {
    let some_string = String::from("hello");
    some_string
}

fn takes_and_gives_back(a_string:String) -> String {
    a_string
}
  • 一个变量的所有权总是遵循同样的模式
    • 把一个值赋给其他变量时就会发生移动
    • 当一个包含了heap数据的变量离开作用域时,他的值就会被drop函数清理,除非数据的所有权移动到另一个变量上了

如何让函数使用某个值,但不获得其所有权

fn main(){
    let s1 = String::from("hello");
    
    let (s2,len) = calculate_length(s1);
    
    println!("the length of '{}' is {}",s2,len);
}

fn calculate_length(s:String) -> (String,usize){
    let length = s.len();
    
    (s,length)
}
  • Rust有一个特性叫做"引用(Reference)"

引用和借用

fn main(){
    let s1 = String::from("hello");
    
    let len = calculate_length(s1);
    
    println!("the length of '{}' is {}",s1,len);
}

fn calculate_length(s:&String) ->usize {
    s.len();
}
  • 参数的类型是&String而不是String
  • &符号就表示引用:允许你引用某些值而不取得其所有权
  • 我们把引用作为函数参数的这个行为叫做借用
  • 是否可以修改借用的东西?
    • 不行
  • 和变量一样,引用默认也是不可变的

可变引用

fn main(){
    let mut s1 = String::from("Hello");
    let len = calculate_length(&mut s1);
        println!("the length of '{}' is {}",s1,len);
}

fn calculate_length(s:&mut String) -> usize {
    s.push_str(",world");
    s.len()
}
  • 可变引用有一个重要的限制:在特定作用域内,对某一块数据,只能有一个可变的引用
    • 这样做的好处是可在编译时防止数据竞争
  • 以下三种行为下会发生数据竞争:
    • 两个或多个指针同时访问同一个数据
    • 至少有一个指针用于写入数据
    • 没有使用任何机制来同步对数据的访问
  • 我们可以通过创建新的作用域,来允许非同时的创建多个可变引用
fn main(){
    let mut s = String::from("Hello");
    {
        let s1 = &mut s;
    }
    let s2 = &mut s;
}
  • 不可以同时拥有一个可变引用和不可变引用
  • 多个不可变的引用是可以的

悬空引用(Dangling References)

  • 悬空指针(Dangling Pointer):一个指针引用了内存中的某个地址,而这块内存可能已经释放并分配给其它人使用了
  • 在Rust里,编译器可保证引用永远都不是悬空引用。
    • 如果你引用了某些数据,编译器将保证在引用离开作用域之前数据都不会离开作用域

引用的规则

  • 在任何给定的时刻,只能满足下列条件之一
    • 一个可变的引用
    • 任意数量不可变的引用
  • 引用必须一直有效

切片

  • Rust的另外一种不持有所有权的数据类型:切片(Slice)
  • 字符串切片是指向字符串中一部分内容的引用
  • 形式:[开始索引…结束索引] (左闭右开)

注意

  • 字符串切片的索引范围必须发生在有效的UTF-8字符边界内
  • 如果尝试从一个多字节的字符中创建字符串切片,程序会报错并退出

将字符串切片作为参数传递

  • 采用&str作为参数类型,这样就可以同时接受String和&str类型的参数了
  • 定义函数时使用字符串切片来代替字符串引用会使我们的API更加通用,且不会损失任何功能。

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

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

相关文章

FFmpeg内存对齐简述

目录 引文 行字节数的计算 ffmpeg中的align ffmpeg中的linesize 内容参考 引文 在ffmpeg的使用过程中有时会发现align这个参数,那么这个参数代表什么意思,不同的值会产生什么影响呢,详见下文。 行字节数的计算 理解内存对齐之前首先要…

无人机之导航系统篇

一、导航系统组成 包括惯性导航系统、卫星导航系统、视觉导航系统等。 二、导航原理 利用传感器感知无人机的位置、速度和姿态信息,结合地图数据和导航算法,计算出无人机当前的位置和航向,从而引导无人机按照预设的航线飞行。 三、导航精…

Linux文件或图片名称中文乱码解决【适用于centos、ubuntu等系统】

👨‍🎓博主简介 🏅CSDN博客专家   🏅云计算领域优质创作者   🏅华为云开发者社区专家博主   🏅阿里云开发者社区专家博主 💊交流社区:运维交流社区 欢迎大家的加入&#xff01…

【unittest】TestSuite搭建测试用例示例二

1.1 打开串口示例 常用的模组则包含AT指令测试,或串口数据测试,则可添加串口配置,将指令通过串口发送出去,如下所示: import serial def open_serial_port(port, baudrate115200, timeout2): try: # 创建并配置串…

Vue 3+Vite+Eectron从入门到实战系列之一环境安装篇

Electron 都应该不会陌生了,是一个使用 JavaScript、HTML 和 CSS 构建桌面应用的框架。通过将 Chromium 和 Node.js 嵌入到其二进制文件中,Electron 允许你维护一个 JavaScript 代码库并创建可在 Windows、macOS 和 Linux 上运行的跨平台应用 - 无需原生开发经验。 实现效果…

YOLOv6训练自己的数据集

文章目录 前言一、YOLOv6简介二、环境搭建三、构建数据集四、修改配置文件①数据集文件配置②权重下载③模型文件配置 五、模型训练和测试模型训练模型测试 总结 前言 提示:本文是YOLOv6训练自己数据集的记录教程,需要大家在本地已配置好CUDA,cuDNN等环…

思源笔记结合群晖WebDav与cpolar内网穿透实现跨网络笔记云同步

文章目录 前言1. 开启群晖WebDav 服务2. 本地局域网IP同步测试3. 群晖安装Cpolar4. 配置远程同步地址5. 笔记远程同步测试6. 固定公网地址7. 配置固定远程同步地址 前言 本教程主要分享如何将思源笔记、cpolar内网穿透和群晖WebDav三者相结合,实现思源笔记的云同步…

如何使用代理IP进行电子邮件保护?

🎬 鸽芷咕:个人主页 🔥 个人专栏: 《C干货基地》《粉丝福利》 ⛺️生活的理想,就是为了理想的生活! 前言 随着企业信息化的深入发展,电子邮件在私人生活和商业运营中起到越来越重要的作用,随之而来电子邮件…

掌握eBay刊登:十大工具助力卖家脱颖而出

在经济全球化的浪潮中,eBay作为全球最大的跨境电商平台之一,为卖家提供了一个展示商品、拓展市场的广阔舞台。然而,平台越大,意味着商家之间的竞争越激烈。如何在eBay上有效刊登商品,是卖家吸引用户的关键步骤。本文将…

500元蓝牙耳机排行榜有哪些?四款百元蓝牙耳机品牌排行推荐

在如今这个充满科技魅力的时代,蓝牙耳机已成为我们日常生活中不可或缺的一部分,无论是沉浸在音乐的世界中,还是在繁忙的通勤路上享受片刻宁静,一副优秀的蓝牙耳机都能为我们带来无与伦比的听觉享受,面对市场上琳琅满目…

合作文章(IF=5.9)|16s和非靶代谢组分析揭示亚麻籽木脂素对PAM过量诱导的肝毒性的保护作用

研究背景 扑热息痛(PAM)是世界上最常用的镇痛解热的药物之一。在肝酶细胞色素P450 Cyp2E1和Cyp1A2PAM酶的作用下,PAM转化为一种高活性的代谢物乙酰对位苯醌亚胺(NAPQI),通过与谷胱甘肽(GSH)偶联可解毒为无毒的谷胱甘肽-NAPQI。然…

视频汇聚平台EasyCVR接入移动执法记录仪,视频无法播放且报错500是什么原因?

GB28181国标视频汇聚平台EasyCVR视频管理系统以其强大的拓展性、灵活的部署方式、高性能的视频能力和智能化的分析能力,为各行各业的视频监控需求提供了优秀的解决方案。视频智能分析平台EasyCVR支持多协议接入,兼容多类型的设备,包括IPC、NV…

自动化测试中元素定位失败的解决策略

🍅 点击文末小卡片,免费获取软件测试全套资料,资料在手,涨薪更快 一、引言 自动化测试是软件开发流程中的重要组成部分,它能够帮助测试人员快速地验证应用程序的功能是否符合预期。然而,在自动化测试的过程…

互联网解决方案-文件存储方案:seafile真实案例

目录 seafile可靠性保证 事件驱动 seafile.log events.log 事件驱动好处 本地联思文件同步云联思真实案例 本地联思文件同步云联思架构 云联思客户端检查文件API 本地联思访问客户端封装 本地联思队列消费检查 实践过程中的弯路 文件目录处理 move = copy & de…

ANTD PRO VUE使用

目录 1.访问Antd Pro Vue官网 2.安装 3.目录结构 4.安装运行 5.npm run serve可能会报以下错误 6.解决办法 ​7.缩放会报以下错误 ​8.解决办法 1.访问Antd Pro Vue官网 https://pro.antdv.com 点击开始使用 2.安装 从 GitHub 仓库中直接安装最新的脚手架代码。 git…

TF卡(SD NAND)参考设计和使用提示

目录 电路设计 Layout 设计说明 贴片注意事项 电路设计 1、参考电路: R1~R5 (10K-100 kΩ)是上拉电阻,当SD NAND处于高阻抗模式时,保护CMD和DAT线免受总线浮动。 即使主机使用SD NAND SD模式下的1位模式,主机也应通过上拉电…

《Attention Is All You Need》总结

🧙‍♂️ 诸位好,吾乃斜杠君,编程界之翘楚,代码之大师。算法如流水,逻辑如棋局。 📜 吾之笔记,内含诸般技术之秘诀。吾欲以此笔记,传授编程之道,助汝解技术难题。 &#…

H5 使用微信地址

效果图 1.先引入sdk 文件 $.ajax({type: "get",url: baseUrl "api/wechat/Pubaccount/jssdk?url" linkurl,//请求接口,获取授权success:function(data){let res data;jWeixin.config({debug: false,appId: res.appId,timestamp: res.time…

【批处理】一、批量修改文件的后缀名

一、简介 批处理(Batch)是常被应用于windows系统下的脚本语言; 此文章主要讲解如何利用批处理批量修改指定后后缀名的文件,具体过程如下所示; 二、批处理代码实现 REM 关闭请求回显功能 echo offREM 延迟环境变量拓展 setlocal enabledela…

Transformers实战04-微调gpt-2生成python代码。

文章目录 简介案例描述收集数据数据集处理回顾预处理input\_ids和attention_mask:special tokenchunkdatacollatormap 预处理 初始化模型回顾模型参数计算 初始化 完整代码测试 简介 GPT-2(Generative Pre-trained Transformer 2)是由OpenAI…