图片来源 Red and Gold Crab on Rock Selective Focus Photography - Pexels
通过示例对比学习 Rust。 不定期更新中……
欢迎关注公众号:
JavaScript与编程艺术。
1. 构建工具
Cargo
cargo new vs cargo init
- cargo new 是产生一个新目录。相当于
npm init。 - cargo init 是在已有目录下初始化文件,常用在 git clone 后。
2. 优秀包
cargo-nextest
输出更漂亮更易读的单测 runner。
❯ cargo install cargo-nextest --locked
3. Rust programming
3.1 option 和 result 的互转
| Input | Output | Method |
|---|---|---|
Option | Result | ok_or / ok_or_else |
Option<T> | Option<T> / None | filter |
Option<Option<T>> | Option<T> | flatten |
Option<T> | Option<U> | map / map_or / map_or_else |
Option<T> | Option<(T, U)> | zip / zip_with / unzip |
| Input | Output | Method |
|---|---|---|
Result<T, E> | Result<&T, &E> | as_ref / as_mut |
Result<T, E> | Result<&T::Target, &E> | as_deref, as_deref_mut |
Result<T, E> | Option<E> or Option<T> | err, ok |
Result<Option<T, E>> | Option<Result<T, E>> | transpose |
Result<T, E> | Result<T, U> or Result<U, E> | map / map_or / map_err |
from: bilibili - Rust与设计模式
3.2 统计词频
JS 版本
JS 我们可以使用对象当做 HashMap。可以使用 for 循环或者 reduce:
const text = "hello worderful world hello world";
const map = {};
for (const word of text.split(' ')) {
map[word] = (map[word] || 0) + 1
}
console.log("count word", map); // {hello: 2, worderful: 1, world: 2}
const map = text.split(' ').reduce((acc, word) => {
acc[word] = (acc[word] || 0) + 1
return acc
}, {})
当然也可以使用 Map:
const text = "hello worderful world hello world";
const map = new Map();
for (const word of text.split(' ')) {
map.set(word, (map.get(word) ?? 0) + 1)
}
console.log("count word", map); // Map(3) {'hello' => 2, 'worderful' => 1, 'world' => 2}
进阶版,如果是生产级别代码,拿空格当做单词分隔符肯定是不够的,需要用到 Intl.Segment。
const text = "hello worderful world hello world";
const map = new Map();
const segmenter = new Intl.Segmenter('en', { granularity: 'word' })
const segments = segmenter.segment()
for (const item of segments) {
// console.log(item)
const { isWordLike, segment: word } = item
isWordLike && map.set(word, (map.get(word) ?? 0) + 1)
}
console.log("count word", map); // Map(3) {'hello' => 2, 'worderful' => 1, 'world' => 2}
Rust 版本
Rust 有自己的 HashMap,如果需要让 key 按照字典序则可以使用 BTreeMap(这是 JS 没有的)。
use std::collections::HashMap;
fn main() {
let mut map = HashMap::new();
let text = "hello worderful world hello world";
for word in text.split(' ') {
let count = map.entry(word).or_insert(0);
*count += 1;
}
println!("count word {:?}", map);
}
3.3 如何判断字符串是标点符号
JS
正则表达式,只能逐个枚举,不方便。
const punctuationRegexp = /^[:;"']$/
if (punctuationRegexp.test(term)) {}
Rust
字符串没有 is_ascii_punctuation,但是 char 有。
let term = "..."; // &str
if term.chars().all(|c| c.is_ascii_punctuation()) {}
3.4 match 强大之处
如果我们要实现一个这样的路由:请求根目录或者 index.html 或 index 都是 serve 入口文件。第一版你可以这么写:
match (request.method(), request.url()) {
(Method::Get, "/" | (Method::Get, "/index.html" | (Method::Get, "/index") => {
serve_index()?
}
其实还有更简洁的写法,是不是很出人意料让人眼前一亮?
match (request.method(), request.url()) {
(Method::Get, "/" | "/index.html" | "/index") => {
serve_index()?
}
3.5 耗时统计
在 JS 中我们通过 console.time / timeEnd 统计耗时。
console.time('Time cost')
// 执行耗时操作
doSthExpensive();
console.timeEnd('Time cost')
Rust 中使用 Instant::now() / elapsed() 更灵活:
use std::time::{Duration, Instant};
fn main() {
let start = Instant::now();
// 执行耗时操作
do_sth_expensive();
println!("Time cost: {:?}", start.elapsed()); // s ms h 更好的阅读
}
不过在 JS 我们也有类似的库 instant-rs - A better console.time.,用法如下:
import Instant from 'instant-rs';
const start = Instant.now();
// 执行耗时操作
doSthExpensive();
console.log("Time cost:", start.elapsed());
3.6 遍历文件,带过滤函数
使用:
fn main() -> Result<()> {
let files = walk_file(Path::new("../docs.gl/"), |fp| {
if let Some(ext) = fp.extension() {
return ext == "xhtml";
}
false;
})?;
println!("{}", files.len());
Ok(())
}
两种实现:
- 递归
fn walk_file<P>(dir: &Path, predicate: P) -> Result<Vec<PathBuf>>
where
P: Fn(&PathBuf) -> bool + std::clone::Clone, // 🔥
{
let mut files = Vec::new();
for file in fs::read_dir(dir)? {
let filepath = file?.path();
if filepath.is_dir() {
let mut sub = walk_file(&filepath, predicate.clone())?;
files.append(&mut sub); // 容易出错 🔥
} else if predicate(&filepath) {
files.push(filepath);
}
}
return Ok(files);
}
- 使用
:lable不易出错,更易读。
fn walk_file<P>(dir: &Path, predicate: P) -> Result<Vec<PathBuf>>
where
P: Fn(&PathBuf) -> bool + std::clone::Clone,
{
let mut files = Vec::new();
'next_file: for file in fs::read_dir(dir)? {
let filepath = file?.path();
if filepath.is_dir() {
walk_file(&filepath, predicate.clone())?;
+ continue 'next_file; // <=
} else if predicate(&filepath) {
files.push(filepath);
}
}
return Ok(files);
}
3.7 .. vs ...
和 JS 的扩展参数符 ... 相对应的是 Rust 的 struct update syntax。
和 JS 不同的是:
..只能放最后,- 只会覆盖已有字段。而不是已有字段覆盖传入字段。
#[derive(Debug)]
struct User {
active: bool,
username: String,
email: String,
sign_in_count: u64,
}
fn main() {
// --snip--
let user1 = User {
email: String::from("someone@example.com"),
username: String::from("someusername123"),
active: true,
sign_in_count: 1,
};
let user2 = User {
email: String::from("xxx@example.com"),
..user1
};
println!("user2: {:?}", user2)
}
3.8 如何对 struct 解构赋值
JS
变量赋值
const { x, y } = point
函数参数结构
function foo({ x, y }) {}
Rust
Rust 中总共有 6 处可以解构赋值:
JS 中只有 3 处(严格来说两处):
更多阅读
示例,对 struct 解构赋值:
let 赋值:
pub struct Options {
total_docs: usize,
term_freq_list: Vec<TermFreq>,
}
pub fn calc_idf(term: &str, options: &Options) -> f32 {
let Options {
total_docs,
term_freq_list,
} = options;
// ...
}
函数参数解构:
pub fn calc_idf(
term: &str,
Options {
total_docs,
term_freq_list,
}: &Options,
) -> f32 {
// ...
}
4. 更多阅读
- JavaScript 工程师的 Python 指南
- Rust programming for JS Developer