最近学习 Rust 的一些想法

经验

最近突然又有了干劲,拿出时间学习了一下 Rust. 之前也尝试过学习,止步于简单看看官方的那本书,这次的区别在于尝试写了一些代码, 包括到 LeetCode 和 Project Euler 做了些简单的题,以及最近正好自身有需求写了个命令行小工具。总的来说 Rust 给我感受是如果恰好你要用的库都维护地很好,那对于刷点题(链表除外),做点小东西来说写起来真的是挺爽的。如果自己造轮子,就要涉及些更高级的特性,我现在能力还不是很够,就比较费劲了。

在这里把对于 Rust 一些浅薄的想法还有学到的一些经验从各个角度记录一下,不一定对,欢迎纠错和讨论。

语言特性

语言特性方面,我现在对 Rust 的印象就是 C++ 内存管理那套加上 Haskell 的一些函数式的东西以及 Ruby 的部分语法。

第一次尝试学 Rust 是本科的时候,看到结构体和枚举类那里就歇菜了。“这是啥?结构体还能就一个名字?枚举还能附加好几个类型?” 然后后面因为觉得自己从来没接触过函数式语言就去看了看号称最 pure 的 Haskell (但还是不会写,只是略微搞懂了些概念), 回头再看 Rust 的一些概念就恍然大悟了,不仅是前面说的枚举类,还有比如 Option, Result, 迭代器啥的,就很眼熟。

我感觉最近的新一些的编程语言基本都带上一些函数式语言的特性,别的不说,模式匹配、闭包啥的都快成各种语言的标配了。 找一门函数式语言学一学基本的概念还是挺有用的。

到了内存管理这块,我本身 C++ 学得也不好,智能指针之类的都没碰过,拷贝构造赋值构造啥的也学得很混乱,经常搞出段错误, 所以学 Rust 的时候也受到些影响,经常引用来引用去的就蒙了,尤其是有闭包出现的场景。这块能力不够是比较影响写代码爽度的。

包管理

包管理这体验也不错,好像新一些的语言都会带一个官方自己的包管理器,不像 C/C++ 有不知道究竟多少种构建软件和构建方式。

项目目录的管理上也挺好接受的。

但是编译确实挺慢的,我写的这个几百行小项目,完全从头编译要 CPU 全满跑上三分钟,而且整个项目目录编译完占的空间也比较大。

标准库

标准库还是带了很多常用方法的,比如Vec还带了个windows(n)方法自动分割成定长的滑动窗口,不像C++连个stringsplit都无。

我用的比较多的是Iterator, 这里也带了很多函数式的东西,mapflat_mapfoldscan啥的。

Go vs Rust

虽然这俩语言并不是在一个生态位上,但如果非得从 GoRust 选一个,我估计会选 Rust. 虽然 Go 挺好上手的,比较劝退我的是变量命名规范、国内网络环境的特有问题和异常处理。

说到变量命名,之前本科的时候可能没太注意,但我现在发现我可能对于官方推荐驼峰命名变量和函数的语言有种天然的排斥。我还是更喜欢变量和方法名全小写加下划线,读起来更容易些。

说到国内网络环境问题,虽然有代理,但Go写出来全是静态编译的,我最常用的Proxychains用不了了,有点不方便。而且代码里可以直接用GitHub路径引用别人包的方式我也不太喜欢。

说到异常处理,写C/C++的时候我都是瞎搞的,也没按规范来,随便返回个数。写python的时候try就经常要缩进看起来很繁琐。Go我写得不多,写的时候也很少处理错误哈哈。 而Rust的这个把数据包起来的方案挺函数式的。在看Haskell的时候Maybe Monad都是被各个教材反复强调的,印象挺深刻。 所以这里学得时候也很熟悉,而且那个?的语法糖确实挺香的。当然,我现在用的还很初级,基本都是返回现成的错误,不知道项目复杂后想法会不会改变。

async/await

Rust的协程我还没系统学,现在也还没搞清楚tokiofutures是个啥关系。

写这个小项目的时候刚好有一个需求是类似现在的 pacman 并行下载, “在一个普通函数里同时开启数个协程下载内容并按顺序返回数据,要求限制最大并发数”, 很简单的需求,也不涉及互相通信,却搞了我好久。 主要是我看文档始终没有搞清楚futures里面stream的用法,网上搜例子也不多。

作为记录,贴一下最后完成的代码的大体结构

fn function() {
    const CONCURRENT_NUM: usize = 5;
    let rt = tokio::runtime::Runtime::new().unwrap();
    let results = rt.block_on(async {
        /* x 内有个 async 方法 do_sth() 访问网络并修改自身的数据 */
        let tasks = xs.iter_mut().map(|x| x.do_sth());
        stream::iter(tasks)
            .map(|task| async { task.await })
            .buffered(CONCURRENT_NUM)
            .collect::<Vec<_>>()
            .await
    });
    handle_results();
}

总之这块的生态我觉得可能还不够成熟,我等一段时间再学习下。

静态编译

虽然不是默认静态编译,但也是支持的。具体操作方法(只看 linux):

  1. 添加musltarget
    rustup target add x86_64-unknown-linux-musl
    
  2. 如果用到openssl要在Cargo.toml里添加
    openssl = {version = "0.10", features = ["vendored"]}
    
  3. 构建时指明target
    cargo build --target x86_64-unknown-linux-musl
    
  4. 编译好后可以用fileldd命令检查下

musl是一个C标准库实现,至于要不要安装musl我不记得了,反正我装了。感觉构建时要编译openssl应该要用到吧。

不喜欢的地方

  1. 要先去 crates.io 找库,看怎么引入,再去 docs.rs 看文档,而且文档都是自动生成,经常只给出了一堆定义, 我这样的新手有点难看懂,不知道怎么用。
  2. 感觉生态不完善,缺少很多常用并且有人持续维护的大型库。
  3. 用的人还不够多,网上找资料有时候不好找或过时,有种之前搞vpp时经历的难受感,真的很劝退。
  4. 编译速度太慢了。

总结

总的来说我还挺喜欢的,但我还没接触太深,可能只尝到了浅层的一些好处。目前来看,对我来说可以当作兴趣去学,要靠这个吃饭还不太可能。我应该会持续关注,希望未来能越来越好用,越来越大众化吧。Mozilla给点力啊,Servo啥时候出?