Rust-06-所有权

所有权(系统)是 Rust 最为与众不同的特性,它让 Rust 无需垃圾回收即可保障内存安全,下面是所有权以及相关功能:借用(borrowing)、slice 以及 Rust 如何在内存中布局数据。
通过所有权系统管理内存,编译器在编译时会根据一系列的规则进行检查。如果违反了任何这些规则,程序都不能编译。在运行时,所有权系统的任何功能都不会减慢程序。

所有权

所有权规则

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

    { // s 在这里无效,因为还没有声名
        let s = "hello world!";
        // 使用变量s
    } // 作用域结束,s 不再有效
    
}

当 s 离开作用域的时候。当变量离开作用域,Rust 为我们调用一个特殊的函数。这个函数叫做 drop ,在这里 String 的
作者可以放置释放内存的代码。Rust 在结尾的 } 处自动调用 drop

变量与数据交互的方式(一):移动

    // 直接拷贝值
    let x = 10;
    let y = x;

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

    // 更改引用,不拷贝值
    let s1 = String::from("hello");
    let s2 = s1;


    println!("{s1} {s2}");

从string类型内存布局可以看出
在这里插入图片描述当我们将 s1 赋值给 s2 ,String 的数据被复制了,这意味着我们从栈上拷贝了它的指针、长度和容量。我们并没有复制指针指向的堆上数据。
在这里插入图片描述
如果直接拷贝堆上的数据,那么操作 s2 = s1 在堆上数据比较大的时候会对运行时性能造成非常大的影响。
过当变量离开作用域后,Rust 自动调用 drop 函数并清理变量的堆内存。不过上图展示了两个数据指针指向了同一位置。这就有了一个问题:当 s2 和 s1 离开作用域,它们都会尝试释放相同的内存。这是一个叫做 二次释放(double free)的错误,也是之前提到过的内存安全性 bug 之一。两次释放(相同)内存会导致内存污染,它可能会导致潜在的安全漏洞。

如果使用以下代码

    let s1 = String::from("hello");
    let s2 = s1;
    println!("{s1}");

会产生如下错误,因为 Rust 禁止你使用无效的引用。

error[E0382]: borrow of moved value: `s1`
  --> src\main.rs:56:15
   |
54 |     let s1 = String::from("hello");
   |         -- move occurs because `s1` has type `String`, which does not implement the `Copy` trait
55 |     let s2 = s1;
   |              -- value moved here
56 |     println!("{s1}");
   |               ^^^^ value borrowed here after move
   |
   = note: this error originates in the macro `$crate::format_args_nl` which comes from the expansion of the macro `println` (in Nightly builds, run with -Z macro-backtrace for more info)
help: consider cloning the value if the performance cost is acceptable
   |
55 |     let s2 = s1.clone();
   |                ++++++++

如果你在其他语言中听说过术语 浅拷贝(shallow copy)和 深拷贝(deep copy),那么拷贝指针、长度和容量而不拷贝数据可能听起来像浅拷贝。不过因为 Rust 同时使第一个变量无效了,这个操作被称为 移动(move),而不是叫做浅拷贝。上面的例子可以解读为 s1 被 移动 到了s2 中。
在这里插入图片描述
这样就解决了我们的问题!因为只有 s2 是有效的,当其离开作用域,它就释放自己的内存。另外,这里还隐含了一个设计选择:Rust 永远也不会自动创建数据的 “深拷贝”。因此,任何自动 的复制都可以被认为是对运行时性能影响较小的

变量与数据交互的方式(二):克隆

如果我们确实需要深度复制 String 中堆上的数据,而不仅仅是栈上的数据,可以使用一个叫做 clone 的通用函数。

    let s1 = String::from("hello");
    let s2 = s1.clone();
    println!("{s1} {s2}");

这里堆上的数据确实被复制了。

Rust 有一个叫做 Copy trait 的特殊注解可以用在类似整型这样的存储在栈上的类型上,如果一个类型实现了 Copy trait,那么一个旧的变量在将其赋值给其他变量后仍然可用。
以下是实现了 Copy trait的特殊类型,后续会谈到。
• 所有整数类型,比如 u32 。
• 布尔类型,bool ,它的值是 true 和 false 。
• 所有浮点数类型,比如 f64 。
• 字符类型,char 。
• 元组,当且仅当其包含的类型也都实现 Copy 的时候。比如,(i32, i32) 实现了 Copy ,但
(i32, String) 就没有。

所有权和函数

将值传递给函数与给变量赋值的原理相似。向函数传递值可能会移动或者复制,就像赋值语句一样。

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 移出作用域。没有特殊之处

返回值与作用域

返回值也可以转移所有权。

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 被清理掉,除非数据被移动为另一个变量所有。

引用与借用

使用&符号
与使用 & 引用相反的操作是 解引用(dereferencing),它使用解引用运算符 *
引用像一个指针,因为它是一个地址,我们可以由此访问储存于该地址的属于其他变量的数据。 与指针不同,引用确保指向某个特定类型的有效值。下面是如何定义并使用一个(新的)calculate_length 函数,它以一个对象的引用作为参数而不是获取值的所有权:

    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()
    }

在这里插入图片描述
&s1 语法让我们创建一个指向值 s1 的引用,但是并不拥有它。因为并不拥有这个值,所以当引用停止使用时,它所指向的值也不会被丢弃。
我们将创建一个引用的行为称为 借用(borrowing)。正如现实生活中,如果一个人拥有某样东西,你可以从他那里借来。当你使用完毕,必须还回去。我们并不拥有它。正如变量默认是不可变的,引用也一样。(默认)不允许修改引用的值。
在这里插入图片描述

虽然是不可变的,但是编译器已经给出了解决办法,那就是和变量一样,使用mut,微调一下上面代码

fn main() {
    let mut s1 = String::from("hello");
    // 传一个引用进去
    change(&mut s1);
    println!("{s1}");

}
fn change(s: &mut String) {
    s.push_str("hello");
}

可变引用有一个很大的限制:如果你有一个对该变量的可变引用,你就不能再创建对该变量的引用。这些尝试创建两个 s 的可变引用的代码会失败:

fn main() {
    let mut s1 = String::from("hello");
    let r1 = &mut s1;
    let r2 = &mut s1;

    println!("{r1} {r2}");
}

错误如下

error[E0499]: cannot borrow `s1` as mutable more than once at a time
  --> src\main.rs:63:14
   |
62 |     let r1 = &mut s1;
   |              ------- first mutable borrow occurs here
63 |     let r2 = &mut s1;
   |              ^^^^^^^ second mutable borrow occurs here
64 |
65 |     println!("{r1} {r2}");
   |               ---- first borrow later used here

第一个可变的借入在 r1 中,并且必须持续到在 println! 中使用它,但是在那个可变引用的创建和它的使用之间,我们又尝试在 r2 中创建另一个可变引用,该引用借用与 r1 相同的数据。主要是为了限制在同一时间对统一数据存在多个可变引用,避免了数据竞争。Rust 避免了这种情况的发生,因为它甚至不会编译存在数据竞争的代码!
数据竞争由三个行为造成:
• 两个或更多指针同时访问同一数据。
• 至少有一个指针被用来写入数据。
• 没有同步数据访问的机制。
修改一下使用之后再进行赋值可变引用:

    let mut s1 = String::from("hello");
    let r1 = &mut s1;
    println!("{r1}");
    let r2 = &mut s1;
    println!("{r2}");

它们的作用域没有重叠,所以代码是可以编译的。编译器可以在作用域结束之前判断不再使用的引用。

悬垂引用

在具有指针的语言中,很容易通过释放内存时保留指向它的指针而错误地生成一个 悬垂指针(dangling pointer),所谓悬垂指针是其指向的内存可能已经被分配给其它持有者。相比之下,在 Rust 中编译器确保引用永远也不会变成悬垂状态:当你拥有一些数据的引用,编译器确保数据不会在其引用之前离开作用域。

fn main() {
 let reference_to_nothing = dangle();
}
fn dangle() -> &String {  // dangle 返回一个字符串的引用
 let s = String::from("hello"); // s 是一个新字符串
 &s // 返回字符串 s 的引用
}  // 这里 s 离开作用域并被丢弃。其内存被释放。

因为 s 是在 dangle 函数内创建的,当 dangle 的代码执行完毕后,s 将被释放。不过我们尝试返回它的引用。这意味着这个引用会指向一个无效的 String ,Rust 不会允许我们这么做。

引用的规则

概括一下之前对引用的讨论:
• 在任意给定时间,要么 只能有一个可变引用,要么 只能有多个不可变引用。
• 引用必须总是有效的。

slice

slice 允许你引用集合中一段连续的元素序列,而不用引用整个集合。slice 是一类引用,所以它没有所有权。类似于切片

fn main() {
 	let s = String::from("hello world");
 	let hello = &s[0..5];
 	let world = &s[6..11];
 }

在这里插入图片描述

字符串slice

知道了slice之后可以理解字符串了,这里 s 的类型是 &str :它是一个指向二进制程序特定位置的 slice。这也就是为什么字符串
字面值是不可变的;&str 是一个不可变引用。

let s = "Hello, world!";

其它类型slice

就跟我们想要获取字符串的一部分那样,我们也会想要引用数组的一部分。我们可以这样做:

let a = [1, 2, 3, 4, 5];
let slice = &a[1..3];
assert_eq!(slice, &[2, 3]);

这个 slice 的类型是 &[i32] 。它跟字符串 slice 的工作方式一样,通过存储第一个集合元素的引用和一个集合总长度。你可以对其他所有集合使用这类 slice。

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

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

相关文章

Java版工程项目管理平台:以源码驱动,引领工程企业数字化转型

在当今数字化时代,随着企业的扩张和业务的增长,传统的工程项目管理方法已显不足。为了提升管理效率、减轻工作负担、增强信息处理的快速性和精确度,工程企业亟需借助数字化技术进行转型升级。本文将向您展示一款基于Spring Cloud、Spring Boo…

当前主流的App开发技术综述

一、引言 随着移动互联网的蓬勃发展,App(应用程序)已经成为人们日常生活中不可或缺的一部分。无论是社交、购物、娱乐还是工作学习,App都以其便捷、高效和个性化的特点深受用户喜爱。而在这一过程中,App开发技术也在不…

MFC 教程-文本框失去焦点处理

【1】增加窗口的消息处理函数 void CTESTMFCDlg::OnKillFocus(CWnd* pNewWnd) {//CDialogEx::OnKillFocus(pNewWnd);//首先使用true将数据从控件传入成员变量中UpdateData(true);//校验成员变量m_data中的数据是否符合要求,如果不符合,修改后将它显示在控…

访问网站时IP被阻止?原因及解决方法

在互联网上,用户可能会面临一个令人困扰的问题——当尝试访问某个特定的网站时,却发现自己的IP地址被该网站屏蔽。 IP地址被网站屏蔽是一个相对常见的现象,而导致这种情况的原因多种多样,包括恶意行为、违规访问等。本文将解释IP地…

转让北京公司带旅行许可证流程和要求

旅行社是开展旅游服务业务的专项经济主体,旅行社开展相关业务必须持有旅行社业务经营许可证。该资质又分为国内旅行社经营许可证和出境旅行社经营许可证。主要区别在于能否开展出境旅游业务,下面老耿带大家了解,新成立国内旅行社要求及出境旅…

Git之解决重复输入用户名和密码(三十九)

简介: CSDN博客专家,专注Android/Linux系统,分享多mic语音方案、音视频、编解码等技术,与大家一起成长! 优质专栏:Audio工程师进阶系列【原创干货持续更新中……】🚀 优质专栏:多媒…

【CS.CN】深入探讨下HTTP的Connection头:通过keep-alive实现高效网络连接

文章目录 0 序言0.1 由来0.2 使用场景0.3 现在还需要吗? 1 Connection: keep-alive的机制2 语法 && 通过设置Connection: keep-alive优化性能3 验证与性能提升4 总结References 0 序言 0.1 由来 Connection头部字段在HTTP/1.1中被引入,主要用于…

React的useState的基础使用

import {useState} from react // 1.调用useState添加状态变量 // count 是新增的状态变量 // setCount 修改状态变量的方法 // 2.添加点击事件回调 // userState实现计数实例import {useState} from react// 使用组件 function App() {// 1.调用useState添加状态变量// coun…

ChatTTS增强版V2,批量导出srt,语速控制,情感控制,支持朗读数字,问题修复

ChatTTS增强版最新版本已经发布,本次更新我主要增加了多文本批量、SRT导出、语速控制、情感控制、停顿控制等新功能,并针对上一版本中存在的数字读音异常、随机uv_break等问题进行了修复。 视频版本 【ChatTTS增强版V2,批量导出srt&#xff…

基于C#的计算机与安捷伦34970A通信方法

概述 安捷伦34970A采集数据,34970A支持RS232接口,但是如果直接用winform自带的seriaport类基本是没必要使用的,安捷伦等仪表通讯需要用到VISA的库。 库的获取 1. 是德科技的IO Library. 2. NI下载NI-VISA. 两者用法接近. 代码如下 using…

数据库安全加固与API防护策略

在数字化时代,数据库作为企业核心资产的安全性至关重要。然而,随着网络攻击手段的不断演进,数据库和API接口成为了黑客的主要攻击目标。本文将探讨数据库被攻击、API接口被滥用的情况,并提供一系列实用的防护措施,旨在…

JSP中连接数据库MySQL

JSP中连接数据库MySQL 一、软件环境 下载并安装MySQL,Tomacat,JDBC、IDEA或其他IDE,本文使用IDEA 二、环境配置 将其环境变量配置好之后,下载Java 专用的连接MySQL的驱动包JDBC。 下载链接:https://dev.mysql.com/…

机器学习----奥卡姆剃刀定律

奥卡姆剃刀定律(Occam’s Razor)是一条哲学原则,通常表述为“如无必要,勿增实体”(Entities should not be multiplied beyond necessity)或“在其他条件相同的情况下,最简单的解释往往是最好的…

一篇学会Arthas的基本使用及常用指令

下载安装 下载arthas的jar包 https://alibaba.github.io/arthas/arthas-boot.jar将jar包下载后放到指定的文件夹中 启动与结束 winr打开命令行使用命令运行arthas对应的jar包 java -jar D:\application\arthas\arthas-boot.jar会列出所有的java服务选择要监控的服务&#xff0c…

mqtt-emqx:paho.mqttv5的简单例子

# 安装emqx 请参考【https://blog.csdn.net/chenhz2284/article/details/139551293?spm1001.2014.3001.5502】 # 下面是示例代码 【pom.xml】 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</…

2024教资认定报名流程,点赞收藏!

2024年要进行教资认定的宝子们提早准备 &#x1f525;教资认定网上报名流程概览 一、进入教资认定网报入口 二、进行实名核验 三、申请网报时间查询 四、个人信息维护 五、认定申请报名 &#x1f525;教资认定所需材料 1⃣️身份证 2⃣️户口本&#xff0f;居住证&#xff0f;学…

算法导论实战(三)(算法导论习题第二十五、二十六章)

&#x1f308; 个人主页&#xff1a;十二月的猫-CSDN博客 &#x1f525; 系列专栏&#xff1a; &#x1f3c0;算法启示录 &#x1f4aa;&#x1f3fb; 十二月的寒冬阻挡不了春天的脚步&#xff0c;十二点的黑夜遮蔽不住黎明的曙光 目录 前言 第二十五章 25.1-10 25.2-5 25…

Liunx环境下redis主从集群搭建(保姆级教学)01

Linux 环境安装redis 准备一台linux虚拟机 我使用基于Linux的开源类服务器操作系统CentOS7。 打开虚拟机&#xff0c;输入密码登录 下载linux版本的redis安装包 已经下载redis-5.0.10.tar.gz 创建一个文件夹用来安装redis,我在/opt目录下创建redis文件夹 将下载好的redis…

Vue3 + TS + Antd + Pinia 从零搭建后台系统(一) 脚手架搭建 + 入口配置

简易后台系统搭建开启&#xff0c;分几篇文章更新&#xff0c;本篇主要先搭架子&#xff0c;配置入口文件等目录 效果图一、搭建脚手架&#xff1a;二、处理package.json基础需要的依赖及运行脚本三、创建环境运行文件四、填充vue.config.ts配置文件五、配置vite-env.d.ts使项目…

Vue3【十一】08使用toRefs和toRef

08使用toRefs和toRef toRefs()函数将person对象中的name和age属性转换为响应式引用&#xff0c;并返回一个对象&#xff0c;对象中的name和age属性都是响应式引用&#xff0c;具有响应式功能。 toRef()函数将person对象中的name属性转换为响应式引用&#xff0c;并返回一个响应…