gitbook/陈天 · Rust 编程第一课/docs/483050.md
2022-09-03 22:05:03 +08:00

13 KiB
Raw Permalink Blame History

特别策划|学习锦囊(三):听听课代表们怎么说

你好,我是课程编辑叶芊。

为了帮助你在新的一年更好地学习Rust和学习这个专栏特地邀请了几位课代表来分享一下他们学习专栏、学习Rust语言的个人经验和方法希望能给你一些参考和启发。

今天是特邀课代表分享个人学习经验的第三辑。话不多说,我们直接看分享。


@Milittle

你好我是Milittle。

2020年硕士毕业研究生期间一直是用C++做项目目前我在做IaaS开发主力语言是Python和Go。我今年的目标就是拿下Rust这门硬通货。非常开心收到编辑的邀请来分享我自己的学习心得。每一个人的经历不同也有属于自己的人生这里我就自己目前的一些感悟聊一聊。

为什么想到来学Rust

第一次了解Rust是在左耳朵耗子的CoolShell中看到对这门语言的描述

如果你对Rust的概念认识的不完整你完全写不出程序哪怕就是很简单的一段代码。这逼着程序员必须了解所有的概念才能编码。但是另一方面也表明了这门语言并不适合初学者……

我看到这句话的时候,非常好奇这是一门什么样的语言,为什么会逼着程序员必须了解所有的概念才能编码?我们的编程语言不是为了解放程序员而发明的么就去大致浏览了一些Rust Book的内容不过当时刚工作也没有太深入地了解和学习。

因为我常年混迹在极客时间上学习后来看到有陈天Rust第一课这门课程刚好工作上每天也可以稍微抽出一些时间就决定入手学一下。每次打开一讲都是新知识涌现的过程。碰到了不熟悉的知识我都会先自己借助Google一通操作把能捕捉到的知识都先吃到自己的大脑里面然后再回过头来看老师的专栏这样能让我的知识叠的厚一点。

新知识确实很多,也很容易丧失学习的兴趣,被难题困住一阵就放弃了。我自己是用了两个方法:持续学习、重复练习

持续学习

在生活中,我自己是一个涉猎比较广的选手,自己学摄影,喜欢看电影,偶尔看看经济学的书籍,我觉得未来一定是留给持续学习、持续做准备的人。

所以相信自己如果持续学习到今天不能让你从Rust的难题中解脱出来那就是你不够持续还得再坚持下去不要放弃说不定明天你就成功战胜了Rust难关。

而且持续学习,还可以用在那些不会立马给你回报的知识上,这样才能持续刺激自己学习的兴趣。

我在研究生期间有一个研究方向是借助机器学习视觉技术做一个基本的步态识别虽然后来由于种种原因没有做下去自己还是做了个小Demo感兴趣可以到我的GitHub上瞧一瞧步态识别案例。因为一直做视觉对计算机视觉中的一些inference的技术感兴趣我自己结合Nvidia官网的TensorRT业余时间自己持续学习也做了一些DemoTensorRT的案例)。

因为有这个习惯学习Rust的时候我每天都在思考怎么用它做点事情。正好20211202这一天是回文日就想到可以搞一个算法题输出所有已经过去的回文日第二天立马上手用Rust试了一下就写了这一段代码

use std::collections::HashMap;

fn main() {
    let mut valid_palindrome: Vec<String> = Vec::new();
    let mut month_days = HashMap::from([
        ("01", "31"),
        ("02", "28"),
        ("03", "31"),
        ("04", "30"),
        ("05", "31"),
        ("06", "30"),
        ("07", "31"),
        ("08", "31"),
        ("09", "30"),
        ("10", "31"),
        ("11", "30"),
        ("12", "31"),
    ]);

    for i in 1..=9999 {
        let full_year = format!("{:0width$}", i, width = 4).to_string();
        let month = &full_year[2..].chars().rev().collect::<String>();
        let day = &full_year[..2].chars().rev().collect::<String>();
        if is_leap_year(full_year.parse::<i32>().unwrap()) {
            let value = month_days.get_mut("02").unwrap();
            *value = "29";
        }
        if is_valid(month, day, &month_days) {
            let s = format!("{} {}-{}", full_year.parse::<i32>().unwrap(), month, day);
            valid_palindrome.push(s);
        }
        let value = month_days.get_mut("02").unwrap();
        *value = "28";
    }
    println!("{:?}", valid_palindrome);
}

#[allow(dead_code)]
fn is_valid(month: &str, day: &str, days: &HashMap<&str, &str>) -> bool {
    if month <= "12" && month != "00" {
        match days.get(month) {
            Some(&d) => {
                if day <= d && day != "00" {
                    return true;
                }
            }
            _ => return false,
        }
    }
    false
}

fn is_leap_year(year: i32) -> bool {
    if (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0) {
        return true;
    }
    false
}

你可以运行一下会惊奇的发现从公元0001年到公元9999年回文日总共是366天仔细一想其实是非常直观合理的但是如果之前没有写过或者思考过一定不能脱口而出。这只是一个简单的小例子当持续学习的劲头保持下去你会发现更多有趣的事情。

这些都是一些小小的Demo但是在促使着我不断学习、提升自己。到现在我的GitHub有很多自己写的一些示例虽然每一个Demo不一定能为自己带来很多的回报但是在让自己保持学习的过程中我一直都会感觉到非常兴奋、非常愉快。

重复练习

持续学习是一个长久的过程,但是面对困难的单个知识点,就要靠重复练习了。

重复是所有人学习的最终杀器。这个心得来自于我的高中老师,他说如果你学不会,就重复多次,一遍不会就来两遍,两遍不会就四遍,重复多看几次一定能看明白。当然,不是所有知识都是能通过重复练习来理解或者获取到的,但我相信绝大多数的知识是可以通过重复训练掌握的。

学习Rust重复练习更是我做的最多的一件事情。比如我读完Rust Book会回过头来看老师的专栏再去找bilibili的课程去重复巩固学习其他人的观点和思路。举我学习异步编程的例子吧重复练习才让我理解的更深刻。

这是学习Rust异步小册子的一段代码我稍微修改了一下

async fn learn_song() -> f64 {
    let mut sum: f64 = 0.0;
    for _i in 0..10 {
        tokio::time::sleep(tokio::time::Duration::from_millis(1)).await;
        sum += 1 as f64;
    }
    sum
}

async fn sing_song() {
    println!("sing song");
}

async fn dance() {
    tokio::time::sleep(tokio::time::Duration::from_nanos(2)).await;
    println!("dance");
}

async fn learn_and_sing_song() {
    let learned = learn_song();
    println!("i already learned song: {}", learned.await);
    sing_song().await;
}

async fn create_dir() {
    let beg = std::time::Instant::now();
    tokio::fs::create_dir("./test").await;
    println!("create folder consume: {:?}", beg.elapsed().as_micros());
}

async fn async_main() {
    let (_a, _b) = tokio::join!(dance(), create_dir());
}

#[tokio::main]
async fn main() {
    let now = tokio::time::Instant::now();
    async_main().await;
    println!("{:?}", now.elapsed().as_micros());
}

通过对代码重复地练习和思考,我总结出了一些自己容易理解的点:

  • async关键字会把一个函数块或者代码块转换为一个Future这个Future代表的是这个代码块想要运行的数据的步骤。
  • 当我们想运行一个Future的时候需要使用关键字await这个关键字会使得编译器在代码处用一个loop{}来运行该Future。
  • Future对象里面有poll方法这个poll方法负责查看Future的状态机是否为Ready如果不是Ready则Pending如果是Ready的话就返回结果。如果为Pending的话_task_context会通过yield把该Future的线程交出去让别的Future继续在这个线程上运行。
  • 因为Future的底层对象是由Generator构成的所以调用poll方法的时候其实调用的是genrator的resume方法这个方法会把Generator的状态机Yielded、Completed返回给poll。如果为Yieldedpoll返回Pending否则Completed返回Ready当返回Ready的时候loop就被跳出这个Future不会被再次执行了。

这些结论就是自己在重复探索的时候不断尝试展并且展开编译器编译后的代码才发现的知识点希望这些可以给你一些学习Rust的启发

// $cargo rustc -r -- -Zunpretty=hir -o test.txt

// source code for learn_song
async fn learn_song() -> f64 {
    let mut sum: f64 = 0.0;
    for _i in 0..10 {
        tokio::time::sleep(tokio::time::Duration::from_millis(1)).await;
        sum += 1 as f64;
    }
    sum
}



// expand hir for learn_song
async fn learn_song()
 -> /*impl Trait*/ 
 #[lang = "from_generator"]
(
    move |mut _task_context| {
        {
            let _t = {
                    let mut sum: f64 =0.0;
                    {
                        let _t = match #[lang = "into_iter"](#[lang = "Range"]{start: 0, end: 10,}) {
                                mut iter => loop {
                                    match #[lang = "next"](&mut iter) {
                                        #[lang = "None"] {} => break,
                                        #[lang = "Some"] { 0: _i } => {
                                            match #[lang = "into_future"](tokio::time::sleep(tokio::time::Duration::from_millis(1))) {
                                                mut pinned => loop {
                                                    match unsafe {
                                                            #[lang = "poll"](#[lang = "new_unchecked"](&mut pinned),
                                                            #[lang = "get_context"](_task_context))
                                                        }
                                                        {
                                                            #[lang = "Ready"] { 0: result } => break result,
                                                            #[lang = "Pending"] { } => { }
                                                        }
                                                        _task_context = (yield ());
                                                },
                                            };
                                            sum += 1 as f64;
                                        }
                                    }
                                },
                            };
                        _t
                    };
                    sum
                };
            _t
        }
    }
)

其实在学习新知识的过程中,谁也不能一次性把知识点吃透,都是在不断重复之前的知识,然后加上自己的思考,在这个过程中不断汲取,内化为自己的知识。这样通过多次的打怪升级,最后我们也一定会有自己独到的见解。

学习资料

最后,也整理了一些我学习的相关资料分享给你,希望对你有所帮助:

寄语

可能你的学习方法和我的不一样但是只要你选择了这门课程就不要后悔一如既往地保持自己学习Rust的初心以持续学习的心态拿出重复多次学习的决心来战胜Rust编程第一课不要抱怨不要后悔勇往直前。你可以的。


这是今天课代表Milittle同学的分享如果你有自己的Rust学习故事欢迎在下方留言区留言交流。

预祝你新年快乐,身体健康,学习进步~