为什么Rust推广使用带有显式导入的use语句?

9

我看到很多Rust代码,其中use语句看起来像这样:

use std::io::net::ip::{SocketAddr, Ipv4Addr};

我的理解是,这个限制了use语句只能导入SocketAddrIpv4Addr

从Java或C#等语言的角度来看,这种做法感觉很奇怪,因为对于这些语言,导入语句总是会导入所有公共类型。

我认为可以在Rust中使用这个语句实现类似的效果。

use std::io::net::ip::*;

我能想到的明确命名的唯一原因是避免两个不同的导入包含具有相同名称的公共API时发生冲突。但是,这可以通过别名来解决,因此我想知道更严格的“仅导入所需内容”的方法是否还有其他优势?


6
另一种操作方式是使用std::io::net::ip。然后在您的代码中通过ip::SocketAddr访问类型。通过保留短类型,您不会冒任何名称空间冲突的风险,即使使用全局导入。 - luke
1
我认为你在写"export"的地方都应该是指"import",或者我没有理解你的意思。编辑:在Java中,你可以使用import com.acme.dynamite.Stickimport com.acme.dynamite.*两种方式导入包,只是偏好不同而已。 - user395760
是的,你说得对。我的措辞在最初的问题中是不正确的。 - Christoph
在Java领域中,@delnan * 导入也不被看好(除非你从一个包中导入了10个或更多类型),最好不要使用。 - Renato
4个回答

9
Rust在这方面受到了Python的启发,二者有一个类似的原则:所有导入都需要显式声明,虽然支持通配符导入(Rust中是use x::*,Python中是from x import *),但一般不推荐使用它们。这种哲学对实践产生了一些影响;例如,在作用域中只有Trait才能调用其方法,因此在导入的traits中存在名称冲突时调用Trait方法会相当困难(将来可以通过统一函数调用语法来改进,这样你就可以调用Trait::function(self)而不仅仅是self.function())。
然而,总的来说,这是Python之禅中很好表达的一点:“显式优于隐式”。当许多东西都在作用域内时,很难看出哪个来自哪里,模块结构的详细知识和/或工具变得非常重要。如果一切都是显式的,那么工具基本上是不必要的,并且在简单的文本编辑器中手动处理文件是完全可行的。当然,工具仍然可以帮助,但并不像之前那样“必不可少”。
这就是为什么Rust采用了Python的显式导入哲学的原因。

6

Huon在邮件中写道:

它们的某些方面会极大地复杂化名称解析算法(就我所知),而且,无论如何,它们对实际代码有各种各样的缺点,例如,在Python中的等效物被视为不好的做法:http://python.net/~goodger/projects/pycon/2007/idiomatic/handout.html#importing

也许在编译型和静态类型语言中它们并不那么糟糕?我不知道;但无论如何,我个人认为没有全局导入的代码更易于阅读,因为我可以很容易地推断出调用哪个函数,而全局导入需要更多的努力。

nikomatsakis在其中写道:

我认为全局导入有其存在的意义。例如,在借用检查器中,有一个浅层次的子模块树,它们都使用在borrowck/mod.rs中定义的数据类型。手动导入所有这些名称似乎相当乏味和愚蠢,而不是写 use rustc::middle::borrowck::*。我知道名称解析算法中存在复杂性,但如果基于基本挑战进行争论,我会更加支持它。

然后这就转到了RFC 305,被steveklabnik拒绝了,但没有评论是否是好的风格:

全局导入现在已经稳定,所以我将关闭此问题。


2
参考资料是很好的,但仅提供链接的答案会被拒之门外。一旦这些链接失效,你的答案将变得毫无价值。如果你希望改善你的回答,那么请直接在这里撰写主要内容(可以引用),并且仅使用链接来支持回答并提供更多信息。 - Matthieu M.

0

要求显式导入使得代码读者能够快速而精确地看到本地符号对应的是哪个东西。这通常帮助他们直接导航到该代码。

use somelib:*;
use otherlib:*;

// Where does SomeType come from? Good luck finding it in a large
// project if you're new.
fn is_brassy(input: SomeType) {
  return input.has_brass;
}

此外,通配符导入可能会隐藏未声明的依赖项。如果某些代码没有声明其依赖项,但总是在通配符导入中与其依赖项一起导入,则该代码将正常运行。但是,在孤立地查看该代码文件的读者可能会对符号的来源感到困惑。
(以下代码尝试使用类似Python的伪代码演示问题,因为我更擅长使用Python表达我的思想,尽管我实际上看到这个问题的时间是使用Rust宏。)
# helpers
def helpful_helper(x):
  return x + ' world'

# helpless
def needs_help(x):
  # helpful_helper not explicitly imported!
  return helpful_helper(x)

# lib
from helpers import *
from helpless import *

# app
from lib import *

needs_help('hello')

如果使用不同的导入方式,甚至可能会使用不同的依赖项。

# other_app
from other_helpers import *
from helpless import *

# what does it do? Better look at other_helpers.py...
needs_help('hello')

0

全局导入是开发阶段方便开发人员的一种方式,可以一次性导入所有内容,因为在此阶段逐个导入每个项可能会很麻烦。它们是在专门为此目的设计的 prelude 模块下导出。

一旦完成逻辑,应将全局导入替换为明确的导入,因为全局导入难以跟踪,需要额外的努力来确定每个项的来源。

它们在Rust中被称为通配符导入。

它们不受欢迎,因为:

  • 可能污染命名空间
  • 可能导致冲突并导致混淆错误
  • 与工具相关的宏问题

我认为这与Python无关。

他们的废弃论点具有较强的优点,但废弃通配符导入可能不可行,并且会影响开发人员的生产力,但是可以设置clippy lints。

warn-on-all-wildcard-imports: bool: 是否允许某些通配符导入(prelude,测试中的super)。 (默认为false)。

了解更多 https://rust-lang.github.io/rust-clippy/master/index.html#wildcard_imports


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接