0%

前言

对于Rust和OS,其实之前也只了解过Rust但是并未深入学习和研究,恰好开源操作系统训练营的开班,然后就积极报名参加,进入了交流群。同时,rust语言博大精深且与之前学习的语言也存在差异,作为从业者而言,每天并没有太多的时间去专注于该训练营开展的活动,但是每天都会尽可能的抽出相应的时间进行学习Rust和OS,为了去挖掘更多其未知的知识,丰富自我,达到更高一个层次。

学习总结感悟

总体来说在第一阶段这个学习过程还是挺顺利的,因为在报名之后,我已经着手学习Rust的相关知识,且网络平台上存在许多大量的资源供学习参考。整个大约一个月学习过程中,曾经碰到过许多的问题,在网上阅读过很多的文章寻找相关答案,也和有在加入的微信群交流探讨,总体感觉Rust还是一个正在发展中的语言,从开始接触到深入探究,就能发现Rust是一门年轻的且充满潜力的编程语言并且Rust语言同时也兼顾了安全性和高性能,因而它是一门非常出色的系统编程语言,能够编写操作系统、数据库、区块链应用等对安全、性能都要求极为严苛的系统级软件。
Rust语言同时是一门优秀的生产力语言,出色的文档、完善且高效的工具链(cargo,rustup),能够快速进行各个层次(小的嵌入式、大到操作系统,从底层系统开发到网络应用开发,均能胜任)的应用的开发。而且Rust社区也在快速发展,不断地创新,不断地摸索,学习者可以在社区中交流探讨中摩擦出了灿烂的火花。

Rust学习

学习历程

学习最开始查找的资料就是Rust语言社区,一般来说一门语言、框架或者库出现后,人们应用它们,不只是因为自身的强大,更是因为其背后的生态圈。这里有相应的且完整的代码、丰富的配套资源以及官方的指导文档,大家互相促进,在一个良好的氛围中交流学习Rust是种非常nice的感觉。

  • 语法:相对而言,Rust的语法是挺特别的。因为有生存周期和借用检查这类独有的概念,所以语法上看上去是比较复杂。但是作为一种现代编程语言,大量使用了泛型,但是也带来代码可读性较差的问题。这个问题也很让人感到苦恼的,需要有耐心且保持积极心态去学习、去摸索。
  • 特性:内存安全性、并发性强、高性能、易用性、跨平台性和社区活跃
  • exercise:边学边练才能达到更好的效果,前大半的练习题都差不多是基础,很容易做,我大概花了一天的时间做完了前100道题,还剩下最后十道的算法题,这还是有点难度在的,其实最终还是得靠牢牢掌握Rust的语法、数据结构以及代码逻辑分析等,还是能很快的解决掉最后的十道题。在这个过程中,Rust难上手很关键的一点就是其严格的编译器,编译器会做尽可能多的检查,而且也十分详细地发现问题,这样可以提早消除安全隐患。所以编译器像一个好老师,及早扼杀你的bug,强制性要求你写出可以正常运行的正确代码
  • note:学习过程中没有笔记是不完美的,所以整理了学习笔记, 记录日常学习的过程以及学习的内容,对于理解不清晰的模块和问题会仔细梳理代码逻辑,这是锻炼思维能力的一种方法,同时这对我来说可以将几节甚至是几章的内容进行串联起来,可以建立起更牢固的知识框架。

    学习氛围

  • 微信群里积极交流,提出问题,解决问题
  • 老师督促,学生活跃
  • 技术性的学术氛围浓厚
  • 目标一致

    学习成果

    在第一阶段的学习中,掌握了rust基础,学习到操作系统相关的概念以及原理

    展望

    Rust 是一门全新的语言,它会带给我们前所未有的体验,同时也能提升通用编程水平,甚至于赋予我们全新的编程思想。希望能够在第二阶段上继续保持,再接再厉

    致谢

    在老师的悉心指导下和交流群互帮互助下,从最开始的 Hello World 起步,到深入系统地学习了操作系统的基本概念、原理。这使我对Rust和OS 有了更加全面和深入的理解。最后,衷心感谢清华大学的老师们为我们提供了这次难得的学习机会,还有两位教学的讲师,对我们授课讲学。
    这次学习经历对我们来说,无疑是非常宝贵的财富,在以后走的更长远。

    分享连接~

    《Rust语言圣经》:https://course.rs/about-book.html
    《Rust程序设计语言》:https://rustwiki.org/zh-CN/book/
    《Rust指南》:https://tourofrust.com/
    《深入浅出rust》:https://zhuanlan.zhihu.com/rust-lang
    https://link.zhihu.com/?target=https%3A//www.gitbook.com/book/rustcc/rustprimer/details
    Rust 语言中文社区:https://rustcc.cn/

训练营第一阶段总结

1. 参加训练背景

​ 其实去年秋就已经知道了这件事情,不过当时是大二,觉得太难了些,当时就是图一个乐子,报了一个名,甚至连 github的仓库都没有打开过,去年可以说是完全没有用心的。没有那个时间和心力去坚持下去。

​ 今年能够重新来过,要走出舒适区,我觉得对于我来说是一个挑战,我本身并没有学过 C++,对于C++的一些浅显的了解仅限于用它打过算法,看过一点点 C++ 的书,甚至 Python 都没有学过,基本上看 Rust 一开始是让我感觉不适应的,这100道题目前80道算是自己摸索模索,大家讨论勉强能够写出,后面的线程,不安全的块unsafe,智能指针,闭包等,其实都是云里雾里,只能说是似是而非,感觉十分不扎实。

​ 不过令人庆幸的是,能够找到志同道合的队友,虽然我在我们的小队中一般是进度最慢的,另外两个队员给了我很多帮助,我有很多的疑问不解他们都会与我讨论,每天都有讨论的乐趣与激情,每晚10点30分就是到了今日学习总结分享的小队时间,这个时候大家的思想融汇贯通,是最能让人收获乐趣的。

​ 进度还是慢了很多,差不多一周半慢慢悠悠的混过去了第一阶段,遗憾是思维导图没有时间去做了,预计要拿出时间进行复盘。

​ 特此鸣谢两位志同道合的队友(theOnlyUniqueccaikano),有人共行是一大乐事

2. 参考书籍

​ 在学习 Rust 语言基础中,Rust 程序设计语言 - Rust 程序设计语言 中文版 (rustwiki.org)对我影响巨大,这本书力图教会我们从程序设计的思想上进行思考,考虑这段代码可能会出现的问题,以及他人看这段代码可能会出现的情况,在认真阅读过这本书之后,实在是令人受益匪浅。

3. 编译器报错

​ 编译器报错是成功之母!! 我写 C++ 都没有这么多报错!! Rust 编译器的报错真是让人想要吐槽,动不动就报错,不过令人欣慰的是,编译器的错误提示很精准,这令人感官十分良好,唯一麻烦的就是要逼着我去阅读编译器报错信息,这是程序员的基本功,但是还是很讨厌!

​ 在面对Rust 中的频繁报错时,尽量学会和编译器做朋友,他能够帮助你很多很多。

4. 资源分享

Tips: 嘿嘿嘿,就厚颜无耻的 借用一下 我队员的资源分享吧,你们偷偷看,把这个当成我的资源分享也是可以的 !!

1Rust 程序设计语言 - Rust 程序设计语言 中文版 (rustwiki.org)

2std - Rust (rustwiki.org)

3Rust 中文文档 | Rust 文档网 (rustwiki.org)

4关于本书 - Rust语言圣经(Rust Course)

5Rust入门第一课 - Rust入门秘籍 (junmajinlong.com)

6介绍 - Rust精选 (rustmagazine.github.io)

7简介 - 通过例子学 Rust 中文版 (rustwiki.org)

来源 : 2024春季开源操作系统训练营刘启东一阶段总结

CreatorHell LearningOS 第一阶段总结

智能指针

  • Box:这是一种在堆上分配内存并管理所有权的智能指针。它允许你在堆上创建数据,并且当你不再需要这些数据时,Box会自动释放它们所占用的内存。
  • Rc:这是一种用于引用计数类型的智能指针,它允许多个所有者同时拥有同一个数据。当最后一个所有者离开作用域时,Rc会自动减少引用计数,并在计数达到零时释放内存。
  • Arc:与Rc类似,Arc也是引用计数类型,但它是线程安全的,可以在多线程环境中使用。
  • Cow<’a, T>:这是一种写时克隆(Clone-on-Write)的智能指针,类似于虚拟内存管理的写时复制。它包含一个只读借用,如果调用方需要所有权做修改操作,它会克隆借用的数据。

    所有权

  • 所有权概念:在Rust中,每个值都有一个被称为其所有者(owner)的变量,且每个值有且只有一个所有者。这意味着当一个变量被创建时,它就被分配给了一个特定的所有者。
  • 作用域关联:值的生命周期与其所有者的作用域相关联。当所有者变量离开其作用域时,值将被清理,从而防止内存泄漏。
  • 移动语义:当值从一个作用域移动到另一个作用域时,原作用域将失去对该值的所有权。这个过程称为移动(move),它是Rust内存管理的关键部分,有助于减少不必要的数据复制。
  • 借用机制:虽然Rust的所有权原则保证了内存安全,但它也引入了借用机制来允许多个引用同时访问数据。借用分为可变借用和不可变借用,它们通过借用检查器来确保数据的并发安全。
  • 克隆与复制:对于实现了Clone trait的类型,可以通过调用clone方法来创建数据的副本。而对于实现了Copy trait的类型,数据会在赋值或作为参数传递时自动复制,这适用于那些可以安全地复制数据的情况。

    多线程

  • 线程创建:在 Rust 中,可以使用标准库中的 std::thread 模块来创建新的线程。通过 spawn 函数可以启动一个线程,该函数接受一个闭包(匿名函数),这个闭包将在新线程中运行。
  • 线程控制:创建的线程可以通过 join 方法等待其执行完毕。如果主线程结束而子线程还在运行,Rust 会触发 panic 以防止资源泄露。因此,通常需要调用 join 或 detach 来避免这种情况。
  • 线程安全:Rust 的所有权和借用机制有助于保证多线程环境下的内存安全。当需要在多个线程间共享数据时,可以使用 Arc, Mutex, RwLock 等智能指针来确保同一时间只有一个线程能够访问或修改数据。
  • 消息传递:对于需要线程间通信的场景,Rust 提供了消息传递机制,如 mpmc(Multiple Producer, Multiple Consumer)队列,允许多个生产者和消费者线程安全地交换信息。

前言

由于对操作系统的相当的感兴趣,很巧的是当时在网上不经意间看到了这个开源操作系统训练营的相关信息,就赶紧报名参加了,同时这也是我第一次参加这样的开源课程,也是我第一次很深入的来学习操作系统的相关知识 ,对Rust也不是很了解,在报名之后就开始查找相关的资料学习了点Rust,去学习一个不熟悉的语言和框架,其实对我而言是非常有挑战性的一次学习旅程。

第一阶段学习总结

总体来说在第一阶段这个学习过程还是有点痛苦的,因为学习Rust的过程中我需要查阅许多相关的资料去了解相关知识点,同时,在学习的过程也会出现许多前所未有的问题,这就需要找方法解决,开始了一种循环模式。不过慢慢的能掌握,同时做练习也更能帮助我理解,总体还是较为顺利的!

Rust的学习

学习历程

rust语言学习起来还是挺困难的,难度比较高,我是通过《Rust语言圣经》这本书边学边看,还同时参考了《Rust程序设计语言》、Rust 语言备忘单等,遇到不会的看书再者看网上的教学边看边学,Rust虽然难度高但是从性能上和从安全性上都是非常不错的,优势也很突出!就比如rust最具特色的就是他的内存所有权机制。一块内存只能有一个所有者。当遇到和函数的参数传递的时候,所有权便会转移。rust中没有Null这个关键字,有的只是Option枚举类型。这就是其他语言所不具备的特性。
其实这个阶段主要还是作业练习这一块占有很大的比重,刚开始都是较为简单的语法题目,很好上手,越到后面的难度就逐渐往上升,特别是test和algorithm这一块,需要好好掌握rust的基础语法和rust实现数据结构算法等相关知识,将其灵活巧妙的运用起来。
在学习过程中整理了学习笔记, 对于理解不清晰的模块和问题会仔细梳理代码逻辑直到过程清晰,这对我来说是个非常好的掌握方法,可以将一些几节甚至是几章的内容进行串联起来,对前面的内容进行快速的梳理。

学习氛围

交流群内大家互帮互助,起到了很好的交流作用
方向一致,共同进步
技术技巧性的学术氛围浓厚
师生交流,积极沟通

学习成果

在第一阶段的学习中,我掌握了rust基础,逐渐学习操作系统相关的知识。通过和群友的交流分析,我也展开了对更多其它知识点的思考,在进一步讲在学习更多Rust关于智能指针和并行方面内容后,关于Rust的安全性保障和性能保障这两方面的疑惑又加深了。Rust在尽可能为安全保驾护航,它也做得非常好,但确实有时为了保障安全需要牺牲性能,这也许是不可避免地,Rust在尽可能做到最好。

展望

Rust 是一门全新的语言,它会带给我们前所未有的体验,同时也能提升通用编程水平,甚至于赋予我们全新的编程思想。我希望能够坚持完成第二阶段的学习,完善Rust的知识体系,深入了解操作系统带来的魅力。

致谢

首先十分感谢陈渝,向勇老师提供的这次参与夏令营的机会,其次是非常感谢给我们第一阶段讲课的两位讲师,认真负责并且提供帮助,我也成功的完成了第一阶段的学习。

参考资料

《Rust语言圣经》:https://course.rs/about-book.html
《Rust程序设计语言》:https://rustwiki.org/zh-CN/book/
《Rust指南》:https://tourofrust.com/
Rust 语言备忘单:https://cheats.rs/
Rust教程:https://www.youtube.com/
Rust 语言中文社区:https://rustcc.cn/

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

写在前面

今年三月初的时候被推荐了这个训练营,当时只抱着功利的目的想着参加这个训练营提升一下自己的能力,也丰富一下自己的履历,但是在学习的过程中逐渐发现了rust语言和操作系统的魅力,不知不觉也坚持做完了第一阶段

对于刚参加训练营的我来说,无论是Rust,Risv-v,还是OS,彼时都是一个较为陌生的概念,虽然本科是计科但是自己掌握的知识其实很有限,而且OS是这学期才开的课所以也没什么基础,每天看着群里大佬们的聊天都感觉自己大学是不是白学了,也是在那个时候下定决心要认认真真的学习

emm…不管怎么样,在复习考研,准备公司面试,进实验室打工的种种压力之下,我还是糊里糊涂地开始了自己的学习历程,并且有幸能够坚持下来

学习内容

主要参考的学习资料:关于本书 - Rust语言圣经(Rust Course) 以及 网上各路大佬的博客

印象比较深刻的内容:

Rust的对象所有权机制,生命周期,Option和trait等概念的引入,宏编程,unsafe机制,智能指针,多线程

尤其是智能指针的概念,让我对对象内存分配机制,以及Rust的安全机制和Java中的GC机制有了更深的理解

Rustlings前面的100道题总体来说比较简单,根据参考资料和编译器的错误提示基本都能解决出来,有几个题目有点小疑问的通过大佬们写的博客也能够顺利解决

最后10道题目应该是难度比较大,在这个地方卡了一段时间,不过后面看了一些资料之后也尝试着自己做了出来,只不过用了不少unsafe😔,以后有时间的话希望能把rust的知识再深入巩固一下,看看这部分的题目有没有更好的做法

最后,在一阶段到二阶段这个期间,希望自己能够根据大佬们的提示赶快把指令集架构和rCore的基本知识先看一下吧

学习心得 后续

虽然做完了rustlings的110道题目,但是我深知我自己对rust的理解只能说是迈出了第一步,如果离开了chatGPT的帮助和网上大佬写的博客我可能rustlings还要做好久吧😔

以及后续趁着自己有时间赶快把OS和Risv-v的知识再巩固一下

希望自己能把第二阶段rCore的内容跟下来吧

2024-04-27

在RUST中文社区看到这个活动,从组队到因停电用手机参加开营仪式,到每周一三五参加线上课程学习。

因之前用写过rust程序,有一定基础,但写rustlings还是有一定难度,对泛型、生命周期以及智能指针
还有许多知识要加强

rust相关的问题

rust标准库中重名方法太多,要熟悉常用的数据类型的方法

实现堆栈

以前没实现过堆栈,对此没有概念,加上用rust写堆栈繁琐,这方面要多补充

完成第一阶段期间用到的一些链接

API文档


Table of Contents

  1. rustlings
    1. conversions
      1. as
      2. frominto
      3. fromstr
      4. tryfrominto
      5. asrefmut
    2. unsafe
      1. modify by address
      2. raw pointer to box
    3. build.rs
    4. extern
    5. 算法

rustlings

conversions

as

fn average(values: &[f64]) -> f64 {
    let total = values.iter().sum::<f64>();
    total / values.len() as f64
}

frominto

struct Person{}


impl From<&str> for Person {
    fn from(s: &str) -> Person {
    }
}
// then you can use

let p = Person::from("Mark,20");

fromstr

//可以自定义错误
impl FromStr for Person {
    type Err = ParsePersonError;
    fn from_str(s: &str) -> Result<Person, Self::Err> {
    }
}

tryfrominto

// 与from类似,但可以定义转换错误处理
// Tuple implementation
// https://doc.rust-lang.org/std/convert/trait.TryFrom.html


// Tuple implementation
impl TryFrom<(i16, i16, i16)> for Color {
    type Error = IntoColorError;
    fn try_from(tuple: (i16, i16, i16)) -> Result<Self, Self::Error> {
        let red = u8::try_from(tuple.0).map_err(|e| IntoColorError::IntConversion)?;
        let green = u8::try_from(tuple.1).map_err(|e| IntoColorError::IntConversion)?;
        let blue = u8::try_from(tuple.2).map_err(|e| IntoColorError::IntConversion)?;

        Ok(Color { red, green, blue })
    }
}

//可见,从i16->u8 可以使用 u8::try_from(i16)

asrefmut

https://doc.rust-lang.org/std/convert/trait.AsRef.html

unsafe

modify by address

  1. raw pointer to ref

    pub struct Adapter<T: Driver>(T);
    
    impl<T: Driver> driver::DriverOps for Adapter<T> {
    type RegType = bindings::pci_driver;
    
    unsafe fn register(
            reg: *mut bindings::pci_driver,
    ) -> Result {
            // SAFETY: By the safety requirements of this function (defined in the trait definition),
            // `reg` is non-null and valid.
            let pdrv: &mut bindings::pci_driver = unsafe { &mut *reg };
    
            pdrv.name = name.as_char_ptr();
    
            //...
    }
    }
//linux/rust/kernel/net.rs

unsafe extern "C" fn get_stats64_callback(
        netdev: *mut bindings::net_device,
        storage: *mut bindings::rtnl_link_stats64,
) {
        // SAFETY: The C API guarantees that `net_device` isn't released while this function is running.
        let dev = unsafe { Device::from_ptr(netdev) };
}


impl Device {
/// # Safety
///
/// The caller must ensure that `ptr` is valid and remains valid for the lifetime of the
/// returned [`Device`] instance.
pub(crate) unsafe fn from_ptr<'a>(ptr: *const bindings::net_device) -> &'a Device {
        // SAFETY: The safety requirements guarantee the validity of the dereference, while the
        // `Device` type being transparent makes the cast ok.
        unsafe { &*ptr.cast() }
}
}
  1. ref to raw pointer

    //linux/rust/kernel/net.rs
    
    impl<T: NapiPoller> NapiAdapter<T> {
    /// Creates a new Napi object.
    pub fn add_weight(dev: &Device, weight: i32) -> Result<Pin<UniqueArc<Napi>>> {
            let mut napi = Pin::from(UniqueArc::try_new(Napi::new())?);
    
            unsafe {
            bindings::netif_napi_add_weight(
                    &*dev as *const Device as *mut bindings::net_device,
                    napi.as_mut().0.get(),
                    Some(Self::poll_callback),
                    weight,
            )
            }
            Ok(napi)
    }
    }
  2. &[u8] to core::ffi::cchar

#[cfg_attr(not(CONFIG_PRINTK), allow(unused_variables))]
unsafe fn printk(&self, klevel: &[u8], msg: fmt::Arguments<'_>) {
        // SAFETY: `klevel` is null-terminated and one of the kernel constants. `self.raw_device`
        // is valid because `self` is valid. The "%pA" format string expects a pointer to
        // `fmt::Arguments`, which is what we're passing as the last argument.
        #[cfg(CONFIG_PRINTK)]
        unsafe {
        bindings::_dev_printk(
                klevel as *const _ as *const core::ffi::c_char,
                self.raw_device(),
                c_str!("%pA").as_char_ptr(),
                &msg as *const _ as *const core::ffi::c_void,
        )
        };
}

raw pointer to box

/// # Safety
///
/// The `ptr` must contain an owned box of `Foo`.
unsafe fn raw_pointer_to_box(ptr: *mut Foo) -> Box<Foo> {
    // SAFETY: The `ptr` contains an owned box of `Foo` by contract. We
    // simply reconstruct the box from that pointer.
    let mut ret: Box<Foo> = unsafe { Box::from_raw(ptr) };
    ret
}

build.rs

set env

let timestamp = std::time::SystemTime::now()
    .duration_since(std::time::UNIX_EPOCH)
    .unwrap()
    .as_secs(); // What's the use of this timestamp here?
let your_command = format!(
    "rustc-env=TEST_FOO={}",
    timestamp
);
println!("cargo:{}", your_command);

set feature

// In tests8, we should enable "pass" feature to make the
// testcase return early. Fill in the command to tell
// Cargo about that.
let your_command = "rustc-cfg=feature=\"pass\"";
println!("cargo:{}", your_command);

extern

//提供ABI接口

mod Foo {
    // No `extern` equals `extern "Rust"`.
    #[no_mangle]
    fn my_demo_function(a: u32) -> u32 {
        a
    }
    #[no_mangle]
    fn my_demo_function_alias(a: u32) -> u32{
        my_demo_function(a)
    }
}

//使用ABI

extern "Rust" {
    fn my_demo_function(a: u32) -> u32;
    fn my_demo_function_alias(a: u32) -> u32;
}

算法

二叉搜索树

https://www.hello-algo.com/chapter_tree/binary_search_tree/

**

阶段一 Rust复习

因为本科期间用 Rust2021 比用c++20更多,所以对Rust的基本用法和tricks比较熟。所以最近时间更多用来做毕设。快到一阶段DDL时开始做 rustlings. 和 quiz.

  1. as_mut, as_ptr, as_ref, 以及rust2021引入的as_deref ,源码虽然都是一行,但是还是要注意很多细节的:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
    pub const unsafe fn as_mut<'a>(&mut self) -> &'a mut T {
unsafe { &mut *self.as_ptr() }

}
pub const unsafe fn as_ref<'a>(&self) -> &'a T {
unsafe { &*self.as_ptr().cast_const() }
// 实际上就是:
// unsafe {&*self.as_ptr() as *const T}
}

pub const fn as_ptr(self) -> *mut T {
self.pointer as *mut T
}

/// 该函数是Rust中是builtin的,命名为`cast_const`,它用于在不改变类型的情况下改变常量性(constness)。 该函数接受一个指向类型`T`的常量指针`self`作为输入,并返回一个指向类型`T`的常量指针。 它不会在代码重构时默默地改变类型。此外,虽然在大多数情况下,`*mut T`类型可以自动转换为`*const T`类型,但为了与`*const T`上的`cast_mut`函数保持对称性,所以该函数仍然需要(GPT)。 该函数是`const`函数,可以在编译时常量上下文中被评估,也是`inline`函数,总是会被内联到调用处。 该函数的返回类型是`*const T`,其中`T`是函数接受的指针类型`self`所指向的类型。
pub const fn cast_const(self) -> *const T {
self as _
}
  1. 重新复习了 Rust 的异步原语和基本的 tokio runtime.

  2. 对于 智能指针的 leak 方法,之前用的还挺少的。

  3. 参考 dbg! 写了一个 func_name, 使用它来做一些debug,在 no_std 下可能不能正常使用(还没试,有空到二阶段再

1
2
3
4
5
6
7
8
9
10
11
#[macro_export]
macro_rules! func_name {
() => {{
fn f() {}
fn type_name_of<T>(_: T) -> &'static str {
core::any::type_name::<T>()
}
let name = type_name_of(f);
name.strip_suffix("::f").unwrap()
}};
}
  1. 对lifetime的理解一直都不是那么到位。这次稍微注意了一下T: 'static, &'static T的差异

  2. Rust 的 FP 还是很舒服的。

  3. rustlings的 algo1.rs 那道题,没有想到用 safe 的方式怎么实现。那道题里面都是 unsafe, 还在卡顿中。而且我用了 NonNull::read_volatile, 很危险。

没有太多写的总结。一方面是不是很有空,另一方面是这算是一个复习。

二阶段会记录更多的东西。之前之做过 xv6 的labs,因为是时间问题,没有做 JYY OSLab 和 PA我一直觉得很遗憾。作为一个物理学学生,肯定是希望能在二阶段学到更多有趣的东西的。

第二次刷rustlings了,由于这次不用一章一章读《rust程序设计语言》,前面的题刷的比较快。但在后面一部分题慢了下来,主要原因是一些概念,要么现在记不清了,要么本来就没理解透,需要重新看资料。例如:迭代器好像就用了较长时间;第100题虽然参考微信群的提示和题目说明答了上来,但对外部块的引用机制还不是太明白。后面10道算法题主要参考了网上一些算法描述以及rust或其它语言的算法实现,再自己进行修改。从中了解了这些算法的原理,也可以看到别人实现时巧妙的地方。我觉得本次训练营这10道题添加的特别好。
我通过这次刷题,对rust语言的认识进一步加深,尤其是所有权机制。

从公众号文章中看到这次rust OS 夏令营活动, 抱着开阔眼界, 多认识同行的目的, 于是报名参加了。

第一阶段是学习rust 基本语法, 作业是使用rustlings 做110 道题。

我是两年前学习的rust 语言, 这两年也基本上在用rust 做项目,语言基础这块还是比较有信心的。但是做这些题也并没有像砍瓜切菜般,刷刷刷的一晚上搞定,花了两晚上做完100道基础题,又花了两晚上昨晚10道算法题。

主要的原因呢,是这些题目是很全面的,除了future async await 之外,关于rust 的各个方面都涉及到了。
比如 unsafe, 我可以说,写项目快两年,我没有写过一行unsafe,做题的过程也全面复习了一下rust 语言,这也算收获之一吧。

类似的, 我比较少用的方面包括, 自己实现trait, 自己实现macro,

记录一下我遇到的一些比较有趣的问题,或许对新学者有点帮助。很多是群里大家讨论的问题。

statge one, rustlings

重点和非重点(对初学者,应用层)

我学习用到的书是 << programming rust >>,现在有第二版了。

全部基于个人感受,和项目中使用的频率。非重点不代表不重要,只是说,如果你做的项目是偏向应用层的,这些内容需要理解,但很少会自己实现。如果是底层相关的,或者开发rust 库,这些可能会是重点,且常用。

重点:
+ 枚举和表达式, 我常常复习的章节, 真的很常用
+ 迭代器,不用迭代器也能实现各种各样的功能, 但是这样的C/C++ 风格不推荐。迭代器真的很有用,也很好用
+ 闭包,可能很多人第一感觉是很少会用到,但是出人意料的是,闭包还算比较常用。像迭代器一样,rust 推荐使用闭包,对闭包也有专门的性能优化
+ 错误处理,这个是rust 不同与其他很多语言的一个地方,也是很多人不理解的地方。初步处理时可以不用像库作者一样严格,结合枚举和闭包,也可以妥善处理。进阶的处理办法,需要稍微研究一下anyhow 和 thiserror 库。不要 unwrap 了之(重要的话手动乘以三遍)
+ 所有权和生命周期,老生长谈的重点,我就不说了,理解就好

非重点:
+ macro, macro 属于高级技巧, 应用层代码基本不会有自己手写macro 的需求,最多会用别人写好的。对于web 框架这种情形,很多人更喜欢函数实现的库,而非宏实现的库。对于有些不得不使用宏的场景,如DSL,再去学习研究就好了,早期没必要在这儿花很多时间
+ unsafe, 对于写OS 这种场景,unsafe 属于是必备技能了,但是对于普通的应用层,基本用不到。
+ 操作符重载, 知道就好了, 反正我没碰到过一次需要我手写操作符重载的

Copy and Clone

这个问题是群里大家讨论的时候提到的。Copy 是所有权转移的一种例外,实现Copy trait的类型, 赋值和传参数时, 会隐式复制。
参考 << programming rust >> page 71, 4.3 章节, page 236, 13.4 章节。
只说结论, Copy trait 是一种标记特型, 从代码上看 Clone trait 是其父特型,但是这并不意味着需要调用copy 方法的地方,
内部在调用clone 方法。copy 使用的仍然是内存中的按位复制方法。这两个特型之间的关系应该是一种逻辑关系,即可copy 的对象一定都是可clone 的。

unwrap ?

初学者(包括我)都会简单粗暴的使用unwrap,但是写了足够多项目代码之后,才终于明白了unwrap 是啥,到底应该怎么用。
我是在经历过上线的程序突然挂掉,集成第三方库总是莫名其妙的报错之后, debug 到怀疑人生之后, 才终于意识到这个问题的。
+ 结论就是,不要用unwrap, 除非你已经检查过了,能够完全确定这个unwrap 不会报错,然后让你的程序直接挂在这儿。
unwrap 是程序员对rust 程序的一种承诺,我已经检查过了,程序你就大胆的往下执行吧,出错了我也不会怪编译器,不会去问rust 不是号称现代,安全的编程语言吗,为啥会莫名奇妙挂掉了。
初学者往往会滥用unwrap, 在不知道自己已经做出了上述这些承诺的情况下。
函数中如果使用了unwrap, 会有一个标记trait, 标记此函数为非 Infallible, 这样在集成某些第三方库时,由于第三方库接口要求,而导致我们实现的函数不满足第三方trait 的要求,从而导致编译失败。
解决unwrap 滥用的一种常用办法是,使用watch, ? 或map_err 等方法,处理掉每一个 Result/Option 类型。

match expresion too deep nested

使用match 处理 Result 和 Option 类型是常见的操作,但是问题在于这种处理多嵌套了一层,
一不小心就会陷入多重分类讨论,层层嵌套的问题,看不清代码逻辑主干。下面是我应对的一种办法:
  • 一种方式

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    // algorithm4.rs
    fn search(&self, value: T) -> bool {
    match &self.value {
    x if *x == value => { return true; },
    x if *x > value => {
    match &self.left {
    None => false,
    Some(left) => {
    left.search(value)
    }
    }
    },
    x => {
    match &self.right {
    None => false,
    Some(right) => {
    right.search(value)
    }
    }
    }
    }
    }
  • 另一种方式

1
2
3
4
5
6
7
8
9
10
11
12
13
// algorithm4.rs
fn search(&self, value: T) -> bool {
match (&self.value, &self.left, &self.right) {
(root_val, _, _) if *root_val == value => { true },
(root_val, Some(left), _) if value < *root_val => {
left.search(value)
},
(root_val, _, Some(right)) if *root_val < value => {
right.search(value)
},
(_,_,_) => false
}
}