0%

生命周期

Rust 的生命周期(lifetime)是一种编译时检查机制,它用来确保引用的有效性,防止悬垂指针和数据竞争等安全问题。生命周期在 Rust 中的作用非常关键,主要体现在以下几个方面:

生命周期注解:Rust 使用生命周期注解来标记引用的有效期。最常见的生命周期注解是 ‘a,这是一种显式标注,用来表明相关的引用必须至少与 ‘a 生存期一样长。
函数与方法中的生命周期:当函数或方法返回引用或使用引用作为参数时,必须显式指定生命周期。Rust 编译器(通过借用检查器)使用这些注解来确保数据的有效性。例如:

1
2
3
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() { x } else { y }
}

这里,’a 指明 x 和 y 的引用以及返回值的引用必须拥有相同的生命周期。
结构体中的生命周期:当结构体中含有引用时,也需要使用生命周期注解来确保引用不会在结构体实例存在时失效。例如:

1
2
3
struct Important<'a> {
part: &'a str,
}

这个结构体表明 part 的生命周期 ‘a 必须至少与结构体 Important 的实例一样长。
生命周期省略规则(Lifetime Elision Rules):Rust 有一套自动推导生命周期的规则,这可以在许多简单情况下省略显式的生命周期注解。例如,单个输入生命周期可以被自动推导,函数返回的引用通常被认为与某个输入引用具有相同的生命周期。
生命周期与泛型的结合:在泛型类型或函数中使用生命周期参数,可以使得类型或函数更加灵活与安全。例如:

1
2
3
struct Wrapper<'a, T> {
value: &'a T,
}

这里 T 是泛型类型,而 ‘a 是生命周期参数,表示 value 的引用至少与 Wrapper 实例持续相同的时间。
总的来说,Rust 的生命周期特性是为了在编译时进行严格的内存安全检查,通过这种方式帮助开发者写出更安全的代码,避免运行时错误。

总结

很久之前就学会rust并用于工作,所以这次训练营的rust部分对我来说是比较简单的。
期待这次训练营的实操,Rust 会给我一个怎样的编程体验。

总结

之前写过一遍rustlings, 所以这次前 100 题刷得比较快. 虽然说之前写过, 但是这次基本没有之前的 repo, 从头自己写了一遍, 还是学到了很多很多, 对Rust的各种特性也更加熟悉了.

个人觉得后面助教加的algorithm还是很有意思的. 大部分的Rust教程只有语法还有特性, 很少有关于如何写算法的. 特别是关于unsafe的使用, 大家好像都对于unsafe如临大敌, 都不敢用. 事实上, 要写出性能好的底层代码, 感觉unsafe还是必不可少的. 后面的图论也挺有趣的, 让同学们在写算法的同时, 熟悉了Rust的各种容器的使用. 就是希望提示能够再清楚明确一些吧.

Rust 程序设计语言能帮助你编写更快、更可靠的软件。在编程语言设计中,高层的工程学与底层的控制往往是难以兼得的;而 Rust 则试图挑战这一矛盾。通过平衡强大的技术能力与优秀的开发者体验,Rust 为你提供了控制底层细节(如内存使用)的选项,而无需承受通常与此类控制相关的所有繁琐细节。

Rust 适合那些渴望在编程语言中寻求速度与稳定性的开发者。对于速度来说,既是指 Rust 可以运行的多快,也是指编写 Rust 程序的速度。Rust 编译器的检查确保了增加功能和重构代码时的稳定性,这与那些缺乏这些检查的语言中脆弱的祖传代码形成了鲜明对比,开发者往往不敢去修改这些代码。通过追求零成本抽象(zero-cost abstractions)—— 将高级语言特性编译成底层代码,并且与手写的代码运行速度同样快。Rust 努力确保代码又安全又快速。

所有权规则
Rust 中的每一个值都有一个 所有者(owner)。
值在任一时刻有且只有一个所有者。
当所有者(变量)离开作用域,这个值将被丢弃

{                      // s 在这里无效,它尚未声明
    let s = "hello";   // 从此处起,s 是有效的

    // 使用 s
}                      // 此作用域已结束,s 不再有效


{
    let s = String::from("hello"); // 从此处起,s 是有效的

    // 使用 s
}                                  // 此作用域已结束,
                                   // s 不再有效

不变引用可以同时有多个(可以有多个读者)
不变引用存在的同时不能有可变引用(读者写者不能共存)
不能同时有多个可变引用(多个写者也不能共存)

字符串 slice(string slice)是 String 中一部分值的引用,它看起来像这样:

let s = String::from("hello world");

let hello = &s[0..5];
let world = &s[6..11];

不同于整个 String 的引用,hello 是一个部分 String 的引用,由一个额外的 [0..5] 部分指定。
可以使用一个由中括号中的 [starting_index..ending_index] 指定的 range 创建一个 slice,
其中 starting_index 是 slice 的第一个位置,ending_index 则是 slice 最后一个位置的后一个值

vector
如果遍历过程中需要更改变量的值:
fn main() {
let mut v = vec![100, 32, 57];
for i in &mut v {
*i += 50;
}
}

fn process(&mut self, message: Message) {
    // TODO: create a match expression to process the different message
    // variants
    // Remember: When passing a tuple as a function argument, you'll need
    // extra parentheses: fn function((t, u, p, l, e))
    match message {
        Message::ChangeColor(r, g, b) => self.change_color((r, g, b)),
        Message::Echo(String) => self.echo(String),
        Message::Move(Point) => self.move_position(Point),
        Message::Quit => self.quit(),
    }
}

“blue”.to_string()
字面量转化为字符串

fn trim_me(input: &str) -> String {
// TODO: Remove whitespace from both ends of a string!
String::from(input.trim())
}
fn compose_me(input: &str) -> String {
// TODO: Add “ world!” to the string! There’s multiple ways to do this!
input.to_string() + “ world!”
}
fn replace_me(input: &str) -> String {
// TODO: Replace “cars” in the string with “balloons”!
input.to_string().replace(“cars”, “balloons”)
}

fn main() {
string_slice(“blue”);
string(“red”.to_string());
string(String::from(“hi”));
string(“rust is fun!”.to_owned());
string(“nice weather”.into());
string(format!(“Interpolation {}”, “Station”));
string_slice(&String::from(“abc”)[0..1]);
string_slice(“ hello there “.trim());
string(“Happy Monday!”.to_string().replace(“Mon”, “Tues”));
string(“mY sHiFt KeY iS sTiCkY”.to_lowercase());
}

Option

fn main() {
let opt = Option::Some(“Hello”);
let opt2: Option<&str>= Option::None;
match opt2 {
Option::Some(something) => {
println!(“{}”, something);
},
Option::None => {
println!(“opt is nothing”);
}
}
}

if-let语法糖
let i = 0;
if let 0 = i {
println!(“zero”);
} else{
println!(“not zero”);
}

for fruit in fruit_kinds {
    // TODO: Insert new fruits if they are not already present in the
    // basket. Note that you are not allowed to put any type of fruit that's
    // already present!
    if !basket.contains_key(&fruit) {
        basket.insert(fruit, 1);
    }
}

for fruit in fruit_kinds {
    // TODO: Insert new fruits if they are not already present in the
    // basket. Note that you are not allowed to put any type of fruit that's
    // already present!
    // 查询Yellow对应的值,若不存在则插入新值
    basket.entry(fruit).or_insert(1);
}


    // Update the team 1 score
    let team_1 = scores.entry(team_1_name).or_insert(
        Team {
            goals_scored: 0,
            goals_conceded: 0,
        }
    );
    team_1.goals_scored += team_1_score;
    team_1.goals_conceded += team_2_score;
    // Update the team 2 score
    let team_2 = scores.entry(team_2_name.clone()).or_insert(
      Team {
        goals_scored: 0,
        goals_conceded: 0,
    });
    team_2.goals_scored += team_2_score;
    team_2.goals_conceded += team_1_score;

quiz2 就是将 string及其指令 转化的过程

for (string, command) in input.iter() {
    // TODO: Complete the function body. You can do it!
    let member = match command{
        Command::Uppercase => string.to_uppercase(),
        Command::Trim => string.trim().to_string(),//into()也可以,to_owned()也可以
        Command::Append(nums) => string.to_owned()+&"bar".repeat(*nums),//想寻找一个简单的写法,repeat就满足
    };
    output.push(member);
}

to_string(), into(), to_owned(), from()区别是什么
一般直接使用to_owned()就好,因为很直观合理

&str -> String

把数据从栈中复制到堆中,成为自己的数据

options2:调用 pop 方法时,它会从数组的末尾移除一个元素,并返回被移除的元素作为 Option。因此,在这个例子中,由于数组的类型是 Vec<Option>,所以 pop 方法返回的类型是 Option<Option>。外层的 Option 表示从数组中获取到的值,内层的 Option 表示数组中原本存储的 Option 类型的值。

fn main() {
let y: Option = Some(Point { x: 100, y: 200 });
match &y {
Some(p) => println!(“Co-ordinates are {},{} “, p.x, p.y),
_ => panic!(“no match!”),
}
y; // Fix without deleting this line.
}

match语句的所有权问题,编译器报错说最后一行的y的value已经被moved了,很明显是match使用后,离开作用域y就失效了。

其实 ? 就是一个宏,它的作用跟上面的 match 几乎一模一样:

let mut f = match f {
// 打开文件成功,将file句柄赋值给f
Ok(file) =>> file,
// 打开文件失败,将错误返回(向上传播)
Err(e) =>> return Err(e),
};
如果结果是 Ok(T),则把 T 赋值给 f,如果结果是 Err(E),则返回该错误,所以 ? 特别适合用来传播错误。

#[test]
fn item_quantity_is_an_invalid_number() {
    assert_eq!(
        total_cost("beep boop").unwrap_err().to_string(),
        "invalid digit found in string"
    );// unwrap_err()的意思是,将ok()或者Err()中的值取出来并报错
}

main函数默认没有返回值,或者说返回值类型是()

let x: i64 = s.parse().map_err(ParsePosNonzeroError::from_parseint)?;

parse().unwrap(),这将会导致如果s不能解析为整数i64,就会直接panic掉,而使用map_err对Err()进行一些修改,捕获原本的ParseInt类型错误,而将它转化成ParsePosNonzeroError::ParseInt

这行代码的意思是,如果 s.parse() 解析成功,则将解析后的整数值赋值给 x;如果解析失败,? 操作符会立即返回并将 ParseIntError 转换为 ParsePosNonzeroError::ParseInt 错误,并将其作为 parse_pos_nonzero 函数的返回结果。

在 Rust 中,? 操作符用于简化错误处理的过程。它只能在返回 Result 或 Option 的函数中使用。当使用 ? 操作符时,编译器会自动为你处理错误的传播。

具体到代码中,s.parse() 返回的是一个 Result<i64, ParseIntError>,该结果表示解析字符串为整数的过程。如果解析成功,返回 Ok 包含解析后的整数值;如果解析失败,则返回 Err 包含一个 ParseIntError 错误。

泛型是个好东西

特征Trait
特征跟接口的作用类似
特征trait中只定义共同行为,但是不描述具体行为的实现,具体行为的实现交给符合该特征的具体类型中来实现

可以同时调佣两个特征中的方法

Rust 生命周期机制是与所有权机制同等重要的资源管理机制。

之所以引入这个概念主要是应对复杂类型系统中资源管理的问题。

引用是对待复杂类型时必不可少的机制,毕竟复杂类型的数据不能被处理器轻易地复制和计算。

简单来说,程序员如果对生命周期判断不好,就会引发程序的隐藏问题,并且很难被发现。而rust在编译器层次实现了生命周期的检查。与之适配的,为了通过生命周期检查,写rust的时候有时候需要手动标注生命周期(其他语言和此前的rust都是编译器自动推导生命周期)。

生命周期主要是解决悬垂引用问题。可以对生命周期进行下总结:生命周期语法用来将函数的多个引用参数和返回值的作用域关联到一起,一旦关联到一起后,Rust 就拥有充分的信息来确保我们的操作是内存安全的。

如果你询问一个 Rust 资深开发:写 Rust 项目最需要掌握什么?相信迭代器往往就是答案之一。无论你是编程新手亦或是高手,实际上大概率都用过迭代器,虽然自己可能并没有意识到这一点:)

迭代器允许我们迭代一个连续的集合,例如数组、动态数组 Vec、HashMap 等,在此过程中,只需关心集合中的元素如何处理,而无需关心如何开始、如何结束、按照什么样的索引去访问等问题。

why?

去年秋冬季已经参加过一次训练营了,今年第二次参加,感觉和去年相比,今年的整体流程更加完善了,相应的训练题目也更加合理了。
本身是非科班出身的,一直以来有一个愿望就是弄清计算机是如何运行的,如何从一个个逻辑门到我们所看到的PC,尤其是在软硬件接口处,OS是软件中距离软硬件接口最近的部分,更像是软件领域的“大管家”,如果说编程是创造的过程,那OS就是编程世界的上帝,上帝为程序员准备好了一切支持,程序员才可以进行创造,所以,OS is charming!每个程序员都应该在创造的基础上更进一步,做自己的god。

Rust

没有最好的编程语言,也没有最好的编程模型,只有最适合的。编程语言的设计需要平衡两件事情:运行速度与抽象程度。越抽象的,越贴近人类思想的,运行速度大概率就越慢,极端情况就是脚本语言。越贴近计算机思想的,运行速度就越快,但是编写起来就越麻烦,极端情况就是二进制指令。
在编程的时候,我们不仅要考虑业务逻辑的实现,还要为自己所写的代码的稳定性和安全性负责,其中最常见的问题就是内存泄漏和并发问题,为了更加贴近人类思想,有些语言会加入GC垃圾处理机制,这种机制可以帮我们实现内存安全,但是代价就是速度,为了并发安全,Python中直接使用了GLI锁,代价也是速度。

C++是什么都没加,是够快,但是代价就是,程序员要负责一切,只有一些基础的安全保障。
Rust的目的就是,在尽量不影响速度的前提下,实现内存安全和并发安全。
但是实际上,有一些算法或者业务逻辑的实现,是不可能在Rust所定义的安全情况下完成的,针对这种情况,Rust提供了unsafe模块,在unsafe包裹的代码中,程序员有更大的自由度,但也不是绝对自由的。

所以,归根结底,想要做到写出的代码具有稳定性以及安全性,Rust只是为我们提供了一个更加完善的支持,但是我们还是要心中对于内存安全和并发安全有相关的概念,才能做到真正安全,GAMES101的闫令琪老师在课上说,API和知识是两码事,学习API不能替代学习知识,对于Rust我想也是这样的,学习Rust的各种语法设置以及安全设置这很重要,但是他们为什么这么设计,为什么这样能实现内存安全和并发安全,这才是更重要的。对于不得不自己实现unsafe代码的这种情景,我不知道是否符合抽象泄漏的定义,但是我想这种不得不亲自上手的感觉是差不多的。Rust就像是我们雇的一个大厨,跟着大厨做饭肯定色香味俱全,但是大厨不可能在任何时间任何地点都陪在我们身边,这种情况就要检验我们自己做饭的水平了。

That is all.

总结

去年有幸参加过2023年球季的操作系统训练营,当时学的较为粗糙,rust中的很多重要的概念都掌握的不是很深刻,经过这一年的学习,感觉自己对rust精进了不少,希望今年能顺利完成训练营。

第一阶段的学习让我对rust有了更深的理解,尤其是智能指针和生命周期的部分,这次的训练同样也是复习之前的内容,为接下来rcore的学习打好基础。

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

Motivation

刚好最近想要做一些OS相关的工作,又趁着OS训练营开营,于是赶着ddl把rustling干完了。在以前的学习中,常用C/C++,这次新学习了Rust语言,编程习惯还是有些不同的,要花大功夫去掌握难点。

学习过程

我主要使用了《Rust 程序设计语言》官方文档作为学习指南,并结合rustling进行知识点的学习和巩固。开始的基础语法较容易,很快就做完了,但是到后面的tests和algorithm部分,难度增大了,花费了不少时间才完成题目呜呜呜。
总体来说在第一阶段这个学习过程还是挺顺利的,因为网络平台上存在许多大量的资源供学习参考。

感想

在老师的悉心指导下和交流群互帮互助下,从最开始的 Hello World 起步,到深入系统地学习了操作系统的基本概念、原理,这使我对Rust和OS 有了更加全面和深入的理解。最后,衷心感谢两位教学讲师对我们的授课讲学。

展望

希望我能把三阶段给坚持下来吧~

Rust学习感悟

早在今年寒假时就打算学习Rust,奈何一直没有开工。四月份在群里偶然看到了2024春夏季开源操作系统训练营,这才算是开始了Rust学习之旅。

我的Rust入门学习主要是通过阅读和练习两点。

阅读其一是Rust 语言圣经

阅读其二是The Rust Programming Language

练习其一是Rust By Practice

练习其二则是训练营第一阶段的rustlings

  • Github: GitHub - rust-lang/rustlings: :crab: Small exercises to get you used to reading and writing Rust code!

  • intro ✅ 2024-04-09

  • variables ✅ 2024-04-09

  • functions ✅ 2024-04-11

  • if ✅ 2024-04-11

  • primitive_types ✅ 2024-04-11

  • vecs ✅ 2024-04-16

  • primitive_types ✅ 2024-04-16

  • move_semantics ✅ 2024-04-16

  • structs ✅ 2024-04-16

  • enums ✅ 2024-04-16

  • strings ✅ 2024-04-16

  • modules ✅ 2024-04-16

  • options ✅ 2024-04-16

  • hashmaps ✅ 2024-04-16

  • error_handing ✅ 2024-04-16

  • generics ✅ 2024-04-20

  • lifetimes ✅ 2024-04-20

  • traits ✅ 2024-04-20

  • iterators ✅ 2024-04-20

  • tests ✅ 2024-04-21

  • macros ✅ 2024-04-21

  • threads ✅ 2024-04-21

  • clippy ✅ 2024-04-21

  • conversions ✅ 2024-04-21

  • smart_pointers ✅ 2024-04-21

  • quiz ✅ 2024-04-21

  • algorithms ✅ 2024-04-22

在此过程中,Rust的所有权(Ownership)、生命周期(Lifetimes)、特型(Traits),以及函数式编程中常见的模式匹配(Patterns and Matching)等概念给我留下了深刻的印象。当然,最为深刻的还是Rust的编译器。

我认为在编写极为熟练的代码时,Rust严苛的编译器确实拖慢了我的编码速度(例如写个链表那叫一个费劲),但是从另一方面看,它尽可能地保证了我写出没有歧义的、不会在运行时错误的更健壮的代码。

总的来说,Rust的入门体验还不错,希望在接下来的学习中,我能更进一步地掌握它的特性,以及它在操作系统中内核级代码的应用。

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

rust 学习历程

以前是做 c 和 java 开发的,也使用过 js、python 等语言,无意中接触到 rust,被宣称的安全和性能吸引,资料东拼西凑学习,写各种小工具,因为概念很多,而且写程序编译器各种报错,差点被劝退,想着既然学习了,就再坚持下,接触 rust 一段时间,慢慢喜欢上了这门语言,编译器提示的很详细,而且只要编译成功后,程序的运行机会不会有问题,rust 是一门上线很高,下线也很低的一门语言,之前都是用 rust 写写小工具,都是在应用层面的开发,这次看到开源操作系统训练营报名,果断报名了,想去深入到新的领域,接触新的东西。

参加了训练营一阶段的培训,跟着老师的教学内容,动手实践,又有 rustlings 的练手,自己对 rust 的理解又更加进了一步,学习到了很多内容,和之前自己自学不太一样,这次又老师的讲解,很多疑惑一扫而光。在做 rustlings 实践中,前面的题都很轻松解决,到了算法的内容时,被卡住了很久,一个是算法很久没有接触了,这个在网上搜搜内容能很快熟悉起来,难得是 rust 的所有权机制以及指针这块,自己之前没怎么接触指针模块,总是卡在编译上,不断的查资料,最终磕磕绊绊总算搞完了,确实学到了很多内容,短短的三周时间过完了,觉得很充实。

总结

rust 现在是我喜欢的语言,到现在自己感觉还是没有入门,我一定会继续学习和使用这们语言工具,去拓展更多的领域空间。

基础阶段报告 yyh929

前言

RSICV架构是目前我接触最多的指令架构,学习过RISCV处理器的设计,但没有深入了解/写过RISCV操作系统,正好借训练营补足。

阶段总结

基础阶段主要是rust语法的学习。
之前了解到,Rust 以性能著称,可以在语言级别支持并发和并行编程,并且有严格的内存管理,适合编写对于内存安全和性能有很高的要求的程序。
对我而言,rust中的所有权,生命周期,智能指针,闭包,并发编程是比较新的概念。
基础阶段的学习还是挺精彩的,看着《Rust 程序设计语言 简体中文版》做完了rustlings,只有后面数据结构的题目比较有难度。
但是感觉做完全部题目还算不算真正入门rust,之后还会再输入研读一下《Rust语言圣经》。期待后面训练营的OS课。