上一篇文章-改进rust代码的35种具体方法-类型(二十二)-最小化可见度
Rust的use
语句从另一个板条箱或模块中提取命名项目,并使该名称无需条件即可在本地模块的代码中使用。表单的通配符导入(或glob导入)use somecrate::module::*
表示,该模块的每个公共符号都应添加到本地命名空间中。
作为次要版本升级的一部分,外部板条箱可能会将新项目添加到其API中;这被视为向后兼容的更改。
这两个观察结果的结合让人担心,对依赖项的不间断性更改可能会破坏您的代码:如果依赖项添加一个与您已经使用的名称相冲突的新符号,会发生什么?
在最简单的层面上,事实证明这不是问题:通配符导入中的名称被视为优先级较低,因此代码中的任何匹配名称都优先:
use bytes::*;
// Local `Bytes` type does not clash with `bytes::Bytes`.
struct Bytes(Vec<u8>);
不幸的是,仍然有冲突可能发生的情况。例如,考虑依赖项添加新特征并为某种类型实现它的情况:
trait BytesLeft {
// Name clashes with the `remaining` method on the wildcard-imported
// `bytes::Buf` trait.
fn remaining(&self) -> usize;
}
impl BytesLeft for &[u8] {
// Implementation clashes with `impl bytes::Buf for &[u8]`.
fn remaining(&self) -> usize {
self.len()
}
}
如果新特征中的任何方法名称与适用于该类型的现有方法名称相冲突,那么编译器就无法再明确确定是哪种方法:
let arr = [1u8, 2u8, 3u8];
let v = &arr[1..];
assert_eq!(v.remaining(), 2);
如编译时错误所示:
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,也可能会从内部模块进行通配符导入。
mod thing;
pub use thing::*;
然而,另一个常见的例外情况是,通配符导入是有意义的。一些 crate 采用惯例,将 crate 的常用项从一个预导模块重新导出,该模块明确地用于通配符导入:
use thing::prelude::*;
虽然理论上同样的担忧也适用于这种情况,但在实践中,这样的前奏模块可能会被精心策划,更高的便利性可能超过未来问题的小风险。
最后,如果您没有遵循此项中的建议,请考虑将通配符导入的依赖项固定到精确版本,这样就不会自动允许对依赖项进行次要版本升级。