Rust核心功能之一(所有权)
目录
1、什么是所有权?
1.1 所有权规则
1.2 变量作用域
1.3 String 类型
1.4 内存与分配
变量与数据交互的方式(一):移动
变量与数据交互的方式(二):克隆
只在栈上的数据:拷贝
1.5 所有权与函数
1.6 返回值与作用域
1、什么是所有权?
所有权(系统)是 Rust 最为与众不同的特性,对语言的其他部分有着深刻含义。它让 Rust 无需垃圾回收(garbage collector)即可保障内存安全,因此理解 Rust 中所有权如何工作是十分重要的。
所有程序都必须管理其运行时使用计算机内存的方式。一些语言中具有垃圾回收机制,在程序运行时有规律地寻找不再使用的内存;在另一些语言中,程序员必须亲自分配和释放内存。Rust 则选择了第三种方式:通过所有权系统管理内存,编译器在编译时会根据一系列的规则进行检查。如果违反了任何这些规则,程序都不能编译。在运行时,所有权系统的任何功能都不会减慢程序。
因为所有权对很多程序员来说都是一个新概念,需要一些时间来适应。好消息是随着你对 Rust 和所有权系统的规则越来越有经验,你就越能自然地编写出安全和高效的代码。
1.1 所有权规则
首先,让我们看一下所有权的规则。当我们通过举例说明时,请谨记这些规则:
- Rust 中的每一个值都有一个 所有者(owner)。
- 值在任一时刻有且只有一个所有者。
- 当所有者(变量)离开作用域,这个值将被丢弃。
1.2 变量作用域
在所有权的第一个例子中,我们看看一些变量的 作用域(scope)。作用域是一个项(item)在程序中有效的范围。假设有这样一个变量:
let a = 12345
变量 a绑定到了一个字符串字面值,这个字符串值是硬编码进程序代码中的。这个变量从声明的点开始直到当前 作用域 结束时都是有效的。
fn main() { // 作用域开始处let a = 12345; // 此时,a才开始分配存储空间
} // 作用域开始处
其实变量是否有效与作用域的关系跟其他编程语言是类似的。
1.3 String 类型
我们已经见过字符串字面值,即被硬编码进程序里的字符串值。字符串字面值是很方便的,不过它们并不适合使用文本的每一种场景。原因之一就是它们是不可变的。另一个原因是并非所有字符串的值都能在编写代码时就知道:例如,要是想获取用户输入并存储该怎么办呢?为此,Rust 有第二个字符串类型,String
。这个类型管理被分配到堆上的数据,所以能够存储在编译时未知大小的文本。可以使用 from
函数基于字符串字面值来创建 String
,如下:
fn main() { let s = String::from("hello");println!("{s}")
}
这两个冒号 ::
是运算符,允许将特定的 from
函数置于 String
类型的命名空间(namespace)下,而不需要使用类似 string_from
这样的名字。
可以修改此类字符串可变:
fn main() { let mut s = String::from("hello ");s.push('w');s.push_str("orld");println!("{s}")
}
那么这里有什么区别呢?为什么 String
可变而字面值却不行呢?区别在于两个类型对内存的处理上。
1.4 内存与分配
就字符串字面值来说,我们在编译时就知道其内容,所以文本被直接硬编码进最终的可执行文件中。这使得字符串字面值快速且高效。不过这些特性都只得益于字符串字面值的不可变性。不幸的是,我们不能为了每一个在编译时大小未知的文本而将一块内存放入二进制文件中,并且它的大小还可能随着程序运行而改变。
对于 String
类型,为了支持一个可变,可增长的文本片段,需要在堆上分配一块在编译时未知大小的内存来存放内容。这意味着:
- 必须在运行时向内存分配器(memory allocator)请求内存。
- 需要一个当我们处理完
String
时将内存返回给分配器的方法。
第一部分由我们完成:当调用 String::from
时,它的实现 (implementation) 请求其所需的内存。这在编程语言中是非常通用的。
然而,第二部分实现起来就各有区别了。在有 垃圾回收(garbage collector,GC)的语言中,GC 记录并清除不再使用的内存,而我们并不需要关心它。在大部分没有 GC 的语言中,识别出不再使用的内存并调用代码显式释放就是我们的责任了,跟请求内存的时候一样。从历史的角度上说正确处理内存回收曾经是一个困难的编程问题。如果忘记回收了会浪费内存。如果过早回收了,将会出现无效变量。如果重复回收,这也是个 bug。我们需要精确的为一个 allocate
配对一个 free
。
Rust 采取了一个不同的策略:内存在拥有它的变量离开作用域后就被自动释放。
这是一个将 String
需要的内存返回给分配器的很自然的位置:当变量离开作用域,Rust 为我们调用一个特殊的函数。这个函数叫做 drop,在这里 String
的作者可以放置释放内存的代码。Rust 在结尾的 }
处自动调用 drop
。
注意:在 C++ 中,这种 item 在生命周期结束时释放资源的模式有时被称作 资源获取即初始化(Resource Acquisition Is Initialization (RAII))。如果你使用过 RAII 模式的话应该对 Rust 的
drop
函数并不陌生。
变量与数据交互的方式(一):移动
fn main() {let x = 5;let y = x;
}
定义的2个简单变量,会直接存储在栈中,因为它们的长度都是固定的。
fn main() {let s1 = String::from("hello");let s2 = s1;
}
当s1、s2存储的时候,会在堆中开辟一片区域用来存储,s1和s2 则通过指针的方式,来指向堆中存储值得位置,这样可以避免在堆中多次开辟存储空间,如果你在其他语言中听说过术语 浅拷贝(shallow copy)和 深拷贝(deep copy),那么拷贝指针、长度和容量而不拷贝数据可能听起来像浅拷贝。不过因为 Rust 同时使第一个变量无效了,这个操作被称为 移动(move),而不是叫做浅拷贝。上面的例子可以解读为 s1
被 移动 到了 s2
中。如果打印s1会出现以下错误:
变量与数据交互的方式(二):克隆
如果我们 确实 需要深度复制 String
中堆上的数据,而不仅仅是栈上的数据,可以使用一个叫做 clone
的通用函数。
以下是一个示例:
fn main() { let s1 = String::from("hello ");let s2 = s1.clone();println!("{s1} {s2}")
}
打印以下看下结果:
只在栈上的数据:拷贝
fn main() {let x = 5;let y = x;println!("x = {}, y = {}", x, y);
}
Rust 有一个叫做 Copy
trait 的特殊注解,可以用在类似整型这样的存储在栈上的类型上。如果一个类型实现了 Copy
trait,那么一个旧的变量在将其赋值给其他变量后仍然可用。
那么哪些类型实现了Copy Trait呢,有以下类型:
- 所有整数类型,比如
u32
。 - 布尔类型,
bool
,它的值是true
和false
。 - 所有浮点数类型,比如
f64
。 - 字符类型,
char
。 - 元组,当且仅当其包含的类型也都实现
Copy
的时候。比如,(i32, i32)
实现了Copy
,但(i32, String)
就没有。
1.5 所有权与函数
将值传递给函数与给变量赋值的原理相似。向函数传递值可能会移动或者复制,就像赋值语句一样。
一个示例:
fn main() {let s = String::from("hello"); // s 进入作用域takes_ownership(s); // s 的值移动到函数里 ...// ... 所以到这里不再有效let x = 5; // x 进入作用域makes_copy(x); // x 应该移动函数里,// 但 i32 是 Copy 的,// 所以在后面可继续使用 x} // 这里,x 先移出了作用域,然后是 s。但因为 s 的值已被移走,// 没有特殊之处fn takes_ownership(some_string: String) { // some_string 进入作用域println!("{}", some_string);
} // 这里,some_string 移出作用域并调用 `drop` 方法。// 占用的内存被释放fn makes_copy(some_integer: i32) { // some_integer 进入作用域println!("{}", some_integer);
} // 这里,some_integer 移出作用域。没有特殊之处
可以发现在值传递给函数的时候,堆里的数据会移除当前作用域,例如,s传递takes_ownership方法里,在当前作用域s的值被释放,栈里的值,传递给函数,它对应的操作是复制,例如,x=5传递给函数之后,在当前作用域还是可以访问的。
1.6 返回值与作用域
返回值也可以转移所有权。以下是一个示例:
fn main() {let s1 = gives_ownership(); // gives_ownership 将返回值// 转移给 s1let s2 = String::from("hello"); // s2 进入作用域let s3 = takes_and_gives_back(s2); // s2 被移动到// takes_and_gives_back 中,// 它也将返回值移给 s3
} // 这里,s3 移出作用域并被丢弃。s2 也移出作用域,但已被移走,// 所以什么也不会发生。s1 离开作用域并被丢弃fn gives_ownership() -> String { // gives_ownership 会将// 返回值移动给// 调用它的函数let some_string = String::from("yours"); // some_string 进入作用域。some_string // 返回 some_string // 并移出给调用的函数//
}// takes_and_gives_back 将传入字符串并返回该值
fn takes_and_gives_back(a_string: String) -> String { // a_string 进入作用域// a_string // 返回 a_string 并移出给调用的函数
}
这里跟上面移动规则一样,将值赋给另一个变量时移动它。当持有堆中数据值的变量离开作用域时,其值将通过
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(); // len() 返回字符串的长度(s, length)
}
但是这未免有些形式主义,而且这种场景应该很常见。幸运的是,Rust 对此提供了一个不用获取所有权就可以使用值的功能,叫做 引用(references),后面我们再来看看引用是什么?
相关文章:

Rust核心功能之一(所有权)
目录 1、什么是所有权? 1.1 所有权规则 1.2 变量作用域 1.3 String 类型 1.4 内存与分配 变量与数据交互的方式(一):移动 变量与数据交互的方式(二):克隆 只在栈上的数据:拷贝…...
跨域(CORS)和JWT 详解
跨域 (CORS) 概念 同源策略 (Same-Origin Policy) 同源策略是一项浏览器安全特性,它限制了一个网页中的脚本如何与另一个来源(域名、协议、端口)的资源进行交互。这对于防止跨站点请求伪造和数据泄露非常重要。 为什么需要跨域? 跨域问题通…...

前端框架Vue学习 ——(二)Vue常用指令
文章目录 常用指令 常用指令 指令: HTML 标签上带有 “v-” 前缀的特殊属性,不同指令具有不同含义。例如: v-if, v-for… 常用指令: v-bind:为 HTML 标签绑定属性值,如设置 href,css 样式等 <a v-bind:href"…...
Linux 指令心法(十四)`flash_erase` 擦除Flash存储器
文章目录 flash_erase 作用flash_erase的主要特点和使用场景flash_erase命令应用方法flash_erase命令可以解决哪些问题?flash_erase命令使用时注意事项 flash_erase 作用 这是一个用于擦除Flash存储器的命令。它可以擦除指定的Flash块或扇区,以便在写入新数据之前…...

GoLong的学习之路(二十一)进阶,语法之并发(go最重要的特点)(协程的主要用法)
并发编程在当前软件领域是一个非常重要的概念,随着CPU等硬件的发展,我们无一例外的想让我们的程序运行的快一点、再快一点。Go语言在语言层面天生支持并发,充分利用现代CPU的多核优势,这也是Go语言能够大范围流行的一个很重要的原…...
加快网站收录 3小时百度收录新站方法
加快网站收录 3小时百度收录新站方法 3小时百度收录新站方法说起来大家可能不相信,但这确实是真实的(该方法是通过技术提交,让百度快速抓取收录您的网站,不管你网站有没有备案,都能在短时间内被收录,要是你的网站迟迟不…...
GPT实战系列-ChatGLM3本地部署CUDA11+1080Ti+显卡24G实战方案
目录 一、ChatGLM3 模型 二、资源需求 三、部署安装 配置环境 安装过程 低成本配置部署方案 四、启动 ChatGLM3 五、功能测试 新鲜出炉,国产 GPT 版本迭代更新啦~清华团队刚刚发布ChatGLM3,恰逢云栖大会前百川也发布Baichuan2-192K,一…...

图片怎么转换成pdf?
图片怎么转换成pdf?图片可以转换成PDF格式文档吗?当然是可以的呀,当图片转换成PDF文件类型时,我们就会发现图片更加方便的打开分享和传播,而且还可以更加安全的保证我们的图片所有性。我们知道PDF文档是可以加密的&…...

【源码】医学影像PACS实现三维影像后处理等功能
医学影像诊断技术近年来取得了快速发展,包括高性能的影像检查设备的临床应用和数字信息技术的图像显示、存储、传输、处理、识别,这些技术使得计算机辅助检测和诊断成为可能,同时人工智能影像诊断也进入了人们的视野。这些技术进步提高了疾病…...

DOCTYPE是什么,有何作用、 使用方式、渲染模式、严格模式和怪异模式的区别?
前言 持续学习总结输出中,今天分享的是DOCTYPE是什么,有何作用、 使用方式、渲染模式、严格模式和怪异模式的区别。 DOCTYPE是什么,有何作用? DOCTYPE是HTML5的文档声明,通过它可以告诉浏览器,使用那个H…...
Go语言实现HTTP正向代理
文章目录 前言实现思路代码实现 前言 正向代理(Forward Proxy)是一种代理服务器的部署方式,它位于客户端和目标服务器之间,代表客户端向目标服务器发送请求。正向代理可以用来隐藏客户端的真实身份,以及在不同网络环境…...

第11章_数据处理之增删改
文章目录 1 插入数据1.1 实际问题1.2 方式 1:VALUES的方式添加1.3 方式2:将查询结果插入到表中演示代码 2 更新数据演示代码 3 删除数据演示代码 4 MySQL8新特性:计算列演示代码 5 综合案例课后练习 1 插入数据 1.1 实际问题 解决方式&#…...

数据时代的新引擎:数据治理与开发,揭秘数据领域的黄金机遇!
文章目录 一、数据时代的需求二、数据治理与开发三、案例分析四、黄金机遇《数据要素安全流通》《Python数据挖掘:入门、进阶与实用案例分析》《数据保护:工作负载的可恢复性 》《Data Mesh权威指南》《分布式统一大数据虚拟文件系统 Alluxio原理、技术与…...
使用 Golang 实现基于时间的一次性密码 TOTP
上篇文章详细讲解了一次性密码 OTP 相关的知识,基于时间的一次性密码 TOTP 是 OTP 的一种实现方式。这种方法的优点是不依赖网络,因此即使在没有网络的情况下,用户也可以生成密码。所以这种方式被许多流行的网站使用到双因子或多因子认证中&a…...

微服务之Nacos配置管理
文章目录 一、统一配置管理Nacos操作二、统一配置管理java操作1.引入依赖2.创建配置文件3.测试4.总结 三、Nacos配置自动更新1.添加注解RefreshScope2.使用ConfigurationProperties注解3.总结 四、Nacos多环境配置共享1.配置文件2.多种配置的优先级3.总结 一、统一配置管理Naco…...
PySpark 优雅的解决依赖包管理
背景 平台所有的Spark任务都是采用Spark on yarn cluster的模式进行任务提交的,driver和executor随机分配在集群的各个节点,pySpark 由于python语言的性质,所以pySpark项目的依赖注定不能像java/scala项目那样把依赖打进jar包中轻松解决问题…...
UNI-APP_获取手机品牌
在uni-app中,使用uni.getSystemInfoSync().brand可以获取设备的品牌信息。根据不同设备的品牌,uni.getSystemInfoSync().brand可能返回以下一些常见值 “Apple” - 苹果 “Samsung” - 三星 “Huawei” - 华为 “Xiaomi” - 小米 “OPPO” - OPPO “Vivo…...

新登录接口独立版变现宝升级版知识付费小程序-多领域素材资源知识变现营销系统
源码简介: 资源入口 点击进入 源码亲测无bug,含前后端源码,非线传,修复最新登录接口 梦想贩卖机升级版,变现宝吸取了资源变现类产品的很多优点,摒弃了那些无关紧要的东西,使本产品在运营和变现…...

「掌握创意,释放想象」——Photoshop 2023,你的无限可能!
Adobe Photoshop 2023(PS2023) 来了,全世界数以百万计的设计师、摄影师和艺术家使用 Photoshop 将不可能变为可能。从海报到包装,从基本的横幅到漂亮的网站,从令人难忘的徽标到引人注目的图标,Photoshop 2023让创意世界不断前进。借助直观的工…...
SQLSugar查询返回DataTable
SQLSugar是一个用于执行SQL查询的C#库,它提供了简单易用的API接口来执行SQL查询。要查询返回DataTable,可以使用SQLSugar的QueryHelper类。 以下是一个示例代码,展示了如何使用SQLSugar的QueryHelper类查询返回DataTable: 首先&…...
挑战杯推荐项目
“人工智能”创意赛 - 智能艺术创作助手:借助大模型技术,开发能根据用户输入的主题、风格等要求,生成绘画、音乐、文学作品等多种形式艺术创作灵感或初稿的应用,帮助艺术家和创意爱好者激发创意、提高创作效率。 - 个性化梦境…...
React Native 导航系统实战(React Navigation)
导航系统实战(React Navigation) React Navigation 是 React Native 应用中最常用的导航库之一,它提供了多种导航模式,如堆栈导航(Stack Navigator)、标签导航(Tab Navigator)和抽屉…...
mongodb源码分析session执行handleRequest命令find过程
mongo/transport/service_state_machine.cpp已经分析startSession创建ASIOSession过程,并且验证connection是否超过限制ASIOSession和connection是循环接受客户端命令,把数据流转换成Message,状态转变流程是:State::Created 》 St…...

【第二十一章 SDIO接口(SDIO)】
第二十一章 SDIO接口 目录 第二十一章 SDIO接口(SDIO) 1 SDIO 主要功能 2 SDIO 总线拓扑 3 SDIO 功能描述 3.1 SDIO 适配器 3.2 SDIOAHB 接口 4 卡功能描述 4.1 卡识别模式 4.2 卡复位 4.3 操作电压范围确认 4.4 卡识别过程 4.5 写数据块 4.6 读数据块 4.7 数据流…...
Neo4j 集群管理:原理、技术与最佳实践深度解析
Neo4j 的集群技术是其企业级高可用性、可扩展性和容错能力的核心。通过深入分析官方文档,本文将系统阐述其集群管理的核心原理、关键技术、实用技巧和行业最佳实践。 Neo4j 的 Causal Clustering 架构提供了一个强大而灵活的基石,用于构建高可用、可扩展且一致的图数据库服务…...
Axios请求超时重发机制
Axios 超时重新请求实现方案 在 Axios 中实现超时重新请求可以通过以下几种方式: 1. 使用拦截器实现自动重试 import axios from axios;// 创建axios实例 const instance axios.create();// 设置超时时间 instance.defaults.timeout 5000;// 最大重试次数 cons…...
重启Eureka集群中的节点,对已经注册的服务有什么影响
先看答案,如果正确地操作,重启Eureka集群中的节点,对已经注册的服务影响非常小,甚至可以做到无感知。 但如果操作不当,可能会引发短暂的服务发现问题。 下面我们从Eureka的核心工作原理来详细分析这个问题。 Eureka的…...
#Uniapp篇:chrome调试unapp适配
chrome调试设备----使用Android模拟机开发调试移动端页面 Chrome://inspect/#devices MuMu模拟器Edge浏览器:Android原生APP嵌入的H5页面元素定位 chrome://inspect/#devices uniapp单位适配 根路径下 postcss.config.js 需要装这些插件 “postcss”: “^8.5.…...

面向无人机海岸带生态系统监测的语义分割基准数据集
描述:海岸带生态系统的监测是维护生态平衡和可持续发展的重要任务。语义分割技术在遥感影像中的应用为海岸带生态系统的精准监测提供了有效手段。然而,目前该领域仍面临一个挑战,即缺乏公开的专门面向海岸带生态系统的语义分割基准数据集。受…...

【电力电子】基于STM32F103C8T6单片机双极性SPWM逆变(硬件篇)
本项目是基于 STM32F103C8T6 微控制器的 SPWM(正弦脉宽调制)电源模块,能够生成可调频率和幅值的正弦波交流电源输出。该项目适用于逆变器、UPS电源、变频器等应用场景。 供电电源 输入电压采集 上图为本设计的电源电路,图中 D1 为二极管, 其目的是防止正负极电源反接, …...