0%

​ 一转眼110道题目已经结束,100道语法练习和10道算法题下来让我受益匪浅。对于此前只有一些c语言和python语言基础的我来说,难度比较大,很多概念还是理解不清楚,算法也是临时学的,可谓非常艰难。不过对rust的学习对我的编程思维很有启发,也是我第一次直接接触到并发和所有权的概念。

​ 此前编程单纯考虑能跑就行,但实际上是不够的,作为信息安全专业的人这几年写代码从不考虑安全实在是惭愧,所幸在学生时期接触到了rust,不至于在未来项目出现一些令人痛苦的问题后惊觉对底层知识的忽视。随着学习的深入,越发理解底层知识对编程的重要性,当然这也是我参加2024oscamp的原因。不罗嗦了,那么作为简单的记录,先总结一下吧!

:Rust 中有两种字符串类型:String&str

String 被存储为由字节组成的 vector(Vec<u8>),是一个有效的 UTF-8 序列。String 是堆分配的,可增长的,且不是零结尾的。&str 是一个总是指向有效 UTF-8 序列的切片(&[u8]),并可用来查看 String 的内容,就如同 &[T]Vec<T> 的全部或部分引用。

自定义的错误特征,错误之间很可能会存在上下级关系,例如标准库中的 std::io::Errorstd::error::Error,前者是 IO 相关的错误结构体,后者是一个最最通用的标准错误特征,同时前者实现了后者,因此 std::io::Error 可以转换为 std:error::Error

在存在多个引用时,编译器有时会无法自动推导生命周期,此时就需要我们手动去标注,通过为参数标注合适的生命周期来帮助编译器进行借用检查的分析。

生命周期标注并不会改变任何引用的实际作用域

标记的生命周期只是为了取悦编译器,让编译器不要难为我们

​ ——RUST语言圣经

生命周期注释有一个特别的:‘static 。所有用双引号包括的字符串常量所代表的精确数据类型都是 &’static str ,’static 所表示的生命周期从程序运行开始到程序运行结束。

map 方法是一个迭代者适配器,它最大的好处不仅在于可以就地实现迭代器中元素的处理,还在于可以捕获环境值。它是惰性的,不产生任何行为,因此我们还需要一个消费者适配器进行收尾:

消费者适配器是消费掉迭代器,然后返回一个值。那么迭代器适配器,顾名思义,会返回一个新的迭代器,这是实现链式方法调用的关键:v.iter().map().filter()...

filter() 的闭包需要用一个引用,并且许多迭代器迭代引用,所以这可能导致混乱的情况,其中闭包的类型是双引用:通常在参数上使用解构来去掉一个

1
2
let mut iter = a.iter().filter(|x| **x > 1);
let mut iter = a.iter().filter(|&x| *x > 1);

flat_map()

map 适配器非常有用,但仅当闭包参数产生值时才使用。 如果它产生一个迭代器,则存在一个额外的间接层。 flat_map() 将自行删除这个额外的层。

特性(trait)概念接近于 Java 中的接口(Interface),但两者不完全相同。特性与接口相同的地方在于它们都是一种行为规范,可以用于标识哪些类有哪些方法。

特性在 Rust 中用 trait 表示:

格式是:

1
impl <特性名> for <所实现的类型名>

Rust 同一个类可以实现多个特性,每个 impl 块只能实现一个。

  • Box<T>,可以将值分配到堆上

  • Rc<T>,引用计数类型,允许多所有权存在

  • Ref<T>RefMut<T>,允许将借用规则检查从编译期移动到运行期进行.写链表的时候再回忆一下……

  • AsRef 是一个 trait(特质),用于将一个类型转换为另一种类型的引用。

  • AsRef 的作用是允许我们以统一的方式处理不同类型之间的转换。通过实现 AsRef trait,我们可以定义一个类型转换函数,该函数将一个类型转换为另一个类型的引用。

  • 如果我们有一个类型 T,并且希望将其转换为类型 U 的引用,我们可以实现 AsRef <U> trait 来完成这个转换。在实现中,我们需要提供一个名为 as_ref 的方法,该方法返回类型 &U。这样,我们就可以使用 as_ref 方法来将 T 转换为 U 的引用。

fn num_sq<T: AsMut>(arg: &mut T)

AsMut< >可变引用。其他同上

Rust 宏(Macros)是一种在编译时生成代码的强大工具,它允许你在编写代码时创建自定义语法扩展。

1
2
3
4
5
6
7
macro_rules! my_macro {
// 模式匹配和展开
($arg:expr) => {
// 生成的代码
// 使用 $arg 来代替匹配到的表达式
};
}

还有很多没写,笔记记在其他地方了,不过太零散了,有空整理一下。

一、引言

本人参加2024春夏季开源操作系统训练营,主要基于对Rust编程语言的浓厚兴趣以及从事操作系统相关工作的背景。训练营的第一阶段聚焦Rust语言学习、习题实践以及算法研究,旨在深化对Rust与操作系统的理解,提升实际工作能力。经过这一阶段的学习,我收获颇丰,现对本次学习进行总结。

二、Rust语言深度探索

Rust以其内存安全性和高性能特性在操作系统开发领域受到广泛关注。在本阶段,我深入学习了Rust的核心概念,如所有权、借用检查器和生命周期管理等。这些特性确保了代码的安全性和可靠性,尤其在并发和内存管理方面表现优异。

通过编写简单的Rust程序,我逐渐掌握了语言的特性和风格。同时,我也浏览了Rust的标准库和生态系统,了解了其在并发编程、网络编程和系统级编程等方面的优势。

三、习题实践

为了巩固所学知识,我积极完成了训练营提供的Rust习题。这些习题涉及从基础语法到高级特性的各个方面,通过实践编程,我加深了对Rust语言的理解,并提高了编程技能。

四、算法

算法是操作系统设计中的关键部分,但是由于时间问题,我最后10个algorithms点到即止,后续在深入研究其思想。

五、Rust与操作系统的结合理解

通过本阶段的学习和实践,我深刻体会到了Rust在操作系统开发中的优势。Rust的内存安全性和并发性能使得它成为构建高效、稳定操作系统的理想选择。同时,Rust的丰富生态系统和活跃的社区也为开发者提供了强大的支持。

我也意识到,将Rust与操作系统知识相结合,不仅可以提升代码的质量和性能,还可以拓展操作系统的功能和特性。因此,在未来的学习和工作中,我将继续深化对Rust和操作系统的理解,探索更多结合两者的可能性。

六、总结与展望

本次训练营的第一阶段学习让我对Rust编程语言和有了更深入的理解。展望未来,我将继续深入学习Rust的高级特性和生态系统,探索更多与操作系统相关的应用场景。同时,我也将积极参与开源社区的建设,与同行交流学习,共同推动Rust在操作系统领域的发展。

最后,我要感谢训练营的组织者和导师们的辛勤付出和悉心指导。在未来的学习和实践中,我将继续努力,不断提升自己,为Rust和操作系统的结合做出更大的贡献。

总结

之前对rust有过了解和使用,所以做 rustlings 题目时主要以复习巩固为主,对之前没掌握好的细节再次通过文档等学了一遍,例如一些编译属性等等。最后10道算法题也比较考验对rust的理解,尽量控制unsafe的使用范围是其中挑战的地方。

同时翻看了一些risc-v的文档,单看文档理解不深,配合rCore-Tutorial-Book看效果更好一些

第一阶段的任务只是开胃小菜,希望后面第二阶段继续努力,全程follow完成所有的任务。

关于

首先非常感谢清华大学提供的平台,让我能够有机会参与到这么了不起的课程中。作为一个非科班的程序员,其实一直很想好好学习一下操作系统和编程原理这些底层的知识,好让自己的基础更加牢固。但由于各种原因,尝试多次都未能坚持下来,都是找几本书走马观花地看看,浅尝辄止。那天无意中在某个微信群看到有个朋友发的这个课程的推广链接,点进去了解了一下后毫不犹豫地报了名,因为既可以学习Rust,又可以学习操作系统。

其实在很早之前就对Rust和Go这两名现代化语言有所了解,也都初步学习过。但是由于Rust的语法较为复杂,上手困难,并没有在实际工作中使用。因此简单学习后就因为没有实践的机会而忘了个七七八八。

Rust

借着这次机会,又好好复习了一下Rust的基础知识。花了点时间仔细阅读了The Book和The Rustonomicon。这两本书确实是值得仔细阅读的,以后还得多读几遍。

关于Rust备受推崇的原因有个就是他的所有权机制和编译前解决大部分内存安全问题吧。其实C++的智能指针支持这种操作,因为日常会使用到C++,因此理解这部分概念的时候并没有遇到太大的困难。有一段时间一度怀疑过Rust是不是有点过于被吹捧了,因为相比于同样现代化、简单且高效的Go,Rust对初学者实在是太不友好了。

知道最近时间看到一些报道说Google使用了Rust替换C++之后,效率提高了两倍不止;而且Cloudflare也开源了他们的Pingorg,作为Nginx的代替。看来确实得好好学习一下这门语言了。

很喜欢Rust的一点是使用Trait实现面向对象的功能吧,相比于C++的Class,这样更简洁,也好理解。

不过接受不了的一点是学了之后才发现用Rust实现一个双向链表都如此费劲。因为日常使用C开发,最常用到的数据结构就是这个了。本来想这两天好好研究一下有没有好的双向链表的实现,看来Rust std里的实现,居然也使用了unsafe,觉得有点不够优雅。好吧,由于今天是提交总结的最后一天了,我还没想好好的实现,只好先草草地提交一个总结了。

最后

感觉其他同学真的很热情,第一阶段刚开始一天就把100题做完了,作为一个毕业五六年的人,不得不佩服。
立个Flag,希望自己下次不要提交的这么晚了。

1. 引言

本人参加2024春夏季开源操作系统训练营,主要基于对Rust编程语言的浓厚兴趣以及从事操作系统相关工作的背景。训练营的第一阶段聚焦Rust语言学习、习题实践以及算法研究,旨在深化对Rust与操作系统的理解,提升实际工作能力。经过这一阶段的学习,我收获颇丰,现对本次学习进行总结。

2. Rust语言的学习:

  1. 通过对 训练营中的 rust资源学习 比如 带有测验的文档 对rust语言的设计 拥有更深入的理解, 明白 rust语法以及规则上的设计道理,而非仅仅通过编译器的编译。
  2. 通过 项目的事件项目 rcore的rustling 对 rust语法规则更加熟练的掌握, 对于 unsafe 以及FFI 的使用有一定的掌握。
  3. 通过 训练营 中 徐老师的 直播视频讲解,对 rust 的生态,以及 Future,类型 变换(协变、逆变、不变) 以及一些其他的 冷门 rust知识 有更深入的理解。(感谢老师的付出

3. 总结与展望

总体来讲, 这些天的rust项目实践带来了不少的收获。本人自身对于OS也比较好奇和一定的知识储备, 希望能够在接下来的课程中通过rcore的项目实践,能够带来 更多对于rust以及OS的 深入理解。

第一阶段总结报告

我大概在一年前知道了开源操作系统训练营,但是因为种种原因没有参加。在今年4月的时候我看到2024春训练营,正好有时间就赶紧报名上车了。

学到的东西

虽然我学习过rust,但是在做rustlings时我还是学到很多东西。

  • Rust Docs
    这次解题的时候因为很多东西都需要自己去了解,这个时候我们就需要去看看相关rust文档,尤其是test7和test8中的那个build.rs。
  • Iterator
    rust的迭代器实在太强了,过去只是简单用一下。在这次rustling的拷打下,我边做题边翻rust的Iterator文档,学到了很多新的Method,多个Method组合在一起解题太舒服了。
  • Heap
    因为我本科没学过数据结构,所以做到第109题的时候发现自己竟然连堆这个数据结构都不知道,然后赶紧去学习Heap。
  • FFI
    我被test9这道题给卡了很久,看文档也不懂,一直对提示的2个attribute的用法不太明白,最后看了一篇关于这个问题的rust社区讨论才明白。
  • Codespace
    最开始我只是想要在上水课的时候用平板做一下题,然后就选择体验一下codespace。我使用了一下后发现体验还不错,就选择在codespace上做完了rustlings,这篇blog也是在codespace上写的。
  • Match Ref Mut
    我在做一道题的题的时候遇到了模式匹配的可变引用问题,被它卡了很久,最后看了下rust blogrust by example和编译器的帮助下解决了它。

总结

第一阶段学到了不少东西,希望自己后面能挺过第二阶段。顺便吐槽一下,后面10道算法题的测试用例有点弱和少,希望后面可以加强一下。

一阶段总结

没有太多可说,rustlings 中规中矩,最后新增的十道算法题有一种复古的感觉。期待二阶段的内容。

想法 rustlings, kotlin koans and others

类似这样的一连串渐进的练习组成的交互式教程,感觉没有直接看书那么有意思,有时甚至都懒得看代码就改完了。
速成也没有 learn x in y minutes 快。也许比较交互,减少代码恶意 :P

想法 stage-2: rcore os lab

提高 OS & RISC-V 知识水平。

  白熊的专业是微电子科学与工程,并不是传统计算机大类中的专业,但是看到朋友转发的操作系统训练营宣传后,想借这个机会熟悉一些关于操作系统的内容,增强自己对硬件驱动、指令集等(尤其是RISC-V)的了解。由于之前没有系统地学过任何计算机方面的知识,而且还是个freshman,基础可以说是相当薄弱,在第一阶段学习Rust的过程中,我也生出了不少傻头傻脑的见解,甚至被贺兰神评价“少见多怪”。不过经过一段时间的学习,我最终完成了第一阶段rustlings的所有题目。本文就写我初见Rust的一些想法咯~

对Rust的认知变化(做rustlings有感)

  在学习Rust之前,飞飞神有跟我普及过Rust,说它很安全,但写起来很麻烦(举了Rust变量所有权的例子)。当时我没太理解,但不明觉厉,很长一段时间里我认为Rust安全且牛X,是某种高贵而神秘(雾)的语言。

  参加训练营刚接触Rust半小时时,因为看到了很多熟悉的语法,我心里对Rust的期望似乎有所降低,觉得Rust不过是语法与其他语言略微不同……直到我看到了所有权、借用的概念,才真正感觉到这语言非同一般。可是rustlings中所有权一章的题目都挺简单的,没有为难人,这给了我一种“我已经完全了解所有权”的错觉……事实证明并非如此,在后续的题目中,随着代码复杂度的上升,经常会出现把一通报错改成另一通报错的情况,不过正是这些拷打,让我对这些新概念的理解慢慢扎实起来。

  rustlings中间做得还蛮快的,不过感觉我自己有点囫囵吞枣,有一种知识从光滑的大脑溜过的感觉……直到多线程那一块,我彻底卡住了,大脑宕机……后来想了想原因有二:一是对前面知识点根本没有理解透彻;二是我完全没有接触过多线程方面的知识(以前写代码都是傻乎乎单个过程)。最让我崩溃的是支持多线程的可变借用的题,第一次做的时候调来调去搞不明白,挺打击自信的……后来把Rust学习扔在一边,两周后重新拾起来,居然很快解决了,看来学习真的需要“沉淀”的过程。

  接着便没什么困难,直接杀完了基础的100题。算法题我感觉不算很难,毕竟都是很简单的数据结构。不过我认为我的答案不太简洁,可能(一定)包含很多没必要的操作(传值),或有优化的多的写法。归根到底还是自己对Rust的变量体系不熟悉,希望在日后的学习中,慢慢强化这方面,有些问题还是得交给时间(笑)。

一些展望

  正如开头所说,我基础很薄弱,不指望能完成整个训练营,所以便怀着“主打学习”的心态。希望在第二阶段中我能建立对操作系统的认知框架,并尝试将其与我自己的专业知识结合——说不定暑假可以亲手用数字电路实现处理器,跑操作系统,想一想都觉得很酷(前提是我得会)。

2024春季开源操作系统训练营第一阶段总结

作为一个非计算机科班的自动化人,对于计算机的学习有着很高的热情,在好友的推荐下,我了解到了开源操作系统训练营。刚好最近想要做一些相关的lab,就借此良机,加入到这个大的训练营家庭中和大家一起学习。在以前的学习中,常用C/C++,这次新学习了Rust语言,编程习惯还是有些不同(悲),要花大功夫去掌握难点。

学习过程:

  • 我主要使用了《Rust 程序设计语言》官方文档作为学习指南,并结合rustling进行知识点的学习和巩固。
  • 开始的基础语法较容易,很快就做完了,但是到后面的tests和algorithm部分,难度增大了,花费了不少时间才完成题目(难难难)。
  • 坚持就是胜利,每天都感觉有进步。
  • 主要难点部分在所有权系统、模式匹配、trait和生命周期,还需要多加联系。

总结与展望:

通过这20天的学习,我对Rust语言有了更深入的了解,也掌握了一些基本的编程技能。期待进入第二阶段的学习了。加油干小伙子!

Rust语法学习

这里将学习Rust中感到困惑的一些Rust语法进行整理

变量隐藏

1
2
3
4
5
6
7
8
9
10
11
12
#include<iostream>
using namespace std;

int main() {
int x = 5;
x = x + 1;
{
int x = x * 2;
printf("in for : %d\n", x);
}
printf("out of for : %d\n", x);
}

这段代码的输出结果是

1
2
in for : 0
out of for : 6

在 C++ 中,当你在 for 循环内部声明一个新的变量 x 并试图初始化它为 x * 2 时,这里的 x 实际上是指向新声明的 x,而不是外部作用域的 x。因为新声明的 x 在这个时候还没有被初始化,所以 x * 2 的结果是未定义的,但在大多数情况下,它会被初始化为 0。

这是因为在 C++ 中,新的变量 x 的声明和初始化是在同一条语句中完成的,所以在 x 的值被计算(即 x * 2)时,新的 x 已经遮蔽了外部作用域的 x

1
2
3
4
5
6
7
8
9
fn main() {
let x = 5;
let x = x + 1;
{
let x = x * 2;
println!("inner {x}");
}
println!("outter {x}");
}

而rust中的结果是

1
2
inner 12
outter 6

这段代码中的 x 变量在不同的作用域中有不同的值。在 Rust 中,可以在一个作用域中重新声明一个与外部作用域同名的变量,这被称为变量遮蔽(shadowing)。

首先,x 被初始化为 5。然后,x 被重新声明并赋值为 x + 1,所以 x 的值变为 6。

然后,进入一个新的作用域(由 {} 定义)。在这个作用域中,x 被重新声明并赋值为 x * 2,所以在这个作用域中,x 的值变为 12。这个值在 println!("inner {x}"); 语句中被打印出来。

当离开这个作用域时,我们回到了外部作用域,x 的值再次变为 6。这个值在 println!("outter {x}"); 语句中被打印出来。

字符串与所有权

结构体元组

成员相同但名称不同的元组不是同一种元组,不能相互赋值

生命周期的3个规则

生命周期约束

'a : 'b 表示 a >= b

子类型的生命周期

SubSuper 的子类型, Sub 的生命周期要包含 Super 的范围,有可能更大

目前Rust生命周期的子类型关系对于泛型存在三种映射

  • 如果 TU 的一个子类型意味着 F<T>F<U> 的一个子类型(即子类型化“通过(passes through)”),则 F<T>T 上是协变的(covariant)_。
  • 如果 UT 的一个子类型意味着 F<U>F<T> 的一个子类型,则 F<T>T 上是_逆变的(contravariant)_。
  • 其他情况下(即不能由参数类型的子类型化关系推导出此泛型的型变关系),F<T>T 上是的_不变的(invariant)_。

引用的生命周期

引用的生命周期从借用处开始,一直到最后一次使用的地方

再引用(ReBorrow), 将指针解引用后再引用的行为,如let ptr2 : Point = &*ptr1;

const泛型

无界生命周期

闭包

闭包中如果未声明参数类型那么一定要使用,否则编译器无法判断是什么类型

闭包根据参数在函数体内如何使用判断捕获参数的类型,可变还是不可变

使用move关键字强制获取所有权

智能指针

表现类似指针,同时拥有额外的元数据和功能,如String在内存中的分布

Box<T>

  • 当有一个在编译时未知大小的类型,而又想要在需要确切大小的上下文中使用这个类型值的时候
  • 当有大量数据并希望在确保数据不被拷贝的情况下转移所有权的时候
  • 当希望拥有一个值并只关心它的类型是否实现了特定trit而不是其具体类型的时候

    使用Box<T>创建递归类型

示例

1
2
3
4
5
6
enum List {
Cons(i32, List),
Nil,
}

let list = List::Cons(1, List::Cons(2, List::Cons(3, List::Nil)));

实现Deref以及函数和方法的隐式转换

通过std::mem::drop提早丢弃值

Rc<T>引用计数

Rc::new 创建

Rc::clone(&a),获取只读所有权

调用Rc::strong_count获取计数

RefCell<T>和内部可变性

对于引用Box<T>借用规则不可变性作用于编译时,对于RefCell<T>不可变性作用于运行时,如果违反则panic

原子计数引用Arc<T>

多线程中的Rc<T>