Lv777

Lv777

github
twitter
jike
playstation
xiaoyuzhou

所有权

蛋疼,在工作电脑上写了一半编程概念的笔记忘 push 了。用着 Obsidian 的 git 插件,又不喜欢自动备份,每次写完得记着手动同步.

一刷圣经到后面就意识到所有权是一系列看起来就复杂的设计的源头,当时都打算刷一半返回来再看几遍所有权了,当然最终还是选择先整体过完一遍。之前试着学过几次 Rust 基本都止步于所有权。现在二刷看起来就有点亲切感了.

所有权是什么#

对于写惯了高级语言 JS 的前端来说,除了面试前,平常基本不需要考虑内存管理,因为 JS 有 GC (Garbage Collection) - 面试常说的垃圾回收,隔一阵自动帮你扫一扫用不上的内存。传统低级语言如 C 语言,写的时候除了考虑逻辑实现,还得自己分配回收内存。而 Rust, 在玩一种很新的东西来管理内存 ➡ 所有权.

为什么需要所有权#

低级语言总得考虑性能和产物体积,GC 方便是方便,得跟程序一起跑,尽管引擎做了优化,但绕不过运行时的扫描和分析,算是牺牲性能提供方便1. 让开发者直接管理内存,容易因人出纰漏导致内存泄漏或空指针崩了. Rust 主打一手内存安全,靠的就是所有权。我理解所有权是一个抽象的约束,开发按特定模式写代码,方便编译器静态分析最终插入内存管理的内容。感觉管理内存了,但没经你手,没完全管。此外,所有权模型帮助开发更好的限制行为和理解代码,避免常见的错误,减少不必要的内存分配和复制提高性能.

堆和栈在这就不讨论了,这段帮我回忆了不少遗忘的知识.

怎么用#

照着编译报错改就完了🤪

复述一下规则:一个值 (字符,数字,对象..) 在一个时刻只能存在一个所有者 (变量), 最终所有者出了作用域值就被丢弃.

理解#

Rust 为了避免内存的二次释放引起的漏洞,对堆上的内存只允许一个指针指向,不允许类似 JS 浅拷贝的行为,对值的传递默认使用移动. 另外还可以使用消耗资源的 clone 实现深拷贝, 对于存在栈上的简单数据可以通过实现 Copy trait 做到快速复制无需考虑所有权.

容量#

讲 String 的内存分配时提到了其存储在栈上的内容,指针 ptr 不用说,lencapacity 分别代表长度和容量,在 Vec 文档中可以找到二者的区别:

Vector 的容量是分配给未来添加到 Vector 中的任何元素的空间量。不能与其 length 相混淆,后者指定了 Vector 中实际元素的数量。如果一个 Vector 的长度超过了它的容量,它的容量将自动增加,但它的元素将不得不被重新分配.
例如,一个容量为 10、长度为 0 的 Vector 是一个空的 Vector, 其空间可再容纳 10 个元素。把 10 个或更少的元素 push 进去不会改变其容量或导致重新分配。但是,如果 Vector 的长度增加到 11, 它将不得不重新分配,而且可能很慢。出于这个原因,建议尽可能使用 Vec::with_capacity 来指定 Vector 的预期大小.

引用和借用#

因为所有权机制导致想要复用一个值变得复杂,尤其当所有权传入函数时,想要复用就需要把所有权从函数里返回。可以使用 & 关键字创建值的引用来避免重复的传值 (所有权) 和返回,引用是一个指向值的指针,值本身的所有权不会被传递。创建引用的行为叫借用.

持有#

我编的概念 这好像是生命周期的概念啊

引用也分可变和不可变,还有额外的限制是如果持有了一个可变引用,就不能持有其他可变或不可变引用。持有就是从创建到最后一次使用中间的阶段,不管创建的先后,可变引用和其他引用持有的阶段都不能交错 (有一点点抽象). 这个限制是为了避免数据竞争,如果创建以后同一层级没再用就不存在数据竞争的问题,同理持有多个不可变引用也是允许的. Rust 对可变性的限制真不少.

let str = String::from("x");
let s1 = &mut str;
// 在中间这段一直持有 s1, 不能再有其他引用
println!("{s1}")

引用可能导致悬垂指针, 即引用还在,引用指向的内存已经不在或者不是引用创建时指向的那位. Rust 通过 [[生命周期]] 避免这种情况。感觉像在不停的打补丁,借用补所有权带来的不便,生命周期补借用带来的坑.

取引用和取地址#

Lv777: Rust 中 &v[0] 应该理解为是 v 的引用的索引 0 元素还是 v 的索引 0 元素的引用


Raycast AI: 在 Rust 中,&v[0] 表示对向量 v 的索引为 0 的元素的引用。具体来说,v 是一个向量,而 &v[0] 是一个指向该向量第一个元素的不可变引用。这个表达式的类型是 &T,其中 T 是向量中元素的类型。因此,你可以通过 *(&v[0])v[0] 来访问这个元素的值。


Lv777: & 关键字的优先级


Raycast AI: 在 Rust 中,& 符号有两种不同的用法,分别是取引用和取地址。它们的优先级是不同的。
取引用的优先级比取地址的优先级更高。这意味着,如果你写了类似于 &x.foo() 的代码,它会被解析为 &(x.foo()),而不是 (&x).foo()
如果你想要取一个变量的地址并将其转换为指针类型,你可以使用 as 关键字,它的优先级比 & 符号低。例如,&x as *const Tx 的地址转换为一个指向类型 T 的常量指针。


Lv777: 什么是取引用和取地址


Raycast AI: 在 Rust 中,& 符号有两种不同的用法,分别是取引用和取地址。
取引用是指使用 & 符号来创建一个指向某个值的不可变引用。不可变引用允许你读取值,但是不能修改它。例如,如果 x 是一个整数,那么 &x 将创建一个指向 x 的不可变引用。你可以使用 * 符号来解引用引用并访问值,例如 *(&x) 或者 x
取地址是指使用 & 符号来获取某个变量的内存地址。这通常用于将变量的地址传递给函数或者其他代码,以便它们可以访问变量的值。例如,如果 x 是一个整数,那么 &x 将返回 x 的内存地址。你可以将这个地址转换为指针类型,例如 &x as *const i32 或者 &x as *mut i32,并将其传递给其他代码。
需要注意的是,取引用和取地址的操作是不同的,它们返回的类型也不同。取引用返回一个指向值的引用,类型为 &T,其中 T 是值的类型。取地址返回一个指向变量的指针,类型为 *const T 或者 *mut T,其中 T 是变量的类型。

Slice#

这概念头一次刷的时候没注意,后面忘了哪章突然蹦出来给我整不会了.

Slice 是值的一部分引用,声明用 Range 指定起始和结束索引,起始索引是要截取的第一个位置,结束索引是截取的最后一个位置的后一个值.

let str = String::from("123456")
let sli = str[1..3]; // 23
let all = str[..]; // 起始索引为 0, 或结束索引为 len 则可以省略

对结束索引有疑惑,看看 why-is-slice-end-index-logic-as-it-is.

在 [[# 容量]] 里说过 String 在栈上存储的内容,指针指向堆上数据的起始位置,Slice 也类似,其指针指向创建 Slice 时声明的起始索引位置,没有 capacity, len 通过声明时的结束索引减去起始索引计算得出. Slice 是值的特殊不可变引用,也受 [[# 持有]] 的限制.

文档这里用 String 的使用场景引出 Slice 存在的便利性,确实不好描述。我一开始理解困难实际是把 JS 里对字符串的认知代入了,Rust 里的 String 不是标量,不能用简单的 Copy 思想去处理.

Footnotes#

  1. https://zackoverflow.dev/writing/unsafe-rust-vs-zig/

載入中......
此文章數據所有權由區塊鏈加密技術和智能合約保障僅歸創作者所有。