Skip to content

Commit

Permalink
Merge pull request #41 from sweet2honey/main
Browse files Browse the repository at this point in the history
item 23 finished
  • Loading branch information
lispking authored Jun 27, 2024
2 parents 83a3a9c + c58f51b commit cdfb267
Show file tree
Hide file tree
Showing 2 changed files with 94 additions and 0 deletions.
1 change: 1 addition & 0 deletions src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
- [第 15 条:理解借用检查器](./chapter_3/item15-borrows.md)
- [第 16 条:避免写 unsafe 代码](./chapter_3/item16-unsafe.md)
- [依赖](./chapter_4.md)
- [第 23 条:避免通配符导入](./chapter_4/item23-wildcard.md)
- [工具](./chapter_5.md)
- [第 27 条:为公共接口撰写文档](./chapter_5/item27-document-public-interfaces.md)
- [超出 Rust 标准](./chapter_6.md)
Expand Down
93 changes: 93 additions & 0 deletions src/chapter_4/item23-wildcard.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
# 第 23 条:避免通配符导入

Rust 的 `use` 语句可以从另一个 crate 或者模块中引入一个具名项,使得该项可以在当前模块的代码中不加限定符使用。形如 `use somecrate::module::*` 的通配符导入 *wildcard import*(或称 *glob import*)表示那个模块*所有*的 public 符号都被添加到了本地的命名空间中。

[第 21 条] 中所述,一个外部 crate 可能会向 API 中添加新的条目作为次要版本升级的一部分;这是一种向后兼容的修改。

这两个场景一旦结合到一起就会产生新的忧虑,依赖项的一个非破坏性更改可能会破坏你的代码:如果依赖项添加了一个与你已经使用的名称冲突的新符号,会发生什么情况?

从最简单的角度来看,这倒不是个问题:通配符导入的符号被视为低优先级,所以在你的代码中使用的匹配的名称都会被优先匹配:

```rust
use bytes::*;

// Local `Bytes` type does not clash with `bytes::Bytes`.
// 本地定义的 `Bytes` 类型不会跟 `bytes::Bytes` 发生冲突。
struct Bytes(Vec<u8>);
```

不幸的是,仍然存在发生冲突的情况。例如,依赖项添加了一个新的 trait 并且为某些类型实现了:

```rust
trait BytesLeft {
// Name clashes with the `remaining` method on the wildcard-imported
// `bytes::Buf` trait.
// `remaining` 方法跟通配符导入的 `bytes::Buf` trait 冲突了。
fn remaining(&self) -> usize;
}

impl BytesLeft for &[u8] {
// Implementation clashes with `impl bytes::Buf for &[u8]`.
// 实现和 `impl bytes::Buf for &[u8]` 冲突了。
fn remaining(&self) -> usize {
self.len()
}
}
```

如果新 trait 中任一个方法的名称与该类型现有的方法名称发生冲突,编译器就没法明确地识别出要调用的是哪个方法:

```rust
let arr = [1u8, 2u8, 3u8];
let v = &arr[1..];

assert_eq!(v.remaining(), 2);
```

就像编译时候的错误显示那样:

```rust
error[E0034]: multiple applicable items in scope
--> src/main.rs:40:18
|
40 | assert_eq!(v.remaining(), 2);
| ^^^^^^^^^ multiple `remaining` found
|
note: candidate #1 is defined in an impl of the trait `BytesLeft` for the
type `&[u8]`
--> src/main.rs:18:5
|
18 | fn remaining(&self) -> usize {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
= note: candidate #2 is defined in an impl of the trait `bytes::Buf` for the
type `&[u8]`
help: disambiguate the method for candidate #1
|
40 | assert_eq!(BytesLeft::remaining(&v), 2);
| ~~~~~~~~~~~~~~~~~~~~~~~~
help: disambiguate the method for candidate #2
|
40 | assert_eq!(bytes::Buf::remaining(&v), 2);
| ~~~~~~~~~~~~~~~~~~~~~~~~~
```

因此,你应该**避免从你无法控制的 crate 中进行通配符导入**

如果你可以控制被通配符导入的项目的代码,那么之前提到的问题就消失了。例如,`test` 模块通常会使用 `use super::*;`。对于主要通过模块来划分代码的 crate 来说,从内部模块进行通配符导入也是一种可能的场景:

```rust
mod thing;
pub use thing::*;
```

然而,还有另一种常见的例外情况也是适用通配符导入的。有一些 crate 遵循一个约定,crate 中常见的条目会通过 *prelude* 模块重新导出,而这个就是特地被用于使用通配符导入的:

```rust
use thing::prelude::*;
```

尽管从理论上来讲这种场景也会出现上面提及过的问题,但实际上这种 prelude 模块大多经过精心的设计,这样使用带来的更高的便利性可能会远超过未来出现问题的小概率风险。

最后,如果你不打断遵循这个条款的建议,**考虑将你使用通配符引入的依赖项固定到一个特定的版本上(见 [第 21 条]**,让依赖项不会自动升级次要版本。

[第 21 条]:https://www.lurklurk.org/effective-rust/semver.html

0 comments on commit cdfb267

Please sign in to comment.