Rust 基础再理解

Rust堆栈

Rust中各种类型的值默认都存储在栈中,除非显式地使用Box::new()将它们存放在堆上,但数据要存放在栈中,要求其数据类型的大小已知。对于静态大小的类型,可直接存储在栈上,如裸指针、布尔、字符、整数浮点数,数组等。

动态大小的(Vecstring)都是存堆的
一些注意事项

  • 栈中的数据赋值给变量的时候,数据是直接放在栈中的。
  • 类型的值都默认放在栈中,所以创建引用的时候,引用的是栈里的值。
  • 容器中保存的是原始类型的栈里的值或者指向堆数据的引用
  • 字符串字面量,static静态变量都会硬编码嵌入到二进制程序的全局内存区
  • const定义的常量,会在编译期间直接以硬编码的方式内联插入到使用常量的地方,即,直接硬编码到对应代码行。同时函数也可以内联,即函数对应代码体会直接展开并插入到调用函数的地方,省去调用函数的开销。

位置与值

位置 :某一块内存位置,它有自己的地址,有自己的空间,有自己所保存的值。
:存储到位置中的数据(即保存在内存中的数据)
位置的产生

  • 会产生变量的时候(初始化)
  • 需要保存某个值的时候(函数调用参数和返回值)
  • 产生新的值(引用,解引用)

let 语句

let a = 1;
a:为变量名,也是对内存位置的一个可读代号,编译期间会被替换为更低级的代号或者直接为地址。也就是位置,是存值1的一块内存。

每个位置就是它所放值的所有值,因为每个值都只能存放在一个位置中,所以每个值都只能有一个所有者。
let v = vec![1, 2, 3, 4];
v:位置,代表栈中的一块内存,值是一个指针地址,实际数据是放在堆里的。

引用

Rust的引用是一种原始数据类型,位置仍然是栈里,保存的值和指针一样,是一个地址。该地址指向了**位置(**也就是前面的av

rust 复制代码
let n = 33; // 假设n的地址为0x234
let nn = &n; // 假设nn的地址为0x123

那么,nn的位置是0x123,它存的值是0x234,也就是n的地址。

编译器维护栈内存,所以它知道栈中的某个内存是否安全,而堆内存由程序员自己负责,程序员自己的行为是无法保证安全的。

所以,Rust的行为模式是将是涉及到内存安全的概念扔到栈上,让程序员远离对堆的操作。所以,允许允许对栈中同一个数据的多个指向,不允许对堆中同一个内存的多个指向,即变量存在多个引用,但所有权只能有一个

位置的属性

位置的属性和状态都由编译器在编译期进行维护。

位置有类型,有标记(是否被引用,可变引用还是不可变,共享还是独占等等),根据位置的类型是否实现Copy Trait来决定该位置的是拷贝还是移走。

所有权和借用

变量作用域

我们知道rust 变量在脱离作用域之后就会被销毁,但事实是,变量在跳出作用域时,会自动Drop Traitdrop函数来销毁内存中堆和栈的数据,全局内存中的数据是从程序启动到终止期间一直存在。

另外rust的作用域为一对大括号{},大括号的作用域是可以访问大括号外部的变量,而在函数的作用域内则不行,这被称为捕获环境,函数是不能捕获环境的,而大括号可以捕获环境

数据的拷贝

由于所有权问题和变量脱离作用域而引起的内存二次释放问题,rust是不允许有两个指针同时指向同一块内存的。所以rust 没有浅拷贝和深拷贝的概念,取而代之的是movecopyclone

  • move:也就是转移所有权,涉及到的过程是拷贝到目标变量,同时会将原来的变量设置到未初始的状态。rust 默认使用的就是move

当使用值的时候,就会产生位置,那么就会发生移动。解引用,字段访问,索引访问等都会隐式移动。

  • copy:和move的区别就是,拷贝之后原来的变量还是可以用。如果要使用copy,就需要要拷贝的数据类型实现了Copy Trait,手动实现的时候需要同时实现Clone Trait
  • cloneclonecopy很接近,区别在于,
  • Copy时,只拷贝变量本身的值,如果这个变量指向了其它数据,则不会拷贝其指向的数据。
  • Clone时,拷贝变量本身的值,如果这个变量指向了其它数据,则也会拷贝其指向的数据。

函数调用之后也是会转移所有权的,有时候这样是很不方便的,所以在传参的时候可以传递到变量的引用,引用时保存在栈里,也实现了Copy Trait,这样效率会更高。

可变引用的排他性

不可变引用是可以共存的,但是可变引用具有排他性,在同一作用域同一数据只能有一个。

这里的排他性,应该看作一把独占锁,在当前作用域内,从第一次使用可变引用开始创建这把独占锁,之后无论使用原始变量(即所有权拥有者)、可变引用还是不可变引用都会抢占这把独占锁,以保证只有一方可以访问数据,每次抢得独占锁后,都会将之前所有引用变量给锁住,使它们变成不可用状态。当离开当前作用域时,当前作用域内的所有独占锁都被释放。

回顾一下可变引用的几个性质

  • 同一作用域,特定数据只能有一个可变引用
  • 可变借用不能用于不可变借用上
  • 有了可变借用就不能再有不可变借用
  • 引用作用域和变量作用域不一样,它的结束位置再最后一次使用的位置

自从第一次使用可变引用导致独占锁出现后,可以随时使用原始变量、可变引用或不可变引用来抢独占锁,但抢锁后以前的引用变量就不能再用,且当前持有的锁也可以随时被抢走。不可变引用抢占之后所有的包括自身都是不可用的,但再次使用可变引用抢占锁之后,该可变引用是可用的。

一切都由程序员控制,程序员可以在任意代码位置通过原始变量或引用来抢锁。

模式匹配

rust中可分为

  • 不可反驳的模式(irrefutable):一定会匹配成功,否则编译错误,如let赋值,for迭代,函数传参等。
  • 可反驳的的模式(refutable):可以匹配成功,也可以匹配失败,匹配失败的结果是不执行对应分支的代码,如if letwhile let

match匹配支持两个模式

  • 当明确给出分支的Pattern时,必须是可反驳模式,这些模式允许匹配失败
  • 使用_作为最后一个分支时,是不可反驳模式,它一定会匹配成功
  • 如果只有一个Pattern分支,则可以是不可反驳模式,也可以是可反驳模式

再谈Trait

组合

Trait最基本的作用是从多种类型中抽取出共性的属性或方法,主要表现为泛型数据类型。可以理解为,它描述了一种通用的功能,功能都要求具有某些特殊的行为,同时功能可以被很多种类型实现。

同样,一个类型也可以实现很多种Trait,组合出很多功能,这和一般面向对象编程语言的继承有所不同,不用继承冗余的功能,而更加的自由。

组合和继承的关系可以理解为 **has a **和 is a 的关系。

特征对象

也就是具有某个特征功能的类的实例。

上篇提到过,Duck Typing ,也就是只需要叫起来想鸭子,就可以当成鸭子来使用。(只需要你会打螺丝,不管你是不是大学生。)

这里的意思就是,实现了某个特征的众多对象都具有该功能,而由于 Trait 自身不能当作数据类型来用(因为,可能一种类型实现了很多中 Trait,显然无法用一种 Trait 来代替这种数据类型)。因此就诞生了 Trait Object,也就是将实现了 Trait A 的类型 B,C,D 当作 Trait ATrait Object使用。(可以类比为继承里的父类)

Trait object的创建是通过&dyn T或者指针Box<dyn T>Rc<dyn T>等等

本质就是由于 Trait object 的大小是不定的,所以选择将引用存在栈中,包含两部分数据

  • 指向数据的指针:指向实现了Trait的具体类型的实例,
  • 指向一个虚表 vtable 的指针:因为实现了 Trait 的类型有很多,而每个类型拥有的方法时各不相同的,所以需要一个虚表来区分保存。虚表中保存了实例可以调用的,实现的来自特征 Trait 的方法。当该对象调用方法时,直接从虚表中找到方法,然后调用。

其他

  • StructEnum类型需要手动实现Trait,即,使用#[derive()]
  • 特征是支持继承的,如
rust 复制代码
trait B{}
trait A: B{}

当类型想实现 Trait A 的时候,需要要求同时实现 Trait B

参考

Rust入门秘籍

相关推荐
LYFlied7 分钟前
【每日算法】LeetCode 64. 最小路径和(多维动态规划)
数据结构·算法·leetcode·动态规划
Source.Liu28 分钟前
【Rust】布尔类型详解
rust
清醒的土土土29 分钟前
Tokio 源码学习01——Mutex
rust
Salt_072834 分钟前
DAY44 简单 CNN
python·深度学习·神经网络·算法·机器学习·计算机视觉·cnn
货拉拉技术35 分钟前
AI拍货选车,开启拉货新体验
算法
MobotStone1 小时前
一夜蒸发1000亿美元后,Google用什么夺回AI王座
算法
Wang201220131 小时前
RNN和LSTM对比
人工智能·算法·架构
xueyongfu1 小时前
从Diffusion到VLA pi0(π0)
人工智能·算法·stable diffusion
永远睡不够的入1 小时前
快排(非递归)和归并的实现
数据结构·算法·深度优先
cheems95271 小时前
二叉树深搜算法练习(一)
数据结构·算法