给Rust初学者的5个小建议
Categories: Language Programming
Tags: Language Rust Programming
本篇文章主要介绍给Rust初学者的5个小建议。
原视频来自Youtube: 5 Tips for Rust Beginners, 本文对其进行翻译简化.
已获得原作者许可。未经允许,请勿随意转载。
建议1: 利用Cargo工具
Rust有很多好用的开发工具,你可以使用cargo --list
来查看所有可用命令的列表(其中有部分是译者所安装的额外工具):
cargo --list
Installed Commands:
b alias: build
bench Execute all benchmarks of a local package
build Compile a local package and all of its dependencies
c alias: check
check Check a local package and all of its dependencies for errors
chef
clean Remove artifacts that cargo has generated in the past
clippy Checks a package to catch common mistakes and improve your Rust code.
config Inspect configuration values
d alias: doc
doc Build a package's documentation
fetch Fetch dependencies of a package from the network
fix Automatically fix lint warnings reported by rustc
fmt Formats all bin and lib files of the current crate using rustfmt.
generate-lockfile Generate the lockfile for a package
git-checkout This subcommand has been removed
init Create a new cargo package in an existing directory
install Install a Rust binary. Default location is $HOME/.cargo/bin
locate-project Print a JSON representation of a Cargo.toml file's location
login Save an api token from the registry locally. If token is not specified, it will be read from stdin.
logout Remove an API token from the registry locally
metadata Output the resolved dependencies of a package, the concrete used versions including overrides, in machine-readable format
mir-checker
miri
new Create a new cargo package at <path>
owner Manage the owners of a crate on the registry
package Assemble the local package into a distributable tarball
pkgid Print a fully qualified package specification
publish Upload a package to the registry
r alias: run
read-manifest Print a JSON representation of a Cargo.toml manifest.
report Generate and display various kinds of reports
run Run a binary or example of the local package
rustc Compile a package, and pass extra options to the compiler
rustdoc Build a package's documentation, using specified custom flags.
search Search packages in crates.io
t alias: test
tarpaulin
test Execute all unit and integration tests and build examples of a local package
tree Display a tree visualization of a dependency graph
udeps
uninstall Remove a Rust binary
update Update dependencies as recorded in the local lock file
vendor Vendor all dependencies for a project locally
verify-project Check correctness of crate manifest
version Show version information
yank Remove a pushed crate from the index
有很多命令比如build
或者run
是大家所熟知的,但也有一些命令对于初学者来说可能是相对陌生的,比如cargo doc
来构建包文档,又比如cargo clippy
来检查常见错误以及提升你的rust代码质量。
这里简单介绍一下clippy的使用。clippy是官方rust-lang所维护的工具,仓库在rust-lang/rust-clippy,如果需要自己安装的话,在安装了rustup
的情况下,可以使用rustup component add clippy
来安装。
安装之后可以运行cargo clippy
来进行检查,或cargo clippy --fix
来检查并自动修正部分问题。全部检查规则可见clippy-lints。
以下举一个使用clippy的例子:
fn main() {
let my_vec: Vec<String> = Vec::new();
if my_vec.len() == 0 {
println!("my_vec is empty");
}
}
如果运行cargo clippy
,就会提示你如下建议:
warning: length comparison to zero
--> src/main.rs:3:8
|
3 | if my_vec.len() == 0 {
| ^^^^^^^^^^^^^^^^^ help: using `is_empty` is clearer and more explicit: `my_vec.is_empty()`
|
= note: `#[warn(clippy::len_zero)]` on by default
= help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#len_zero
这个建议就是让你使用is_empty()
这个函数来替代len()
来检查长度是否为0.
那么我们修改后的程序就可以变为:
fn main() {
let my_vec: Vec<String> = Vec::new();
if my_vec.is_empty() {
println!("my_vec is empty");
}
}
建议2: 不要有哨兵值(Sentinel Values)
核心就是用Option代替空值返回
其他语言比如Javascript,在某些情况下会利用哨兵值返回空值,比如如下这个例子中,在找不到条件值的情况下,会返回-1以告诉你没有找到,这个值就是哨兵值,那么如果我们需要利用返回结果时,则需要对于这个值做额外检查:
const arr = [1,2,3]
arr.find(item => item === 4)
而Rust则具有一个更好的类型系统来应对这个问题,你不需要再依赖哨兵值了,可以直接使用Option来代替,如下:
- 使用哨兵值的写法(不推荐):
fn maybe_get_name(x: bool) -> &'static str {
if x {
"Bob"
} else {
""
}
}
fn main() {
let name = maybe_get_name(true);
println!("first name is {}", name);
let name = maybe_get_name(false);
println!("second name if {}", name);
}
- 使用Option的写法(推荐):
fn maybe_get_name(x: bool) -> Option<&'static str> {
if x {
Some("Bob")
} else {
None
}
}
fn main() {
let name = maybe_get_name(true);
if let Some(name) = name {
println!("first name is {}", name);
}
let name = maybe_get_name(false);
if let Some(name) = name {
println!("second name is {}", name);
}
}
可以看到,在第一个例子中,第一次打印出来了Bob
,第二次打印出来了空,但其实这种情况下是很难判断是否为一个空值的;而在第二个例子中,第一次同样打印出来了Bob
,但第二次就没有进行打印了,这里就是明确发现了是个空值的情况。利用Option,可以让我们更好的去判断是否为空值,而不需要知道或定义一个哨兵值了。
建议3: 使用impl创建更灵活的API参数
这里我们创建一个打印出文件内容的函数作为例子,这个函数可以接受一个文件名作为参数,会打印出文件内容:
use std::fs;
fn print_file_content(path: &str) {
let content = fs::read_to_string(path).unwrap();
println!("{}", content);
}
fn main() {
print_file_content("README.md");
}
如果我们只是这样定义函数参数,那么我们必须要传入一个字符串slice作为文件名称,而不能够传入其他的同样可以指定到文件的参数,例如Path::new("README.md")
。所以更好的做法是:
use std::fs;
fn print_file_content(path: impl AsRef<Path>) {
let content = fs::read_to_string(path).unwrap();
println!("{}", content);
}
fn main() {
print_file_content("README.md");
let path = Path::new("README.md");
print_file_content(path);
}
建议4: 为你的结构体实现一些例如Debug
, Default
等通用的Traits
例如我们有一个这样的结构体:
struct SomeStruct {
inner: Option<Box<SomeStruct>>
}
然后我们在main函数里面初始化了一个这样的变量:
fn main() {
let nested_struct = SomeStruct {
inner: Some(Box::new(SomeStruct {
inner: Some(Box::new(SomeStruct {inner: None}))
})),
};
}
然而我们这样没法直接打印出这个变量。这个时候我们就应该给这个变量实现一个Debug
的trait:
#[derive(Debug)]
struct SomeStruct {
inner: Option<Box<SomeStruct>>
}
这样我们就可以在main函数中打印出这个变量了:
fn main() {
let nested_struct = SomeStruct {
inner: Some(Box::new(SomeStruct {
inner: Some(Box::new(SomeStruct {inner: None}))
})),
};
// 连缀在一起的打印
println!("{:?}", nested_struct);
// 格式化的打印
println!("{:#?}", nested_struct);
}
运行cargo run
打印出来的结果如下:
SomeStruct { inner: Some(SomeStruct { inner: Some(SomeStruct { inner: None }) }) }
SomeStruct {
inner: Some(
SomeStruct {
inner: Some(
SomeStruct {
inner: None,
},
),
},
),
}
然后如果我们还想看到我们打印的位置和变量的名称,那么可以使用dbg!
宏来打印:
fn main() {
let nested_struct = SomeStruct {
inner: Some(Box::new(SomeStruct {
inner: Some(Box::new(SomeStruct {inner: None}))
})),
};
dbg!(nested_struct);
}
运行cargo run
打印结果如下:
[src/main.rs:12] nested_struct = SomeStruct {
inner: Some(
SomeStruct {
inner: Some(
SomeStruct {
inner: None,
},
),
},
),
}
我们还可以为SomeStruct
实现一个Default
的trait:
#[derive(Debug, Default)]
struct SomeStruct {
inner: Option<Box<SomeStruct>>
}
这样我们就可以直接使用默认值来初始化这个变量了:
fn main() {
let nested_struct = SomeStruct {
..Default::default()
};
dbg!(nested_struct);
}
运行cargo run
打印结果如下:
[src/main.rs:10] nested_struct = SomeStruct {
inner: None,
}
建议5: 命名规范
详细可见官方api-guidelines/naming
通常, Rust 倾向于在“类型级别”(类型和traits)使用 UpperCamelCase
, 在值级别使用 snake_case
。更加精准的说明可见下表:
Item | Convention |
---|---|
Crates | unclear, kebab-case 和snake_case 一般认为均可 |
Modules | snake_case |
Types | UpperCamelCase |
Traits | UpperCamelCase |
Enum variants | UpperCamelCase |
Functions | snake_case |
Methods | snake_case |
General constructors | new or with_more_details |
Conversion constructors | from_some_other_type |
Macros | snake_case! |
Local variables | snake_case |
Statics | SCREAMING_SNAKE_CASE |
Constants | SCREAMING_SNAKE_CASE |
Type parameters | concise UpperCamelCase , usually single uppercase letter: T |
Lifetimes | short lowercase , usually a single letter: 'a , 'de , 'src |
Features | unclear |
更多的详细描述请见官方指南。
其实cargo check
已经对于命名会做出一些检查,所以在编码完成之后可以运行该命令,会给出不够规范的命名的修改建议。