【译】12 条你可能还不知道的 Rust 提示和技巧
- 12 Rust Tips and Tricks you might not know yet 译文(12 条你可能还不知道的 Rust 提示和技巧)
- 原文链接:https://federicoterzi.com/blog/12-rust-tips-and-tricks-you-might-not-know-yet/
- 原文作者:Federico Terzi
- 译文来自:https://github.com/suhanyujie/article-transfer-rs/
- 译者:suhanyujie
- 译者博客:suhanyujie
- ps:水平有限,翻译不当之处,还请指正。
- 标签:Rust,Rust 技巧
Rust 是一种伟大的编程语言: 可靠、快速、令人愉快,但也相当复杂。在过去的两年里,我一直在专业和业余项目(比如 Espanso)中使用它。在那段时间里,我偶然发现了许多有用的模式和 crate,我希望在刚开始学习它的时候就知道它。
使用 Cow<str>
作为返回类型
有时你需要编写接受字符串片段(&str
)并有条件地返回其修改版本或原始版本的方法。对于这些情况,你可以使用 Cow<str>
,以便只在必要时分配新内存。
use std::borrow::Cow;
fn capitalize(name: &str) -> Cow<str> {
match name.chars().nth(0) {
Some(first_char) if first_char.is_uppercase() => {
// No allocation is necessary, as the string
// already starts with an uppercase char
Cow::Borrowed(name)
}
Some(first_char) => {
// An allocation is necessary, as the old string
// does not start with an uppercase char
let new_string: String = first_char.to_uppercase()
.chain(name.chars().skip(1))
.collect();
Cow::Owned(new_string)
},
None => Cow::Borrowed(name),
}
}
fn main() {
println!("{}", capitalize("bob")); // Allocation
println!("{}", capitalize("John")); // No allocation
}
使用 Crossbeam channel 而非标准库中的 channel
crossbeam crate 提供了一个强大的替代标准 channel,支持 select 操作,超时等等。类似于你在 Golang 和传统的 Unix 套接字的使用。
use crossbeam_channel::{select, unbounded};
use std::time::Duration;
fn main() {
let (s1, r1) = unbounded::<i32>();
let (s2, r2) = unbounded::<i32>();
s1.send(10).unwrap();
select! {
recv(r1) -> msg => println!("r1 > {}", msg.unwrap()),
recv(r2) -> msg => println!("r2 > {}", msg.unwrap()),
default(Duration::from_millis(100)) => println!("timed out"),
}
}
Golang 风格的 Scopeguard 操作符
如果你从 Golang 转过来,你可能会错过某些用例的 “defer” 操作符(比如使用原始指针时释放内存或关闭套接字)。
在 Rust (除了 RAII 模式之外)中,你可以使用作用域保护(scopeguard)框来轻松实现“清理”逻辑。
#[macro_use(defer)] extern crate scopeguard;
fn main() {
println!("start");
{
// This action will run at the end of the current scope
defer! {
println!("defer");
}
println!("scope end");
}
println!("end");
// Output:
// start
// scope end
// defer
// end
}
使用 Cargo-make 进行打包
构建脚本对很多场景都很有用,但通常不适用于打包。我最喜欢的解决方案是 sagiegurari 的 Cargo Make,一个基于 Rust 的任务运行器和构建工具。
自定义并链接 panic 处理程序
应急处理程序(Panic handlers)(也称 hooks)可以被重写和链接,这在为应用程序设置自定义错误报告和日志记录时特别有用。
use std::panic::{set_hook, take_hook};
fn main() {
let prev_hook = take_hook();
set_hook(Box::new(move |panic| {
println!("custom logging logic {}", panic);
prev_hook(panic);
}));
let prev_hook = take_hook();
set_hook(Box::new(move |panic| {
println!("custom error reporting logic {}", panic);
prev_hook(panic);
}));
panic!("test")
// Output:
// custom error reporting logic panicked at 'test', src/main.rs:20:5
// custom logging logic panicked at 'test', src/main.rs:20:5
}
使用 VSCode 中的 Rust Analyzer 插件开发
matklad 写的 Rust Analyzer 扩展是显着优于“官方” 的 Rust 插件之一。不幸的是,它仍然作为第二个查询结果出现在扩展市场上,误导了很多初学者。
使用闭包时使用 impl Trait
如果可能的话,倾向于将闭包传递给一个函数(称为 impl Trait) ,而不是通用函数,以保持签名的干净。对于非常见的情况,你可能需要用 Box<Fn()>
包装闭包,但请记住这将会有额外的开销。
// Instead of this
fn setup_teardown_generic<A: FnOnce()>(action: A) {
println!("setting up...");
action();
println!("tearing down...")
}
// Use this
fn setup_teardown(action: impl FnOnce()) {
println!("setting up...");
action();
println!("tearing down...")
}
// As a note, this pattern is very useful inside tests
// to create/destroy resources.
fn main() {
setup_teardown(|| {
println!("Action!");
})
// Output:
// setting up...
// Action!
// tearing down...
}
使用 VSCode 时,配置保存时启用 Clippy
如果你正在使用 VSCode + RA,我强烈建议进入设置 > RA > 检查保存: 命令和设置“ clippy”作为新的默认值,而不是“检查”。同样的用户体验,更好的警告。
在常用错误处理中使用“thiserror”和“anyway”
使用 thiserror 和邋遢板条箱处理惯用错误。当消费者需要根据错误有条件地采取行动时,你应该使用此错误,否则无论如何。一个很好的指导方针是“对库和应用程序使用
这个错误”。
使用 dbg!() 宏代替 println!()
在调试时使用 dbg!() 宏而不是 println!()
。代码更少,信息更有用。
fn main() {
let var1 = 2;
println!("{}", 2); // Output: 2
dbg!(var1); // Output: [src/main.rs:5] var1 = 2
dbg!(var1 * 2); // Output: [src/main.rs:6] var1 * 2 = 4
}
include_str!() 和 include_bytes!() 宏
使用 include_str!() 和 include_bytes!() 宏在编译时读取文件的内容并将其存储在 const 中。有助于避免混乱使用多行字符串文本。
// Both of these files are read at *compile time*
const FILE_CONTENT: &str = include_str!("./path/to/the/file.txt");
const BINARY_FILE_CONTENT: &[u8] = include_bytes!("./path/to/image.png");
fn main() {
println!("{}", FILE_CONTENT); // Output: file content as string
}
与 c/c++ 代码的集成
如果你需要将 c/c++ 代码与 Rust 集成在一起,那么 cc crate 和适当的构建脚本可以让你更加得心应手。例如,我使用它们将流行的 c++ gui 框架 wxWidgets 与我的项目 Espanso 集成(参见构建脚本)
// Inside the build script (build.rs)
fn main() {
println!("cargo:rerun-if-changed=src/native.c");
println!("cargo:rerun-if-changed=src/native.h");
cc::Build::new()
.include("src/native.h")
.file("src/native.c")
.compile("nativemodule");
println!("cargo:rustc-link-lib=static=nativemodule");
}
// Then, in another Rust file (for example, ffi.rs)
#[link(name = "nativemodule", kind = "static")]
extern "C" {
pub fn your_cool_c_module();
}