名称解析
_名称解析_是将路径和其他标识符绑定到这些实体的声明的过程。名称被分成不同的命名空间,允许不同命名空间中的实体共享相同的名称而不冲突。每个名称在作用域内有效,即可以引用该名称的源文本区域。对名称的访问可能会根据其可见性受到限制。
名称解析在整个编译过程中分为三个阶段。第一阶段,展开时解析,解析所有 [use 声明]和宏调用。第二阶段,主要解析,解析所有尚未解析且不依赖于类型信息来解析的名称。最后阶段,类型相关解析,一旦类型信息可用,就解析剩余的名称。
Note
展开时解析也称为早期解析。主要解析也称为晚期解析。
通用
本节中的规则适用于名称解析的所有阶段。
作用域
Note
这是关于各种作用域内名称解析的未来扩展的占位符。
展开时名称解析
展开时名称解析是完成宏展开并完全生成 crate 的 AST 所需的名称解析阶段。此阶段需要解析宏调用和 use 声明。解析 use 声明是通过基于路径的作用域解析的宏调用所必需的。解析宏调用是为了展开它们。
展开时名称解析后,AST 不得包含任何未展开的宏调用。每个宏调用都解析为最终 AST 或外部 crate 中存在的有效定义。
#![allow(unused)]
fn main() {
m!(); // ERROR: Cannot find macro `m` in this scope.
}
名称的解析必须是稳定的。展开后,完全展开的 AST 中的名称必须解析到相同的定义,无论宏展开和导入解析的顺序如何。
在宏展开期间选择的所有名称解析候选都被视为投机性的。一旦 crate 完全展开,所有投机性导入解析都会被验证,以确保宏展开没有引入任何新的歧义。
Note
由于宏展开的迭代性质,这会导致所谓的时空旅行歧义,例如当宏或 glob 导入引入一个与其自身基本路径有歧义的项时。
fn main() {} macro_rules! f { () => { mod m { pub(crate) use f; } } } f!(); const _: () = { // Initially, we speculatively resolve `m` to the module in // the crate root. // // Expansion of `f` introduces a second `m` module inside this // body. // // Expansion-time resolution finalizes resolutions by re- // resolving all imports and macro invocations, sees the // introduced ambiguity and reports it as an error. m::f!(); // ERROR: `m` is ambiguous. };
导入
所有 use 声明在此解析阶段完全解析。类型相关路径在此阶段无法解析,将产生错误。
#![allow(unused)]
fn main() {
mod m {
pub const C: () = ();
pub enum E { V }
pub type A = E;
impl E {
pub const C: () = ();
}
}
// Valid imports resolved at expansion-time:
use m::C; // OK.
use m::E; // OK.
use m::A; // OK.
use m::E::V; // OK.
// Valid expressions resolved during type-relative resolution:
let _ = m::A::V; // OK.
let _ = m::E::C; // OK.
}
#![allow(unused)]
fn main() {
mod m {
pub const C: () = ();
pub enum E { V }
pub type A = E;
impl E {
pub const C: () = ();
}
}
// Invalid type-relative imports that can't resolve at expansion-time:
use m::A::V; // ERROR: Unresolved import `m::A::V`.
use m::E::C; // ERROR: Unresolved import `m::E::C`.
}
在外部作用域中通过 use 声明引入的名称会被来自内部作用域的同名同命名空间候选遮蔽,除非受到名称解析歧义的限制。
#![allow(unused)]
fn main() {
pub mod m1 {
pub mod ambig {
pub const C: u8 = 1;
}
}
pub mod m2 {
pub mod ambig {
pub const C: u8 = 2;
}
}
// This introduces the name `ambig` in the outer scope.
use m1::ambig;
const _: () = {
// This shadows `ambig` in the inner scope.
use m2::ambig;
// The inner candidate is selected here
// as the resolution of `ambig`.
use ambig::C;
assert!(C == 2);
};
}
在以下情况下,允许在单个作用域内遮蔽通过 use 声明引入的名称:
歧义
在展开时解析期间,存在某些情况,其中存在多个宏定义、use 声明或模块,导入或宏调用的名称可能引用它们,但编译器无法一致地确定哪个候选应该遮蔽另一个。在这些情况下不允许遮蔽,编译器会发出歧义错误。
名称不能通过有歧义的 glob 导入解析。只要名称未被使用,glob 导入允许在同一命名空间中导入冲突的名称。来自有歧义 glob 导入的具有冲突候选的名称仍然可以被非 glob 导入遮蔽,并且可以在不产生错误的情况下使用。错误在使用时发生,而不是在导入时。
#![allow(unused)]
fn main() {
mod m1 {
pub struct Ambig;
}
mod m2 {
pub struct Ambig;
}
// OK: This brings conficting names in the same namespace into scope
// but they have not been used yet.
use m1::*;
use m2::*;
const _: () = {
// The error happens when the name with the conflicting candidates
// is used.
let x = Ambig; // ERROR: `Ambig` is ambiguous.
};
}
#![allow(unused)]
fn main() {
mod m1 {
pub struct Ambig;
}
mod m2 {
pub struct Ambig;
}
use m1::*;
use m2::*; // OK: No name conflict.
const _: () = {
// This is permitted, since resolution is not through the
// ambiguous globs.
struct Ambig;
let x = Ambig; // OK.
};
}
多个 glob 导入允许导入相同的名称,并且如果导入的是同一个项(遵循重导出),则允许使用该名称。名称的可见性是导入的最大可见性。
mod m1 {
pub struct Ambig;
}
mod m2 {
// This reexports the same `Ambig` item from a second module.
pub use super::m1::Ambig;
}
mod m3 {
// These both import the same `Ambig`.
//
// The visibility of `Ambig` is `pub` because that is the
// maximum visibility between these two `use` declarations.
pub use super::m1::*;
use super::m2::*;
}
mod m4 {
// `Ambig` can be used through the `m3` globs and still has
// `pub` visibility.
pub use crate::m3::Ambig;
}
const _: () = {
// Therefore, we can use it here.
let _ = m4::Ambig; // OK.
};
fn main() {}
当外部作用域中有另一个候选可用时,导入和宏调用中的名称不能通过 glob 导入解析。
Note
当
core::panic!或std::panic!之一由于标准库 prelude被带入作用域,并且用户编写的glob 导入将另一个带入作用域时,rustc目前允许使用panic!,即使它是有歧义的。用户编写的 glob 导入优先以解决此歧义。在 Rust 2021 及更高版本中,
core::panic!和std::panic!操作相同。但在早期版本中,它们不同;只有std::panic!接受String作为格式参数。例如,这是错误:
extern crate core; use ::core::prelude::v1::*; fn main() { panic!(std::string::String::new()); // ERROR. }这是被接受的:
#![no_std] extern crate std; use ::std::prelude::v1::*; fn main() { panic!(std::string::String::new()); // OK. }不要依赖此行为;计划是移除它。
有关详细信息,请参阅 Rust issue #147319。
#![allow(unused)]
fn main() {
mod glob {
pub mod ambig {
pub struct Name;
}
}
// Outer `ambig` candidate.
pub mod ambig {
pub struct Name;
}
const _: () = {
// Cannot resolve `ambig` through this glob
// because of the outer `ambig` candidate above.
use glob::*;
use ambig::Name; // ERROR: `ambig` is ambiguous.
};
}
#![allow(unused)]
fn main() {
// As above, but with macros.
pub mod m {
macro_rules! f {
() => {};
}
pub(crate) use f;
}
pub mod glob {
macro_rules! f {
() => {};
}
pub(crate) use f as ambig;
}
use m::f as ambig;
const _: () = {
use glob::*;
ambig!(); // ERROR: `ambig` is ambiguous.
};
}
Note
这些歧义错误特定于展开时解析。在解析的后期阶段,给定名称有多个候选不被视为错误。只要导入本身没有歧义,总会有一个单一的无歧义最近解析。
#![allow(unused)] fn main() { mod glob { pub const AMBIG: u8 = 1; } mod outer { pub const AMBIG: u8 = 2; } use outer::AMBIG; const C: () = { use glob::*; assert!(AMBIG == 1); // ^---- This `AMBIG` is resolved during primary resolution. }; }
名称不能通过有歧义的宏重导出解析。当宏重导出会遮蔽外部作用域中同名的文本宏候选时,宏重导出是有歧义的。
#![allow(unused)]
fn main() {
// Textual macro candidate.
macro_rules! ambig {
() => {}
}
// Path-based macro candidate.
macro_rules! path_based {
() => {}
}
pub fn f() {
// This reexport of the `path_based` macro definition
// as `ambig` may not shadow the `ambig` macro definition
// which is resolved via textual macro scope.
use path_based as ambig;
ambig!(); // ERROR: `ambig` is ambiguous.
}
}
Note
由于编译器中的实现细节,特别是当前的作用域访问逻辑和支持此行为的复杂性,需要此限制。此歧义错误可能在将来被移除。
宏
宏通过遍历可用作用域以找到可用候选来解析。宏分为两个子命名空间,一个用于函数式宏,另一个用于属性和派生。来自错误子命名空间的解析候选被忽略。
可用的作用域类型按以下顺序访问。每种作用域类型代表一个或多个作用域。
Note
编译器将尝试解析在其关联宏将其引入作用域之前使用的派生辅助。此作用域在正确在作用域内的解析派生辅助候选的作用域之后访问。此行为计划被移除。
有关更多信息,请参阅派生辅助作用域。
Note
此访问顺序可能在将来更改,例如根据词法作用域交错访问文本和基于路径的作用域候选。
2018 Edition differences
从 2018 版本开始,当存在
#[no_implicit_prelude]时,不访问#[macro_use]prelude。
名称 cfg 和 cfg_attr 在宏属性子命名空间中是保留的。
歧义
名称不能通过宏展开内的有歧义候选解析。当宏展开内的候选会遮蔽来自第一个候选的宏展开外部的同名候选,并且被解析的名称的调用也来自第一个候选的宏展开外部时,宏展开内的候选是有歧义的。
#![allow(unused)]
fn main() {
macro_rules! define_ambig {
() => {
macro_rules! ambig {
() => {}
}
}
}
// Introduce outer candidate definition for `ambig` macro invocation.
macro_rules! ambig {
() => {}
}
// Introduce a second candidate definition for `ambig` inside of a
// macro expansion.
define_ambig!();
// The definition of `ambig` from the second invocation
// of `define_ambig` is the innermost canadidate.
//
// The definition of `ambig` from the first invocation of
// `define_ambig` is the second candidate.
//
// The compiler checks that the first candidate is inside of a macro
// expansion, that the second candidate is not from within the same
// macro expansion, and that the name being resolved is not from
// within the same macro expansion.
ambig!(); // ERROR: `ambig` is ambiguous.
}
反向不被视为有歧义。
#![allow(unused)]
fn main() {
macro_rules! define_ambig {
() => {
macro_rules! ambig {
() => {}
}
}
}
// Swap order of definitions.
define_ambig!();
macro_rules! ambig {
() => {}
}
// The innermost candidate is now less expanded so it may shadow more
// the macro expanded definition above it.
ambig!();
}
如果被解析的调用在最内层候选的展开内,也不被视为有歧义。
#![allow(unused)]
fn main() {
macro_rules! ambig {
() => {}
}
macro_rules! define_and_invoke_ambig {
() => {
// Define innermost candidate.
macro_rules! ambig {
() => {}
}
// Invocation of `ambig` is in the same expansion as the
// innermost candidate.
ambig!(); // OK
}
}
define_and_invoke_ambig!();
}
即使两个定义来自同一个宏的调用,最外层的候选仍然被认为是“较少展开的“,因为它不在包含最内层候选定义的展开内。
#![allow(unused)]
fn main() {
macro_rules! define_ambig {
() => {
macro_rules! ambig {
() => {}
}
}
}
define_ambig!();
define_ambig!();
ambig!(); // ERROR: `ambig` is ambiguous.
}
这也适用于导入,只要名称的最内层候选来自宏展开内。
#![allow(unused)]
fn main() {
macro_rules! define_ambig {
() => {
mod ambig {
pub struct Name;
}
}
}
mod ambig {
pub struct Name;
}
const _: () = {
// Introduce innermost candidate for
// `ambig` mod in this macro expansion.
define_ambig!();
use ambig::Name; // ERROR: `ambig` is ambiguous.
};
}
用户定义的属性或派生宏不能遮蔽内置的非宏属性(例如 inline)。
// with-helper/src/lib.rs
use proc_macro::TokenStream;
#[proc_macro_derive(WithHelperAttr, attributes(non_exhaustive))]
// ^^^^^^^^^^^^^^
// User-defined attribute candidate.
// ...
pub fn derive_with_helper_attr(_item: TokenStream) -> TokenStream {
TokenStream::new()
}
// src/lib.rs
#[derive(with_helper::WithHelperAttr)]
#[non_exhaustive] // ERROR: `non_exhaustive` is ambiguous.
struct S;
Note
无论内置属性是什么名称的候选,这都适用:
// with-helper/src/lib.rs use proc_macro::TokenStream; #[proc_macro_derive(WithHelperAttr, attributes(helper))] // ^^^^^^ // User-defined attribute candidate. // ... pub fn derive_with_helper_attr(_item: TokenStream) -> TokenStream { TokenStream::new() }// src/lib.rs use inline as helper; // ^----- Built-in attribute candidate via reexport. #[derive(with_helper::WithHelperAttr)] #[helper] // ERROR: `helper` is ambiguous. struct S;
主要名称解析
Note
这是关于主要名称解析的未来扩展的占位符。
类型相关解析
Note
这是关于类型相关解析的未来扩展的占位符。