0%

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

我对于操作系统有一定兴趣,在被同学推荐了这个OS训练营后,便按步骤开始了训练,现在我将第一阶段任务完成,汇总为这篇第一阶段总结报告。

虽然以前用过其他编程语言,但由于之前从未接触过rust语言,在完成前100道题时几乎每一道题都被编译器疯狂拷打,实属让我回想起刚接触编程时的痛苦回忆了,但在历经100题的磨练后,我才能发现rust如此设计都是有一定理由的(虽然不是全部的特性都能理解到),后10道算法题在有rust基础后其实实现是比较简单的,虽然还会被编译器拷打。

总而言之,第一阶段是基础,便于我们后续阶段的展开,希望后续我能继续保持这份热情,完成后续阶段。

南京大学  吴奕胜

初见 Rust

训练营第一阶段的学习过程中,我初步掌握了一门新的编程语言 rust。这是我第一次接触到一门比较”新鲜”的编程语言,相较于课堂上学习的 C/C++,这无疑是一门更加现代也更加复杂的编程语言,拥有不少闻所未闻的新概念,新特性,当然,这也导致 rust 的学习并没有那么轻松。

余观夫 rust 之难,在”所有权”之义。入门阶段所以为惑者,所有权占十之八九。

我如何理解所有权?

C/C++ 中,一个变量名被映射到唯一一个内存中有实际含义的内容,反之不成立,我们可以使用指针很轻松地做到这一点。rust 所做的,正是保证这个”反之”的成立,将一块内容唯一对应于一个所有者,当所有者生命周期结束时,内存也被回收,这就保证了内存安全。

这样做的好处很明显,我们可以断言在满足所有权规则的前提下,内存的使用总是安全的;但是坏处也很明显,由于所有权机制的存在,不少在 C/C++ 中可以很简单地实现的数据结构或者函数,在 rust 可能需要费一番功夫。

把我们所学的 rust 的新特性做个分类,大致也就是两类:

  • 为了实现特定功能而实现的特性
  • 为了补充所有权机制的灵活性而存在的特性

这一点很好地体现在智能指针、unsafe rust 之类的概念里,虽然这可能造成很多麻烦,但长期来看终究是利大于弊,rust 严格得看起来有些偏执的编译器在教会我们,一个优秀的程序员应该在编程的过程中注意什么,不是吗?

令人耳目一新的枚举类型

初学 rust,其中的枚举类型让我赞叹不已。我之前只学习了 C/C++ 语言,并且实际上只是学习了 C 和部分的 C++C 中的枚举类型让程序员可以赋予某些值以现实的语义,但是并不好用。

在某个课程中,我们被要求实现一个游戏,游戏要求实现一个可以在地图中上下左右移动的人物,人物还需要可以执行诸如放炸弹之类的功能,部分动作可能有附带的属性。对于每一个动作,我们当然可以实现一个类,用以表示指令,但是这样做显然是不够简洁的,而用 rust 中的枚举功能,则可以优雅地实现。

再比如编译原理课程中,我们要实现类型检查,那么很自然的就是要实现一个结构体,用以表示各种类型。在这个结构体中,一个枚举类型用以表示当前类型,一个 union 中保存了该种类型附加的信息。即使使用到了 union 这样的关键字减少冗余的部分,也并不优雅,如果换成 rust,则可以用一个枚举类型实现类型系统,相当简洁明了,且优雅。

说到底,枚举类型好用还是其成员可以附带一些信息,这是一般的枚举做不到的,并且即使用结构体或类等实现了类似的功能,也远不如 rust 中枚举的实现优雅。

模式匹配——优雅版本的 switch

说模式匹配是优雅版本的 switch 其实也有失偏颇,毕竟模式匹配的功能要比后者多多了,但更多的时候(至少在入门学习阶段),主要的用法还是这两种:

  • 用于解构元组、枚举等等
  • 用于匹配特定的分支

先来说说 switch 让我感觉最不舒服的地方,就是每个分支后面需要加上一个 break,在编译原理课程实验中,对语法树进行语义分析的过程中,时常需要根据产生式的类型实现不同的功能,由于某些稀奇古怪的原因,在写代码的时候会漏掉 break,这会导致比漏掉 break 的原因还稀奇古怪的结果。

相比之下,rust 中的模式匹配就避免了这个问题,同时也让逻辑一致的情况可以使用 | 合并到同一个分支,相较于 switch 中省略 break 的写法,可以消除很多潜在的问题。

Option<T>Result<T, U>

在以往的编程经验中,我常常碰到需要表达”没有”的语义的情况,这时候可能会使用一个极大值或者 $0$ 等约定的值来表达。比如在我们在表示一个无权图中不存在的边时,可以使用 $0$ 来表示,但如果是一个有权图呢?根据我们要解决的问题不同,用 $0$ 或者极大值的情况都有,这就造成了一些潜在的麻烦。Option<T> 就很好地解决了这个问题。我知道,其实 C++ 中已经有类似的功能了,但是就使用方便程度来讲,模式匹配加上 Option<T> 可以称得上是一个大杀器了。

而错误处理也很自然地解决了需要调用者自行处理问题的情况。以往的编程经验中,遇到需要传递错误信息的情况往往是通过某些约定的值,这些值被假设不在函数返回值的域中,通过这些值来告诉调用者出现了什么问题。Result<T, U> 显然是更好的实现方法。

说到底,我们想要表达的”空”的语义和”幺元”的语义并不总是一致的,又或者在函数返回的域中并没有不会用到的值,这就导致了”空”的语义不好表达,这个情况下,带值的枚举真香。

我的收获

上面提到的内容,大体上就是所有 rust 给我带来最印象深刻的地方了,当然,rust 还有不少精心设计的语言特性,不过由于我的编程经验不够,并不能直接说出这些特性好在哪,也并没有被这些特性震撼到,不过,我心里也埋下了这样一颗种子,如果在将来的学习工作中遇到了什么问题,再回过头来看如今所学,或许就能有更深刻的理解了。

这是我第一次学习一门”课外语言”,这样的体验是很有价值的,不管是对我学习使用这门语言本身来说,还是对我更深入理解以前学习过的语言来说。前面所写的几节内容都是我在学习比较 rust 过程中的切身体会,比学习优秀的设计更重要的是,我知道了一个没那么优秀的设计不好在哪里。

无论怎样,第一阶段的学习落下帷幕,接下来需要进入第二阶段攻克更加困难的主题,这是十分激动人心的。祈祷中……

have done the orginal rustlings test before, try again and also get something to learn

not only the tech an trick experience , but also the design philosophy behind the language.

RoadBlock

0 variales

varaibles5.rs

  • shadowing: use let

1 primitive_types

primitive_types3.rs

  • shorthand to create an arry: [elment; times]

primitive_types4.rs

  • Ownership, borrow, ref: &
  • slice: [ .. ]

2 vector

vecs1.rs

  • Vec::new()
  • macro: vec![elements… ]

vecs2.rs

  • two ways
    • direct: use *
    • map:
  • used to do in the * way, now prefer the map way

3 move_semantics

move_semantics1.rs

  • ownership, borrow: the new va. takes the ownership from the old, the old can not be accessible
  • mutable

move_semantics2.rs

  • clone()
  • mutally borrow a reference to its argument: the next exec

move_semantics6.rs

  • ownership

4 struct

structs3.rs

  • self

5 enums

enums3.rs

  • match

    1
    2
    3
    4
    5
    match expr1 {
    type1 => { expr2 },
    type2(var) => { expr3 },
    _ => {}
    }
  • match allows us to compare a value to a series of patterns and execute the code based on the matching pattern

    • A pattern can consist of literal quantities, variables, wildcards, and many other things
    • bind partial values of matching patterns
  • the return value of one branch is the return value of the whole match expection

6 strings

string1.rs

  • lifetime
  • to_string()
  • From

strings3.rs

  • RTFD(RTFM): Read the fxxxx document(mannul)
  • STFW

string4.rs

  • std

7 modules

modules1.rs

  • private default
  • pub

modules2.rs

  • use xx as xxx

modules3.rs

  • use xx :: {yy, zz}

8 HashMap

hashmap1.rs

  • <key, value>
  • Hash::new()
  • insert()

hashmap2.rs

  • entry() and or_insert()

hashmap3.rs

  • well, read the answer I wrote last time
  • have ideas, but stuck on &mut T

quiz2.rs

  • stuck on modules and strings for a while

9 Options

options1.rs

  • Option, Some(), None
  • unwrap() or match
  • read the answer of match way

options2.rs

  • if let statement and while let statement
  • read the answer: think in the reverse way, .unwrap() – Some()

options3.rs

  • Bind by reference during pattern matching.
    ref annotates pattern bindings to make them borrow rather than move.
    It is not a part of the pattern as far as matching is concerned: it does not affect whether a value is matched,
    only how it is matched.

10 error_handling

error1.rs

  • Result<T, E>
    • Ok(T)
    • Err(E)

error2.rs

  • ? – match

  • If the value of Result is OK, the expression will return the value in OK and the program will continue.
    If the value is Err, Err is used as the return value for the entire function,
    as if the return keyword were used, so that the error value is propagated to the caller.

error3.rs

  • the ? is valid only in function that -> Result<T, E>
  • in main function, use () to present nothing needed

error4.rs

  • kind of read the answer

error5.rs

  • trait
  • Box<T>: A pointer type that uniquely owns a heap allocation of type T.
  • dyn

error6.rs

  • stuck, read the answer; .map_err()

11 generics

generics2.rs

  • <T>
    • functions: fn func_name(arg: T) -> T
    • structs: struct StructName
    • traits: trait TraitName
    • impl StructName { … }

12 traits

traits1.rs

  • trait: A trait defines a set of behaviors that can be shared, and once the train is implemented,
  • you can use that set of behaviors.
    • similar to interface
  • trait trait_name { … }
    • impl trait_name for StructName { … }

traits4.rs

  • traits as parameters

traits5.rs

  • multiple traits: impl Trait1 + Trait2 + Trait3 for StructName { … }

quiz3.rs

  • stuck, but according to the compiler info, fix it

13 lifetimes

lifetimes1.rs

  • lifetime: When returning a reference from a function,the lifetime parameter for the return type needs
  • to match the lifetime parameter for one of the parameters
    • follow the compiler
    • ‘a: fn funcName<’a>(x : ‘a i32) -> &’a str

lifetimes2.rs

  • same mark, same lifetime
  • paths:
    • make y live longer
    • make println! inner

lifetimes3.rs

  • lifetime in struct: struct StructName<'a> { field: 'a type1 }

14 tests

tests1.rs

  • assert!(condition, “{}”, message)

tests4.rs

  • attribute should_panic

15 iterators

iterators1.rs

  • iter(), next ()

iterators3.rs

  • .map(), collect()

iterator4.rs

  • into_iter() and fold(): e.g. iterator.fold(initial_value, |acc, x | { acc + x })

  • The fold() method is similar to the iterator’s forEach() method, but it returns a value.

  • Folds every element into an accumulator by applying an operation, returning the final result.

    fold() takes two arguments: an initial value, and a closure with two arguments: an ‘accumulator’, and an element.
    The closure returns the value that the accumulator should have for the next iteration.

    The initial value is the value the accumulator will have on the first call.

    After applying this closure to every element of the iterator, fold() returns the accumulator.

    This operation is sometimes called ‘reduce’ or ‘inject’.

iterators5.rs

  • more familiar with the iter(), map() and fold()

16 smart_pointers

box1.rs

  • compile time
  • Box: a smart pointer used to store data on the heap, which also allows us to wrap a recursive type.

rc1.rs

  • Rc<T>: used for multiple owners
  • clone(), Rc::clone() and drop()

arc1.rs

  • Arc: used for multiple owners, but it is thread-safe
  • clone()

cow1.rs

  • Cow(Copy-On-Write type): It can enclose and provide immutable access to borrowed data,

    and clone the data lazily when mutation or ownership is required.

    The type is designed to work with general borrowed data via the Borrow trait.
  • Cow::Owned() and Cow::Borrowed()
  • well, this is a little confusing, got stuck; solved by simulating the code above TODOs and the answer wrote before

17 threads

threads1.rs

  • thread::spawn(): create a new thread and run the closure in it; -> JoinHandle
  • move: move the value into the closure
  • join(): wait for the thread to finish, return the result

threads2.rs

  • Mutex<T>: A mutex is a mutual exclusion primitive that can be used to protect shared data
    • lock() : -> LockResult<MutexGuard<’_, T>>
  • Arc<T>:

threads3.rs

  • std::sync::mpsc: multi-producer, single-consumer channel, sending end and receiving end
  • clone()
  • well, I did get stuck on this one last time, this time go smoothly.

18 macros

The term macro refers to a family of features in Rust:

  • declarative macros with macro_rules! and
  • three kinds of procedural macros:
    • Custom #\[derive\] macros that specify code added with the derive attribute used on structs and enums
    • Attribute-like macros that define custom attributes usable on any item
    • Function-like macros that look like function calls but operate on the tokens specified as their argument

macros2.rs

  • the order of definition and use matters

macros3.rs

  • [macro_use]

macros4.rs

  • use ; to separate the macro arms

19 clippy

clippy1.rs

  • constant s

clippy2.rs

  • let Some(x) = option
  • there was a bug, solved

clippy3.rs

  • std::mem::swap, vec.clear()

20 conversions

using_as.rs

  • as operator: type casting and renaming imports

from_into.rs

  • The From trait is used for value-to-value conversions. If From is implemented correctly for a type,

    the Into trait should work conversely.
  • copilot this time; I dit get stuck on this last time for a while, solved eventually
  • impl From\<T\> for U

from_str.rs

  • copilot this time; did get stuck on this last time for a while, solved eventually
  • iterators: next(), last()
  • parse::\<T\>()

try_from_into.rs

  • TryFrom and TryInto traits are provided by the standard library to support this conversion.
    • impl TryFrom\<From\> for To and -> Result\<T, E\>
  • tuple and array will be checked at compile time, struct will be checked at runtime,
  • slice implementation needs to check the slice length,

as_ref.rs

  • trait bound: AsRef\<T\> and AsMut\<T\>

21 tests-II

tests5.rs

  • unsafe: item declaration and code block
  • The unsafe keyword has two uses:
    • to declare the existence of contracts the compiler can’t check (unsafe fn and unsafe trait),
    • and to declare that a programmer has checked that these contracts have been upheld (unsafe {} and unsafe impl,

      but also unsafe fn – see below).
  • confusing

tests6.rs

  • Box: Box::into_raw() and Box::from_raw()

tests7.rs || build.rs || tests8.rs

  • build.rs
    • Building a bundled C library.
    • Finding a C library on the host system.
    • Generating a Rust module from a specification.
    • Performing any platform-specific configuration needed for the crate.

test9.rs

  • ABI
  • extern
    • “C” for C-like ABI, “stdcall” for Windows ABI, “C++” for C++ ABI,

      “Rust” for Rust ABI, “system” for system ABI”
    • #[linkname = “..”]
    • export symbol to the linking environment, e.g. extern "C" fn funcName()
  • mangle symbol name: `#[no_mangle]
  • confused, copilot; the attributes should be applied properly

22 algorithm

algorithm1.rs – merge linked list

  • got stuck for a long time, not the fault of not understanding linked list, but those features
  • impl<T: PartialOrd + Clone>: a trait bound that specifies requirements for the type T.

algorithm2.rs – reverse double linked list

  • a little confused, but solved

algorithm3.rs – sort, bubble sort

  • copilot for the trait Ord

algorithm4.rs – bst, the binary search tree

  • glad it’s not avl tree
  • recursion

algorithm5.rs – bfs, adjacency matrix

  • VecDeque, bfs, visited, Adjacency matrix
  • familiar with this one, with VecDeque, solved quickly

algorithm6.rs – dfs, adjacency matrix

  • smooth

algorithm7.rs – stack, vec

  • copilot when the match arm

algorithm8.rs – impl stack with queues

  • with one queue
    • push: just push
    • pop: pop the pre and push them again, loop queue.size - 1 times
  • with two queues – this way this time
    • make sure that the new element is in the front of the queue, and one queue is empty
    • use one empty queue q1 to store the new, move the rest of elements from the other queue q2 to the cur queue q1,
    • then use the other queue q2 to store the next new element, move the rest of elements from the cur queue q1 to the other queue q2,

algorithm9.rs – binary heap

  • although aware of that heap keeps the smallest or the biggest element at the top, often used in the k-th min or max problem
  • not so familiar with this one, stfw, gpt, copilot
  • use vec to implement heap: the parent’s index (i - 1) / 2, the left child’s index 2 * i + 1, the right child’s index 2 * i + 2, start from 0
  • add: after push, use heapify_up
  • heapify_up: continuous swap until the parent is smaller(or greater, depending on the heap type) than the child
  • smallest_child_idx: the smallest child idx
  • next: iterate to get the smallest element

algorithm10.rs – graph: adjacency table, undirected graph

  • copilot a little for programming quickly, like finding elem in hashmap, trying to insert new elem into hashmap

rCore 三周rust折磨记录

前言

本人是大三的菜鸡一枚, 因为突然一时兴起,想好好学习下操作系统,又同时看到了这个课程,于是跟着课程一起开始学习了rust.

rustlins 通关记录

说起来也比较惭愧, 学了三周的rust,我对一些东西感觉还是模模糊糊,没有掌握清楚,这可能跟我没有按时听课有关系,忙着找实习笔试和面试(悲).
前30道题比较入门, 我是跟着进度来的, 到前70道题也都还好. 结果70-110 真的把我绕晕了, 又是去官网看英文资料, 又是去查别人的博客,还有问群里的大佬.
我们3群给我的感觉还是很温馨的,很多愚蠢的问题,大佬们都细心的解答.

说到rust, 感触最深的就是它的安全性, 可变引用只能有一个,这条规则把我恶心了好久 呜呜呜. 还有所有权的转移问题. 要时时刻刻注意用clone或者borrow_mut来避免所有权转移.
其实刚开始学习的时候, 我觉得也没什么吗, 不就是cpp里的unique_point吗, 我也学过, 但实际写起来就是容易忘记. 还有各种生命周期的问题, 以及范型要注意trait的限制, 真是太安全了. 无奈的转向了 unsafe 大法().

总的来说, 我还是很喜欢rust的, 但还是有很多东西需要学习, 希望能在后续的学习中, 能有所进步. 期待第二阶段的学习!!!

前言

首先非常感谢陈渝老师和向勇老师提供开源操作系统训练营这个平台,在去年机缘巧合下知道了这个训练营,但当时只是报名了,作业都没领取。这次是第一次正式参加,算是一次挑战吧,push自己走出舒适区。

环境配置

安装rustlings之前要安装gcc编译器,用包管理器也安装不了,试过用aptitude解决依赖问题,最后是更新了系统软件包就装好了。

参考资料

Rust 程序设计语言

通过例子学 Rust

Rust语言圣经(Rust Course)

Pro Git 中文版 (第二版)

学习总结

Rust作为一种系统级编程语言,具有内存安全性、并发性和高性能的特点。在学习过程中,通过 Rustlings 这个练习题库,对 Rust 语言有了更深入的了解和掌握。

Rustlings 提供了一系列简单到复杂的练习题目,涵盖了 Rust 语言的各个方面,从基础的语法到高级的模式匹配、并发等。同时,Rustlings 的反馈机制非常及时和友好。这种及时的反馈可以快速纠正错误。前面100道题主要是熟悉语法,做一些小的改动就可以过了。后面10道题是算法,因为之前学习对算法掌握的不够扎实,花了几天时间去重新学习了一下。

虽然做完了rustlings的题目,但是感觉这只是入门,如果没有gpt估计要做好久。

艰难的开头

第一次参加训练营对rust也是零基础,刚开始还有点担心,而且开放第一天立马就有好几个大佬交了直接满分通过,真给我吓到了。

不过潜下心来认真做还是很快进入了高强度学习的状态。

每道题目给的提示和对应的rust文档都很有帮助,前面题目难度不算很大,主要是有一些rust的规则或者说特性有点难以理解,生命周期和特性确实是好一番折腾,到现在也不敢说弄明白了。那部分题边做边看着报错改稀里糊涂的就做过去了。

我做完rustlings也算比较早的,不过blog拖了这么久,一个是考试,一个是有些简单的规则感觉没必要特意写在笔记里,而复杂的例如生命周期和特性这部分还真没完全搞懂。还是得慢慢学慢慢练,rust基础还是差得远。

最后这几天还连续发高烧人差点烧傻了。

二阶段能做完一半就算胜利!

生命周期

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.