0%

开端

原是学安全的,在学习二进制的时候对底层产生了兴趣,看到了这个训练营,刚好学习新的语言的同时,还能巩固操作系统知识。此外鼠鼠快找工作了,希望简历上也能多一个说的过去的项目。

rust start

第一次接触rust,之前都是用C/C++居多。

首先是进行了一系列的语法学习。英文不太好,啃起来比较吃力,所以主要还是看的中文资料。

然后就开始做 rustling。前面的题,还是相对容易的,到后期理解起来还是有些吃力,经常编译通不过。(解决方法就是多看报错,以及求助人工智能)。最后10题是一些基本数据结构题,并没有想象的那么难。

和C++相比Rust的包管理简直太方便啦。之前听闻rust,说它又安全又快,实际学下来,所有权、借用和生命周期之类的概念,确实能很好的防止内存安全问题。而且没有C++那种臃肿(但看起来不太实用)的感觉。确实值得学习。(但是貌似现在工作岗位还不多?)

虽然通过了所有的题,但是感觉并没有完全的熟悉rust开发,还需要持续的写代码和学习。

未来工作

由于还在忙别的事,因而基本上隔几天以后集中突击一下,希望之后能够平均的分配一下时间。每天都学一点。每天都写日志好好的总结。rust需要持续学习,多多使用。抓紧学习RISC-V。紧跟第二阶段进度。

作案动机

期望对操作系统内核有一些基本的了解

第一阶段总结

主要是rust语言的学习

Rust的宏比C的宏友善太多了,C就好比是老师傅的工具箱,而宏就是老师傅的独门绝技,属于是将简单的功能玩出花来。在C中使用宏一般是对一些漏点做弥补,其中就不乏很多Hack的写法,这些写法我也喜欢写,它也着实能解决实际的问题,但也只限于写起来爽,后续的维护和升级都要付出巨大代价。我这一阶段忙也就是因为手上的项目有大量宏相关的内容要更改。在Rust中,部分C需要宏实现的功能被原生支持,虽然Rust中宏也有跟C宏替换所导致的同样的问题,但总体上来说是好上不少的。

RISC-V

虽然有过写RISC-V汇编的经历,基本上是C到汇编的codegen模块,但也是东拼西凑没怎么系统化的,这是我暴露出的一大漏洞。目前对于RISCV体系架构处在一个能领略但不精的状态,希望能在后续随着实验好起来。

2024春开源操作系统训练营

第一阶段总结

引言

很早之前就听说过rust,但是一直没有机会专门去学习(其实是懒哈哈哈),通过朋友了解到thu的这个开源os训练营项目是基于rust的,一下子就来了兴趣,正好最近在学习操作系统的知识,干脆就一起学了吧!就当是给自己的一个鞭策啦!虽然做的比较慢,但是很庆幸自己还是把rust的基础语法过了一遍,但是一些细节可能需要在实践中深入学习掌握。下面简单介绍一下学习的过程。

小白的日常记录 比较简略了哈哈哈

参考资料

群里有很多人已经给出了学习的参考资料,这里推荐一下我用到的:

  • Rust程序设计语言
    课程就是按照这个进度来讲的,rustlings也基本是这个顺序,所以我主要看的就是这个
  • Rust语言圣经
    很火的Rust语言学习资料
  • 通过例子学Ruts
    demo比较多,注释也很详细
  • 大语言模型(ChatGPT, Kimi, …)
    4202年了,相信AI能帮你在入门阶段解决很多问题!

学习总结

有其他的高级语言的基础,Rust的基础语法学起来其实挺快,本人平时使用C/CPP和Python较多,所以在学习Rust的时候下意识会对比类比这些语言的特性来学习。
Rust总体给我的感觉还是比较惊喜的,它确实省去了程序员的很多心思。C/CPP是那种给予了程序员足够的自由,什么都让程序员自己来管,但出了事也得是自己哼哧哼哧debug,比如我指针乱指都能编译通过(点头哈哈哈),自由但费神;而rust直接在编译阶段就给我喊停了,程序员必须符合它的要求来code,语法上做了限制,但是同样的,也就意味着更安全。

  • Cargo是rust的一个比较大的特色,比起cpp中使用cmake来进行包管理,cargo在我看来要更轻松一些。
  • 内存安全:Borrow和所有权系统
  • 并发:std::thread和std::sync::mpsc,避免了常见的并发bug
  • 模式匹配:match、if let和while let
  • 错误处理:Result和Option
  • 生命周期:表达引用的有效范围
  • 宏系统和各种类型:像枚举和各种trait

anyway,总之我觉得rust是个蛮不错的现代语言,来日方长,一步一步接触学习。

个人情况

目前是大三,未来打算搞系统,目前的话,比较钟意的是OS领域,,因为系统的其他方面,就是并行与分布式这块还行,其他的都过于小众了(bushi),所以当时找到了这个训练营,感觉非常的贴合,就报名了。

For Rust

不得不感慨,Rust确实难绷。。我可能也知道了一点Rust为何以安全性著称了,,,,太复杂了,各种约束和检查,让人想死。。

俗话说得好,C是运行时想跳楼,Rust是编译时想撞墙。。。

最大的体验是在写数据结构和算法的10道题。。。思路都很简单,然后常常为了语法错误而搞半天。。再次感受到了C的不负责态度。。

这10道算法题如果用c,大概就是几分钟一道。。但是在这,平均一道都是半小时往上,原因还是在于对其语法的不熟悉。。天天给我报编译错误,我真的麻了,但是熟悉了之后,发现哦原来是这样。

同时Rust的很多特性确实也让我感觉到了其为何安全(代价就是编码的程序猿会死),不过总得来说,确实是一门非常特别的语言。

一阶段学习收获

印象深刻的几个点:

首先是在牵扯build.rs来构建环境变量的时候,可能是我的环境的问题还是怎么着的,基本可以确定是已经实现了,但是仍然不过,而且貌似评测的有bug,会莫名的通过,然后又不通过,很迷茫。

然后是算法题的调了老长时间,,,,没有实现Copy特征,,这个问题真的难受,,泛型的使用真的需要再多做练习和学习。后面看了很多别人的博客,,发现把Copy特征提高一级,改成Clone特征的约束,然后再需要传递的地方改成.clone()就过了,,,难绷

rust的很多的stl和模块化编程也非常的强大,让我看到了强大的解耦合能力和组件化的能力,可以根据编译选项和参数的限制来实现条件编译,在项目实战的时候能带来很多的便利。

Rusiling

确实是折磨人的好题。。。太折磨了

因为题目太多了,所以就不放上做题或者说学习过程中的笔记了,只做一个总结。

留在最后

最后的话,想对未来的自己push一下,五一马上要到了,不打算出去了,直接图书馆走起,越学越觉得自己菜,越学越觉得需要干的事越多,如果运气好的话(希望,,,但是真渺茫)能在9月解放,大概率是奋斗到12月份了

目标的话是完成训练营的三个阶段吧。

第一阶段 Rustlings 总结

第一阶段做的事就是 70-80% 刷 rustlings,剩余时间复习Rust语法知识,翻看Std Doc 和复习了算法知识/(ㄒoㄒ)/~~,总是会忘记细节😂
其中

参考资料

做题中值得注意的点有以下方面:
  • String主题的练习中,&str和String类型的互相转换
  • Iterator中许多方法使得代码更加简洁,如fold, map, map_of, filter等,官方文档续多看
  • 智能指针这块也值得注意,RC, Aec RefCell等
  • 除了以上基础语法方面的内容外,通过手动实现链表, 双向链表, 堆栈, 深度优先和广度优先算法, 排序算法等增进对Rust的进一步理解

第二阶段 rCore

第二阶段从操作系统的发展历史的循序渐进的讲解操作系统的开发,并逐渐加入文件系统、进程。

在这几章的学习内容中,本阶段较为不熟悉的是文件系统这快内容,通过不断研究文档,熟悉代码,研读V3文档,逐渐对rCore加深了认识。

想起一句流传甚广的话”源码面前了无秘密”。

共勉!!!

第三阶段 R4L驱动和跨内核驱动框架设计与实现

本阶段主要时熟悉了R4L, 并在此基础上实现了Print模块、Misc设备驱动模块、树莓派GPIO驱动模块等的开发。
对Rust在Linux体系的驱动开发加深了进一步的认识。
在此基础上熟悉了跨内核驱动框架的设计与实现。
值得说明的是以上皆基于Qemu进行操作。

艰难的Rust学习之旅

自己是在偶然的情况下,在一个计算机体系结构群里发现了这个训练营,当时一看到这个训练营还以为是谁混进来打得一个广告,点开仔细一看,发现
并不是像我想的那样,而是一个非常正规的训练营,而且还是我感兴趣的方向——OS,我立马下定决心,一定要参加,我做到了,然后……就是痛苦的
rust学习之旅了:(
rust和我以前学的语言都不是很相似,这种感觉当我发现rust中变量默认不可变时候就一直缠绕在我的心中,但是这还只是rust众多特性中最好理解
的一个(悲),所有权问题和生命周期问题曾今困扰我很久(虽然现在也不是特别懂……),小小总结一下所有权和生命周期吧:
所有权:是为了解决内存申请与释放问题而采取的一种有别于其他编程语言更热衷使用GC和手动管理机制的特殊管理机制,通过编译器在编译时候
根据一系列预设规则进行检查来确保内存的安全。而这一系列规则中最重要的一点规则是:
Rust中每个值都只能被一个变量所拥有,而当所有者或者说变量离开作用范围时,这个值将被抛弃(drop)

生命周期:生命周期简而言之就是引用的有效作用域,主要作用是为了避免垂悬引用,而生命周期的标注主要是为了帮助编译器进行借用检查的分析
需要特别注意的是,生命周期的标注并不会改变任何引用的实际作用域
rust 中其他的几个非常重要的概念有:智能指针,迭代器,Unsafe,这几个概念也是非常的令我头疼,rustlings做到相关的习题时,老是卡个半天,
但是这些都是rust学习中必须要掌握的重点知识,所以我也是花了相当多的精力在于编译器斗智斗勇中(
rustlings 最后10道算法题也是把我折腾的够呛,写的时候压根就无法关注到它的复杂度,能通过测试就很不容易了:( , 而且后面几道题需要频繁
调用标准库中的方法,我也是找个半天,看来以后还是得多用才能熟悉的写啊
但是不管怎样艰难,我最终还是坚持写完了110题,rust虽然不能说学会了,但是至少入门了吧,那现在就让我继续向操作系统进军吧,加油!

学习rust感觉最难的还是所有权机制和每次在前面要加上的标识,
还有各种API,初学很难记住
以后还得多复习rust

前言

之前听说并且尝试过rust,但是,因为其陡峭的学习曲线,第一次学习失败!在这第一次的时候连蒙带混地走了一遍rustlings,但是依旧对很多语法细节不理解。

对于risc-v,因为他的开源性质,对他很感兴趣,赞同他是指令集中的Linux。并且其简单优雅的设计,让我不必忍受x86的各种复杂和历史包袱也能入门学习OS。

对于OS,之前只是看过理论教材,但是对于具体的实现并不清楚,想通过一门实践课程来丰富自己的实践经验,让理解落地。我认为,学习理解了操作系统之后,计算机便祛魅了很多(除了硬件层面),思考问题可以一眼想到底能够让设计更加灵活,思维更加创新。

过程

这次再次学习rust,重点去理解了生命周期的概念,主要是通过官方的the rust programming language以及查阅std docs来学习。

rust有很多现代语言的特性,并且融合了很多函数式语言的特点,之前热衷于lisp语言,因此对于rust这种做法有好感。

但是,尽管做完了rustlings,因为缺乏更多的工程实践,我认为自己的rust灵活运用能力仍然欠妥,对于rust的所有权机制仍然会有所不适应,仍然以c的眼光来思考编程。

此外,我觉得rust有些过于繁琐,这种保证内存安全的方式真的是最好的吗?对于我来说,这种限制极大影响了我目前的编程思路(可能来自c的编程思路本身就是不内存安全的),
以及其语法,我觉得有些丑陋, 每每看到生命周期就会头昏眼花, 在这里希望zig语言早日能发布1.0(跑

第一阶段做完之后的几周,因为课业问题,迟迟没有开始第二阶段的实践,希望自己能够在此后的过程中调整好自己的时间安排,不慌不乱,有条不紊,最终完成整个训练营。

最后

这次参加本次训练营,有很大的偶然成分,偶然间从某个微信公众号看到消息,但惧于高校的威名,一开始我是害怕报名的(对未知事物本能的惧怕),但是看到开源的字号,想着开源的含义,open to everyone,
以前一直在屏幕后作为观察着的我,一个热爱自由软件,开放共享精神的学生,应该踏出实践的一步,从这样一个课程开始,参与到知识共享的实践中去了(从受益者到贡献者)。

而且,我也很期待自己能够写出自己的一个OS,他可能不够完善,唯一的优点就是能跑,但当我有了这个技术资本,就可以参与到其他的项目(如:Linux)中去了。
也希望能够在这样一个项目中结识到志同道合的朋友,找到属于自己的圈子,提高自己的社交能力。

目录

前言

本着学习 Rust 和 OS 底层相关的想法加入了这一届的训练营。

  • 关于 Rust: 对 Rust 这个语言之前仅仅是略有耳闻, 听说学习曲线陡峭, 也一直没真的了解一下, 所以借此机会学习并了解一下。
  • 关于 OS: 一个方面是虽然写了几年代码, 但是其实对 OS 了解不多, 浅浅的知道一些模糊概念, 另外是对 thu 的 ucore 早有耳闻, 而这个 rcore 同出一源, 因此决定借此机会来学习一下。

由于第一阶段并不涉及 OS 的部分, 因此主要是从 Rust 的学习角度来总结一下第一阶段的内容。

第一阶段使用 rustlings 为评价标准, 提供提示、参考教程、直播讲解、线上答疑等方式组合推进。 我本人没有参与太多直播讲解和线上答疑,主要使用 hint 提示和相关文档、LLM 完成了第一阶段的回答。 因此主要从三个角度来分析所学:

  1. 所有权与类型系统: 这是我觉得 Rust 与其他语言最为迥异的地方, 所以在这里简单阐述一下我的理解。
  2. 做题感受: 对 Rustlings 和 训练营一阶段的一些感受。
  3. 个人反思: 对自己参与一阶段训练营和学习 Rust 的一些反思。

所有权与类型系统

Rust 不进行自动的 GC(内存回收), 也不需要开发者手动释放内存, 而是通过所有权来管理内存, 这是一个非常有趣的特点。 其次, RUst 有着与 Java、 C++、 Python等不同的类型系统, 这一点不仅仅体现在一个字符串就会有 String, str, &String 等多个类型上, 也体现在 sturctimplwhere 上。

所有权

所有权是指当前变量值所占据的内存, 归哪个变量所管理(这一句是个人总结)。 这也就意味着, 当一个变量丧失了对值的拥有, 它就不再能使用这个值。

与 Java 和 Swift 使用的引用计数不同, 引用计数需要管理复杂的引用关系, 同一个值可能被非常多的变量所引用, 这导致内存在很多时候难以回收, 甚至在某些时候无法判断 OOM(堆内存溢出) 的具体位置。 –这个是本人亲身经历的一次问题。

虽然由于同一时刻一个值只能有一个所有者这一个概念及编程环境的实际需要带来了很多的额外的复杂内容, 比如借用引用, 可变借用不可变借用等等。 但是不可否认的是, 这让开发者更深刻的了解了堆、栈等程序等基础概念, 也有利于开发者从思维上管理自己程序的运行时。

类型系统

类型系统第一次感觉奇特是在于 structimpl 的分离。 与常见语言中每个 class 与他的 method 耦合在一起不同, Rust 中方法是对 struct 使用 impl 构建的。

其次是对于 Stringstr 的类型, 让人区分堆与栈, 又或者说, 当明白了一个结构体会被分布在堆还是栈上的时候, 也就不会再纠结于到底是 String 还是 str 了。

做题感受

由于 Rustlings 主要是通过做题来驱动, 因此通过第一阶段训练营意味着完成所有的 Rustlings 练习题。 这样操作的好处是, 可以了解 Rust 的基本语法和编译器, 学会阅读 Rust 的文档和教程等内容。 因此夏令营的学习还是依赖于自我的驱动(当然, 学习编程可能没有那么多的技术热情, 但是确实应该学会自我驱动来跟上新的技术, 跟上时代的脚步)。

但是 Rustlings 存在一个很大的弊端, 似乎是通过对应名称的文件能否顺利执行来判断是否通过的, 也就是可以通过修改不应该修改的代码来使得练习通过。 在练习一开始的时候, 我按照编程习惯和编译器提示懵懵懂懂的过了十来道题, 中间可能修改了 assetequals 等内容。 同理, 如果只是想速通、进入第二阶段等, 完全有很多稀奇古怪的方法可以在不完成解题的情况下进行。

而要解题, 其实也并不需要一定看教学视频(当然这里的含义并非视频或直播不好), 而是 Rust 的编译器可以完成一些基础的提示, 其次是通过 hintReadme 和 代码中的注释可以获取到很多关于对应知识点的信息, 能完成接近于 90% 的问题, 因此靠自己也同样能完成很好的 Rust 学习, 并不需要等待直播开启。

当然, 依然有约 10% 的问题可能难以解决, 这时候利用诸如 ChatGPT、 ChatGLM、 BaichuanAI 等 LLM 可以解决剩下的9% 的问题。 通过提示词优化可以解决几乎 Rustlings 中 100% 的问题。

因此总结下来, 第一阶段使用 Rustlings 来作为评价标准, 是因为 Rustlings 更多的是一个强自驱的学习过程, 需要对自己的学习质量和学习内容有着一定的自我要求。

个人反思

尽管在几天内完成了全部的 Rustlings, 比不过训练营当天秒通的那些人, 但是鉴于我本人并没有 Rust 基础, 所以倒也没什么气馁或难受。

在学习过程中还是对 Rust 有了一点点浅薄的了解, 对 Rust 的代码不再是一知半解或者完全看不懂的情况, 这是一阶段学习后最欣慰的事情。

但是 Rustlings 并不能算是一个全面的 Rust 学习指南, 更多的是一个学习入门的指引, 对于很多的概念比如智能指针过程宏都是浅尝辄止, 需要自行补充更多的学习内容来进行增强。

其次是对于 AI 的使用也会导致个人思维的怠惰, 对于部分题只要求解出来而忽视了解题的思路(比如算法题链表和堆)。 这种只能适用于一时的情况, 不利于长期的学习。

总结

整体总结下来, 训练营选择 Rustlings 还是一个非常不错的决定, 不像是传统课程一般强制直播那样干涸, 也不会是纯练习那样放飞。 而且也成功的体会到了 Rust 的好玩之处, 和他的陡峭艰难, 期待二阶段可以顺利的使用 Rust 写出 Rcore, 对自己交出一份满意的答卷。

花了大约一周的时间基本看完了rust圣经,两天刷完rustling,发现rust真是把各种安全考虑(内存,数据…)直接暴露给用户了。

😯rust三大支柱:

  • 没有垃圾回收的内存安全(内存安全)
  • 没有数据争用的并发(数据安全)
  • 没有开销的抽象(性能)

[1]😯无GC: 随时回收内存啊,这不c++ RAII么,NLL比RAII更强!

[2]😯所有权、借用:最有特色的一章。这是直接把读写锁加在语言层了是吧。

[3]生命周期:最难理解的一章。NLL还好理解,给参数和返回值手动标生命周期怎么突然魔幻起来了🥵,好在编译器会帮我们做标注。

[4]泛型、trait:最惊艳的一章。泛型?大伙都有啊;trait? 这不Java的接口(c++虚函数)么,但是确实优化掉了继承,难道这就是组合优于继承么🧐;

😯零开销抽象!!

组合:用”has-a”(有什么或用什么)去替代”is-a”(是什么)

零开销抽象:极度强调运行时性能,把所有解释抽象的工作都放在编译时

[5]函数式编程:这不所有权+借用版本的c++迭代器和lambda表达式么

[6]Box智能指针、循环引用:这不所有权+借用版本的c++ unique_ptr,shared_ptr,weak_ptr的么

[7]unsafe:c++裸指针,但仍然无法逃离所有权和借用

[8]多线程与并发:大伙都有,但是更严格了啊,还得时刻考虑数据的位置和所有权转移


1.变量解构

(a, mut b): (bool,bool)
1
2
3
4
5
6
7
8

2.数值

```61_f32```表示f32类型的61

3.as 转类型

例如```'a' as u8

4.range

和 ```1..
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

5.```字符串```通过硬编码,而```String类型```在堆上

6.申请堆内存后,通过```栈中的堆指针```进行访问

7.一个值只能拥有一个所有者

> **变量在离开作用域后,就自动释放其占用的内存**

> 可以多个常量引用,但是只能有一个可变引用
>
> 可变引用和常量引用不能同时存在
>
> 引用的作用域在最后一次使用后时结束(编译器的优化)

```rust
let s1 = String::from("hello");
let s2 = s1;
//s1将所有权移交s2

8.字符串String 和 &str

str是内置类型,String是标准库类型

&str字符串切片

中文3个字节

9.尽量使用迭代方式访问数组 使用下标访问每次有越界检查

i in &v{}```
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

和 for i in v{} 的区别?

> 如果不使用引用的话,所有权会被转移(move)到 `for` 语句块中,后面就无法再使用这个集合了:(

> 对于实现了 `copy` 特征的数组(例如 [i32; 10] )而言, `for item in arr` 并不会把 `arr` 的所有权转移,而是直接对其进行了拷贝,因此循环之后仍然可以使用 `arr` 。



10.模式匹配

```rust
match some_u8_value {
1 => println!("one"),
3 => println!("three"),
5 => println!("five"),
7 => println!("seven"),
_ => (),
}

() 表示返回单元类型与所有分支返回值的类型相同,所以当匹配到 _ 后,什么也不会发生。

11.函数参数的三种传入方式:转移所有权、可变借用、不可变借用

12.只要闭包捕获的类型都实现了Copy特征的话,这个闭包就会默认实现Copy特征。

1
2
3
4
5
6
7
8
9
10
11
12
// 拿所有权
let s = String::new();
let update_string = move || println!("{}", s);

exec(update_string);
// exec2(update_string); // 不能再用了

// 可变引用
let mut s = String::new();
let mut update_string = || s.push_str("hello");
exec(update_string);
// exec1(update_string); // 不能再用了

😎仅实现 FnOnce 特征的闭包在调用时会转移所有权

13.调用第一个参数为self的成员函数会移交所有权,实例不能再使用

1
fn takes_long_type(f: Box<dyn Fn() + Send + 'static>) {    // --snip-- }

14.数据内存布局

VecStringHashMap都是固定大小的类型,都是对堆上数据的引用,引用的大小是固定的,即栈上的引用类型是固定大小的

DST:不定长类型,在代码中直接使用 DST 类型,将无法通过编译。

Rust 中常见的 DST 类型有: str[T]dyn Trait它们都无法单独被使用,必须要通过引用或者 Box 来间接使用

dyn Trait表示它所指定的 Trait,确切的原始类型被删除,补全 Trait 对象指针所需的信息是 vtable 指针,被指向的对象的运行时的大小可以从 vtable 中动态地获取。

1
2
3
4
struct MySuperSliceable<T: ?Sized> {
info: u32,
data: T,
}

?Sized 是一个特殊的 trait bound,表示泛型类型 T 可以是非固定大小类型。

T 既可以是普通的固定大小类型(如 i32, u64),也可以是动态大小类型(如 str 或者 dyn Trait

#[repr(c)] 表示字段的顺序、大小和对齐方式与你在 C 或 C++ 中期望的完全一样。

通过 FFI (Foreign Function Interface,不同语言交互)边界的类型都应该有repr(C),因为 C 是编程世界的语言框架。

15.堆栈

  • 小型数据,在栈上的分配性能和读取性能都要比堆上高
  • 中型数据,栈上分配性能高,但是读取性能和堆上并无区别,因为无法利用寄存器或 CPU 高速缓存,最终还是要经过一次内存寻址
  • 大型数据,只建议在堆上分配和使用

栈的分配速度肯定比堆上快,但是读取速度往往取决于你的数据能不能放入寄存器或 CPU 高速缓存

将一个简单的值分配到堆上并没有太大的意义。将其分配在栈上,由于寄存器、CPU 缓存的原因,它的性能将更好.

16.智能指针往往都实现了 DerefDrop 特征

Box可当智能指针用

17.Deref

  • 把智能指针(比如在库中定义的,Box、Rc、Arc、Cow 等)从结构体脱壳为内部的引用类型,也就是转成结构体内部的 &v
  • 把多重&,例如 &&&&&&&v,归一成 &v
1
2
// 由于 String 实现了 Deref<Target=str>    
let owned = "Hello".to_string(); // str
  • T: Deref<Target=U>,可以将 &T 转换成 &U,也就是我们之前看到的例子
  • T: DerefMut<Target=U>,可以将 &mut T 转换成 &mut U
    • T: Deref<Target=U>,可以将 &mut T 转换成 &U

18.

Rc 只能用于同一线程内部,想要用于线程之间的对象共享,你需要使用 Arc

Arc 是线程安全的,Atomic Reference Count

Arc并不允许直接修改其中的数据,应该在Arc内部包装一个mutex


RefCell 实际上并没有解决可变引用和引用可以共存的问题,只是将报错从编译期推迟到运行时,从编译器错误变成了 panic 异常

borrow() borrow_mut()

  • RefCell 适用于编译期误报或者一个引用被在多处代码使用、修改以至于难于管理借用关系时

Cell对于实现了Copy的类型。如 Cell 使用.get()获取一个copy后的数据,使用.set()修改原来的数据 => 进而实现了可变借用和不可变借用同时存在。


Weak

使用Weak来解决循环引用导致的内存泄漏问题,也可直接使用unsafe 裸指针解决 裸指针没有所有权转移

通过RC创建Weak,或者直接创建Weak


NonNull [一个非空,协变的裸指针]

19.unsafe

创建原生指针是安全的行为,而解引用原生指针才是不安全的行为

unsafe代码块加在解引用裸指针的周围

将引用转化为裸指针是一种😎再借用

一旦开始使用裸指针,就要尝试着只使用它,不然就会突破rust的引用规则和栈借用规则。产生不好的后果

内部可变性

一个不可变引用 &UnsafeCell<T> 指向一个可以改变的数据,这就是内部可变性。

20.再借用
f

21.Pin

自引用最麻烦的就是创建引用的同时,值的所有权会被转移

1
2
3
4
5
6
7
8
9
10
11
12
13
14
struct SelfRef<'a> {
value: String,

// 该引用指向上面的value
pointer_to_value: &'a str,
}

fn main(){
let s = "aaa".to_string();
let v = SelfRef {
value: s, // 所有权转移
pointer_to_value: &s // 所有权已经被转移,不能再借用
};
}

22.线程安全

  • 实现Send的类型可以在线程间安全的传递其所有权
  • 实现Sync的类型可以在线程间安全的共享(通过引用)

23.常量可以在任意作用域进行定义,其生命周期贯穿整个程序的生命周期

Rust 要求必须使用unsafe语句块才能访问和修改static变量

lazy_static懒初始化静态变量,之前的静态变量都是在编译期初始化的,因此无法使用函数调用进行赋值,而lazy_static允许我们在运行期初始化静态变量!但是定义的静态变量都是不可变引用

Rust为我们提供了Box::leak方法,它可以将一个变量从内存中泄漏(听上去怪怪的,竟然做主动内存泄漏),然后将其变为'static生命周期,最终该变量将和程序活得一样久,因此可以赋值给全局静态变量CONFIG

24.unwarp通过unwarp() 取出Result<i32,ParseIntError>中的i32(潜在panic)

使用?方法后接收option或result时,遇到err,None直接返回

25.iterator

三种迭代器类型

IntoIter 类型迭代器的 next 方法会拿走被迭代值的所有权,IterMut 是可变借用, Iter 是不可变借用。

🥵26.链表

不能直接将head的所有权直接移交给新节点的next。take将head偷出,填入None,返回head

加泛型后head为Option<Box<Node>>


不使用as_ref会将head的所有权移入map函数内(map的函数签名为self,转移所有权),这样&node.elem就是返回map函数内的局部变量的引用。

使用as_ref将Option<Box<Node>> 转为 Option<&Box<Node>>解决问题

27.逆变协变

一个variable被拆分成‘读’跟‘写’,其中读的部分是协变,写的部分是逆变,可读可写的var则是不变

协变:需要动物的地方给一个猫

逆变:需要猫的地方给一个动物

不变:不能转换

rust中函数返回的是协变的,参数是逆变的

rust中生命周期和泛型T都有协变逆变性

*mut T 对于T是不变的 &’a mut T 对于T是不变的

引入NonNUll包裹T,这时T是协变的

在构建list时,使用*mut Node 会导致不能使用其协变性。用NonNull引入协变性

例子:

1
2
3
4
5
6
7
8
9
10
11
12
fn assign<T>(input: &mut T, val: T) {
*input = val;
}
fn main() {
let mut hello: &'static str = "hello";
{
let world = String::from("world");
assign(&mut hello, &world);
}
println!("{hello}");
}
// 这里传入assign的T为hello,hello是一个&'static str类型,即T是&'static str,它是不变的,不能用&'world str类型的val(第二个参数)来

NonNull<T> 是关于 T 协变的。这意味着如果你有一个 NonNull<T>,并且 UT 的子类型,那么你可以将 NonNull<T> 转换为 NonNull<U>。这种属性在泛型编程中非常有用,尤其是在处理继承或类型转换时。

子类型:

  1. 引用的子类型关系:在 Rust 中,引用 &T 是其指向的类型 T 的子类型。这意味着,如果有一个函数期望接受类型 T 的参数,你可以传递类型为 &T 的参数给它,而编译器会自动进行引用的解引用操作。这样的设计避免了数据的不必要的拷贝,并且使得代码更加灵活。
  2. 安全的子类型关系:在 Rust 中,如果一个类型 B 实现了 trait A,那么 B 就是 A 的子类型。这种子类型关系通过 trait 来实现。比如,如果类型 String 实现了 trait AsRef<str>,那么 String 就是 str 的子类型。这种关系允许你在期望 AsRef<str> 类型参数的地方传递 String 类型的值。
  3. Enum 的子类型关系:在 Rust 中,Enum 可以有多个变体(variants),这些变体可以拥有不同的数据类型。对于一个枚举类型来说,它的每个变体都可以被认为是它本身的子类型。这种设计在模式匹配和组合类型时非常有用。

28.module

当使用 mod 语句导入模块时,Rust 自动 为它创建一个模块命名空间

mod logging; 可以使用logging::xxx访问该module下的内容

extern

1.调用外部代码

1
2
3
extern "C" {
fn ctanf(z: Complex) -> Complex;
}

2.外部调用rust代码

1
pub extern "C" fn rustfn1() { }

29.多态

  • 泛型是一种 编译期多态 (Static Polymorphism),在编译一个泛型函数的时候,编译器会对于所有可能用到的类型进行实例化并对应生成一个版本的汇编代码,在编译期就能知道选取哪个版本并确定函数地址,这可能会导致生成的二进制文件体积较大;

  • 而 Trait 对象(也即上面提到的 dyn 语法)是一种 运行时多态 (Dynamic Polymorphism),需要在运行时查一种类似于 C++ 中的 虚表 (Virtual Table) 才能找到实际类型对于抽象接口实现的函数地址并进行调用,这样会带来一定的运行时开销,但是更为灵活。