mcpField
I
idiomatic-rust

idiomatic-rust

github.com/rewrite-rs开发1042 次安装10 周前更新
代码仓库

关于 idiomatic-rust

专注于 Rust 惯用表达的编码技能,涵盖迭代器、类型转换、派生实现、新类型和命名等常见选择,适合整理 Rust 代码,并附有标准验证清单。

主要功能

  1. 01表达式导向的 Rust 模式
  2. 02迭代器流水线指南
  3. 03转换 Trait 的选择
  4. 04派生实现与新类型指南
  5. 05命名与验证建议

适用场景

  1. 01将循环重构为迭代器流水线
  2. 02为类型选择转换 Trait
  3. 03用派生实现简化样板代码
  4. 04检查 Rust 命名与代码风格

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/rewrite-rs/skills --skill idiomatic-rust

安装后在 Claude Code 中输入/idiomatic-rust确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

BOILERPLATE.md
NAMING.md
agents/
openai.yaml

详细介绍

地道的 Rust

这项技能关注的是表达方式:Rust 读者期待看到什么形式?

地道 Rust 的风格

优先使用表达式而非语句:返回值的 match 胜过在每个分支中都赋值给 let mut;能直接得出结果的链式操作胜过在循环中设置标志变量、再于循环后检查——让类型系统维护不变量,而不是依赖运行时检查。let ... else 能让提前返回保持在顶部,避免在嵌套的 if let 下方把正常路径越推越靠右;matches! 是用于模式的布尔测试。如果工具链支持,if-let 链可以组合条件——这需要较新的 edition,因此请先检查仓库的 MSRV。

迭代器

优先考虑迭代器流水线,而不是索引循环——for i in 0..v.len() { ... v[i] ... } 几乎总有对应的迭代器写法,而且迭代器版本会在边界不正确时编译失败,而不是在运行时 panic。直接收集为所需类型,而不是先收集到 Vec 再转换:

// Reads like a translation.
let mut names = Vec::new();
for user in &users {
    names.push(user.name.clone());
}

// Reads like Rust.
let names: Vec<String> = users.iter().map(|u| u.name.clone()).collect();

collect 也可以收集为 HashMap、HashSet、String 和 Result<Vec<_>, _>——最后一种正是 ? 与迭代组合的方式。

use std::num::ParseIntError;

fn parse_all(lines: &[&str]) -> Result<Vec<i64>, ParseIntError> {
    lines.iter().map(|line| line.parse::<i64>()).collect::<Result<Vec<_>, _>>()
}

如果循环体有实际副作用,或存在组合器会掩盖的提前退出,就优先使用普通 for 循环——清晰胜过对迭代器的执着。

类型转换

当一种类型可以由另一种类型构造时,实现 From——通过通用 impl 会自动获得 Into,因此不要手动实现两个方向。当转换可能失败时,实现 TryFrom,而不是实现 From 后再 panic 或返回哨兵值。

struct Celsius(f64);
struct Fahrenheit(f64);

impl From<Celsius> for Fahrenheit {
    fn from(c: Celsius) -> Self {
        Fahrenheit(c.0 * 9.0 / 5.0 + 32.0)
    }
}

优先派生,而非手写 impl

手写之前先考虑 #[derive(Debug, Clone, PartialEq, Default, Hash)]——派生的 impl 根据实际字段生成,因此会随着结构体的变化保持同步。只有派生实现不正确时,才手动编写:

  • Default 必须保留字段逐个取默认值会破坏的不变量(例如,Percentage 的总和必须为 100,而不是各字段都为零)。
  • Debug 必须隐藏敏感字段(如 API 密钥、密码),而不是将其打印出来。
  • PartialEq 中,逻辑上相等的值可能在不应参与比较的字段上有所不同(如缓存的哈希值、时间戳)。

对于枚举,使用 #[derive(Default)] 并在预期变体上标注 #[default],遵循的也是同一原则——本目录中的 BOILERPLATE.md 提供了这两种写法。

Newtype

Newtype 不产生运行时开销,还能让参数顺序错误的调用变成类型错误。只要裸 u64、String 或 f64 代表某个领域概念(如 ID、货币金额),而不只是一个数字,就应考虑使用 newtype。

命名

名称中的每个词都应有意义:用 Bookings,而不是 BookingService——Service、Manager、Helper、Util、Data 和 Info 都表明名称还没斟酌完成。as_/to_/into_ 前缀表示有成本——免费视图、分配、消费。字段访问器不使用 get_;缩写按普通单词的方式书写(HttpClient、Uuid);不需要 Self 的辅助函数应是自由函数,而不是把 impl 块当作命名空间。分层表和示例对照见 NAMING.md。

实际上反映设计问题的样板代码

并非所有重复代码都能通过 derive 消除——五个几乎相同的 match 分支,或一个有十个转发方法的包装类型,都可能表明缺少抽象。有关这些示例对照(包括 Deref 陷阱),请参阅 BOILERPLATE.md。

延后处理

这项技能规范的是表达方式,而非决策:clone 还是 borrow 的选择见 /ownership-not-clone;错误类型的形式见 /rust-errors;如何让非法状态无法表示见 /type-driven-design。

验证

行为不变,形式不同——运行标准三项检查:

cargo clippy --all-targets   # add -- -D warnings only if the repo has no lint config
cargo fmt --check
cargo test

按照仓库配置的 lint 级别运行这三项检查;除非仓库启用了 clippy::pedantic,否则应将其发现视为建议,而非违规。

评论

支持 Markdown