🎈给前端开发者看的 Rust Tips 🦀

1,258 阅读4分钟

crab-red-klippenkrabbe-grapsus-grapsus-shellfish-76966.webp

图片来源 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 的互转

InputOutputMethod
OptionResultok_or / ok_or_else
Option<T>Option<T> / Nonefilter
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
InputOutputMethod
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);
}

示例来自 Updating a Value Based on the Old Value

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.htmlindex 都是 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(())
}

两种实现:

  1. 递归
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);
}
  1. 使用 :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 不同的是:

  1. ..只能放最后,
  2. 只会覆盖已有字段。而不是已有字段覆盖传入字段。
#[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 处可以解构赋值:

image.png

JS 中只有 3 处(严格来说两处):

image.png

更多阅读

示例,对 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. 更多阅读