Day3:Rust所有权

Rust所有权

所有权使得Rust无需垃圾回收器(garbage collector)即可保证内存安全。

Rust中的栈(Stack)和堆(Heap)

  • 栈:以放入值顺序存储值,以相反的方向取出值。(后进先出),栈中所有数据必须占用已知且固定的大小,在编译未知大小或者大小可以变化的数据,要存储在堆上。
    • 栈中增加数据叫做进栈,移出数据叫做出栈。
  • 堆:堆中存储数据,要先请求一定大小的空间,并标记为已经使用,返回一个表示该地址位置的指针。(这个过程叫做在堆上分配内存)
  • 指针可以存储在栈上,但实际需要时,必须访问指针。指针大小是固定的,所以不会定义为==“分配”==
  • 入栈相比在堆上分配内存较快,因为入栈无需分配新的内存,内存会存储在栈顶,而堆上分配内存会先找到足够存放数据的内存空间,然后为分配内存做准备。
  • 访问堆上的内存需要使用指针访问,所以访问数据时栈相比堆也较快,在堆上分配大量的空间会消耗时间,数据距离较近(栈)相比较远(堆)中能更好的工作。
  • 当你的代码调用一个函数时,传递给函数的值(包括可能指向堆上数据的指针)和函数的局部变量被压入栈中。当函数结束时,这些值被移出栈。

而所有权的出现就时为了更加有效的管理堆中的数据。

所有权规则

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

变量的作用域

fn main{
let s = "hello";
}
  • 这个变量在main函数内部都有效,这个就是它的作用域。

String 类型

  • string是可变的,而字面量是不可变的。
fn main() {
    let mut s = String::from("hello");

    s.push_str(", world!"); // push_str() 在字符串后追加字面值

    println!("{}", s); // 将打印 `hello, world!`
}

  • 字符串字面量是通过硬编码存储在程序中,但是他们是不可变的。
  • 可以使用from函数来进行基于字符串字面变量来创建String,同时可以使用s.push_str("xxxx")在字符串之后添加新的字符串字面值。

内存和分配

下面是官方文档的描述:

在这里插入图片描述

  • String类型在堆上分配在编译时大小未知的内存存放内容。
    • 在运行时向分配器请求内存
    • 需要处理完String时将内存返回给分配器的方法。
  • Rust中内存回收在变量离开作用域时就会自动释放。
fn main() {
    {
        let s = String::from("hello"); // 从此处起,s 开始有效

        // 使用 s
    }                                  // 此作用域已结束,
                                       // s 不再有效
}

Rust中释放内存代码自动使用drop函数,使用位置在"}"处使用。在C++中又称做资源获取即初始化

变量和数据交互的方法(一):移动

let x = 5;
let y = x;
  • 多个变量可以和一个数据以不同的方式交互。
let s1 = String::from("hello");
let s2 = s1;

但是string和整形变量的交互方式并不相同,string组成部分有三个,指向存放字符串内容内存的指针,一个长度,一个容量。
下面是官方文档:

在这里插入图片描述


在这里插入图片描述


简而言之,Rust中在拷贝过程中只拷贝了指针,长度,容量,而没有直接拷贝堆上数据。

  • 然而,这种拷贝过后在释放内存的过程中,需要了解的是,二者指向同一个位置(数据指针),所以在释放过程中有可能造成二次释放错误,但是Rust中做出了约束,释放过程中只会释放s2,而将s1看作不再有效。
fn main() {
    let s1 = String::from("hello");
    let s2 = s1;

    println!("{}, world!", s1);
}

$ cargo run
   Compiling ownership v0.1.0 (file:///projects/ownership)
error[E0382]: borrow of moved value: `s1`
 --> src/main.rs:5:28
  |
2 |     let s1 = String::from("hello");
  |         -- move occurs because `s1` has type `String`, which does not implement the `Copy` trait
3 |     let s2 = s1;
  |              -- value moved here
4 | 
5 |     println!("{}, world!", s1);
  |                            ^^ value borrowed here after move

For more information about this error, try `rustc --explain E0382`.
error: could not compile `ownership` due to previous error

Rust禁止无效引用。
在其它语言中,将指针、长度、容量进行拷贝而不对数据拷贝叫做浅拷贝,对应的有深拷贝,而在Rust中不仅做出了浅拷贝,同时使第一个变量无效,这种方式叫做移动

在这里插入图片描述


这种方式,有点类似指针指向同一个地址。
但是s1此时是无效的。

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

如果需要深拷贝,需要使用一个函数clone,但是这会导致运行速度降低。

fn main() {
    let s1 = String::from("hello");
    let s2 = s1.clone();

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

这时,处在堆上的数据是被复制的。复制方式如图4.3.(官方文档)

只在栈上的数据:拷贝

与String不同,整型变量为大小已知的变量,存储在栈上,下面代码中:

fn main() {
    let x = 5;
    let y = x;

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

  • 会正确的编译出来,也就是说,此时x、y都是有效的变量。
  • Rust中变量有Copy trait、Drop traittrait、并且这两种trait是不能够相互兼容的,类似string的无法使用copy trait
  • 作为通用的标准,任何一组简单标量值组合都能够实现Copy,任何不需要分配内存或某种形式资源类型可以实现Copy,下面是常见的Copy类型:
    • 所有的整数所有整数类型,比如 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 移出作用域。不会有特殊操作

在这里插入图片描述

下面是错误代码,在some_string结束时会使用drop释放内存。

返回值和作用域

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 并移出给调用的函数
}

  • 这里s1得到了give_ownership()函数中的返回值

  • s3得到了take_and_gives_back()函数中的返回值。

  • 此时需要注意的是:s2已经被移动到takes_and_gives_back中,后面再使用时就使用不了了。
    若在主函数末尾添加println!("{}",s2);会报错,显示borrow of moved value: s2

  • 如果想要使用一个值但是不反回所有权,可以使用元组

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中有更好的方法,叫做引用。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


学习编程是顺着互联网的发展潮流,是一件好事。新手如何学习编程?其实不难,不过在学习编程之前你得先了解你的目的是什么?这个很重要,因为目的决定你的发展方向、决定你的发展速度。
IT行业是什么工作做什么?IT行业的工作有:产品策划类、页面设计类、前端与移动、开发与测试、营销推广类、数据运营类、运营维护类、游戏相关类等,根据不同的分类下面有细分了不同的岗位。
女生学Java好就业吗?女生适合学Java编程吗?目前有不少女生学习Java开发,但要结合自身的情况,先了解自己适不适合去学习Java,不要盲目的选择不适合自己的Java培训班进行学习。只要肯下功夫钻研,多看、多想、多练
Can’t connect to local MySQL server through socket \'/var/lib/mysql/mysql.sock问题 1.进入mysql路径
oracle基本命令 一、登录操作 1.管理员登录 # 管理员登录 sqlplus / as sysdba 2.普通用户登录
一、背景 因为项目中需要通北京网络,所以需要连vpn,但是服务器有时候会断掉,所以写个shell脚本每五分钟去判断是否连接,于是就有下面的shell脚本。
BETWEEN 操作符选取介于两个值之间的数据范围内的值。这些值可以是数值、文本或者日期。
假如你已经使用过苹果开发者中心上架app,你肯定知道在苹果开发者中心的web界面,无法直接提交ipa文件,而是需要使用第三方工具,将ipa文件上传到构建版本,开...
下面的 SQL 语句指定了两个别名,一个是 name 列的别名,一个是 country 列的别名。**提示:**如果列名称包含空格,要求使用双引号或方括号:
在使用H5混合开发的app打包后,需要将ipa文件上传到appstore进行发布,就需要去苹果开发者中心进行发布。​
+----+--------------+---------------------------+-------+---------+
数组的声明并不是声明一个个单独的变量,比如 number0、number1、...、number99,而是声明一个数组变量,比如 numbers,然后使用 nu...
第一步:到appuploader官网下载辅助工具和iCloud驱动,使用前面创建的AppID登录。
如需删除表中的列,请使用下面的语法(请注意,某些数据库系统不允许这种在数据库表中删除列的方式):
前不久在制作win11pe,制作了一版,1.26GB,太大了,不满意,想再裁剪下,发现这次dism mount正常,commit或discard巨慢,以前都很快...
赛门铁克各个版本概览:https://knowledge.broadcom.com/external/article?legacyId=tech163829
实测Python 3.6.6用pip 21.3.1,再高就报错了,Python 3.10.7用pip 22.3.1是可以的
Broadcom Corporation (博通公司,股票代号AVGO)是全球领先的有线和无线通信半导体公司。其产品实现向家庭、 办公室和移动环境以及在这些环境...
发现个问题,server2016上安装了c4d这些版本,低版本的正常显示窗格,但红色圈出的高版本c4d打开后不显示窗格,
TAT:https://cloud.tencent.com/document/product/1340