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 所有权规则

首先,让我们看一下所有权的规则。当我们通过举例说明时,请谨记这些规则:

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

 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 collectorGC)的语言中,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 将返回值
                                        // 转移给 s1

    let 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),后面我们再来看看引用是什么?

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Verilog使用vscode

使用vscode打开.v文件 Tools setting texteditor vscode文件路径 [line number]:[file name] (可能会出错,可以去vscode确认打开的文件路径,后经调整后改为 vscode文件路径 [file name]) 安装插件 搜索Verilog 添加使用最多的 …

3D视觉引导纸箱拆码垛,助力物流行业转型升级

近年来,自动化和智能化技术在各行业的应用越来越广泛,特别是在物流和仓储领域。纸箱拆码垛是物流仓储中的一个重要环节。 人工分拣效率低、错误率高、成本高,传统的拆码垛设备存在兼容性差,对纸箱的识别率不高、操作不灵活等问题…

Centos7下搭建H3C log服务器

一、rsyslogH3C 安装rsyslog服务器 关闭防火墙 systemctl stop firewalld && systemctl disable firewalld关闭selinux sed -i s/enforcing/disabled/ /etc/selinux/config && setenforce 0centos7服务器,通过yum安装rsyslog yum -y install r…

vr地铁消防虚拟逃生自救系统降低财产及人员伤害

无论是在公共场所还是在家中,火灾都是一种常见的突发事件。这往往会严重影响到人们的财产和生命安全。因此,如何预防火灾和安全逃生就成为了非常重要的话题。这款VR模拟火灾疏散逃生系统,帮助人们了解火灾逃生的技巧以及正确的应对方法。 以传…

Leetcode刷题详解——子集

1. 题目链接:78. 子集 2. 题目描述: 给你一个整数数组 nums ,数组中的元素 互不相同 。返回该数组所有可能的子集(幂集)。 解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。 示例 1: 输入&#xf…

day05_Java中的运算符

在Java中提供了丰富的运算符 其按照功能分:算术运算符、赋值运算符、比较运算符、逻辑运算、条件运算符按照操作数个数分:一元运算符(单目运算符)、二元运算符(双目运算符)、三元运算符 (三目…

zabbix监控安装-linux

zabbix6.4中文文档1. 简介 (zabbix.com) Zabbix 是一个企业级的开源分布式监控解决方案。 1.zabbix结构体系 Server: server 是存储所有配置、统计和操作数据的中央存储库。 Proxy: zabbix proxy可以代替 Zabbix server 收集性能和可用性数据。p…

AI芯片架构体系综述:芯片类型CPU\GPU\FPGA\ASIC以及指令集CSIS\RISC介绍

大模型的发展意味着算力变的越发重要,因为大国间科技竞争的关系,国内AI从业方在未来的一段时间存在着算力不确定性的问题,与之而来的是许多新型算力替代方案的产生。如何从架构关系上很好的理解计算芯片的种类,并且从计算类型、生…

Java附件和base64相互转换

1 文件转base64 声明:我用的是Hutool的Base64下的api package cn.hutool.core.codec; 首先找一张图片 很简单,直接使用Base64的encode方法就可以拿到文件的base64码: File file new File("D:\\Tools\\Images\\北极熊.jpg");String…

Feign服务调用

Feign服务调用 使用Feign&#xff0c;在服务消费者中&#xff0c;调用服务提供者的接口。 注册中心 此处使用 Nacos&#xff0c;详情见&#xff1a; https://www.cnblogs.com/expiator/p/17392549.html Feign依赖 <properties><java.version>1.8</java.vers…

OJ中常用平衡树,Treap树堆详解

文章目录 Treap定义Treap的可行性Treap的构建节点定义旋转左单旋右单旋旋转的代码实现 插入插入的代码实现 删除遍历查找Treap对权值的扩展Treap对size的扩展扩展size域后的节点定义和旋转&#xff0c;插入&#xff0c;删除操作查询第k小的元素求元素的排名 查询后继、前驱Trea…

虚幻引擎:RPC:远端调用

1.如何区当前是服务器还是在客服端 2.如何修改一个actor的所有权 修改所有权必须 在服务器上进行修改,不允许在客户端进行修改

大数据商城人流数据分析与可视化 - python 大数据分析 计算机竞赛

0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; 基于大数据的基站数据分析与可视化 该项目较为新颖&#xff0c;适合作为竞赛课题方向&#xff0c;学长非常推荐&#xff01; &#x1f947;学长这里给一个题目综合评分(每项满分5分) 难度…

220v插座led指示灯维修

由于220v是交流电&#xff0c;有反向电压的情况&#xff0c;而led反向通电的时候电阻无穷大&#xff0c;所以分压也无穷大&#xff0c;220v一导通就击穿&#xff0c;即使加了很大的电阻也没用&#xff0c;串联电阻只能作用于二极管正向的时候。 目前有两种方案&#xff1a; 方…

远程运维用什么软件?可以保障更安全?

远程运维顾名思义就是通过远程的方式IT设备等运行、维护。远程运维适用场景包含因疫情居家办公&#xff0c;包含放假期间出现运维故障远程解决&#xff0c;包含项目太远需要远程操作等等。但远程运维过程存在一定风险&#xff0c;安全性无法保障&#xff0c;所以一定要选择靠谱…

【深度学习】pytorch——神经网络工具箱nn

笔记为自我总结整理的学习笔记&#xff0c;若有错误欢迎指出哟~ 深度学习专栏链接&#xff1a; http://t.csdnimg.cn/dscW7 pytorch——神经网络工具箱nn 简介nn.Modulenn.Module实现全连接层nn.Module实现多层感知机 常用神经网络层图像相关层卷积层&#xff08;Conv&#xff…

MPLAB X IDE 仿真打断点提示已中断的断点?

这种中间带裂缝的是无效断点。 原因可能与XC编译器的优化有关&#xff0c;最后生成的汇编与C语言并不是一一对应的(官方给的解释是效率高)。所以这一行C语言转换的汇编代码可能并不在这个位置&#xff0c;也可能与其它汇编合并后根本就没有 我的解决方法是把优化等级调到最低&a…

MapReduce:大数据处理的范式

一、介绍 在当今的数字时代&#xff0c;生成和收集的数据量正以前所未有的速度增长。这种数据的爆炸式增长催生了大数据领域&#xff0c;传统的数据处理方法往往不足。MapReduce是一个编程模型和相关框架&#xff0c;已成为应对大数据处理挑战的强大解决方案。本文探讨了MapRed…

wpf添加Halcon的窗口控件报错:下列控件已成功添加到工具箱中,但未在活动设计器中启用

报错截图如下&#xff1a; 注意一下新建工程的时候选择wpf应用而不是wpf应用程序。 添加成功的控件&#xff1a;

python 之 正则表达式模块re

文章目录 findall例子&#xff1a;特点和注意事项&#xff1a; match示例&#xff1a;match 对象的方法和属性&#xff1a;注意事项&#xff1a; search示例&#xff1a;match 对象的方法和属性&#xff1a;注意事项&#xff1a; split示例&#xff1a;参数说明&#xff1a;注意…