- Feature Name: N/A
- Start Date: 2022-01-19
- RFC PR: rust-lang/rfcs#3373
- RFC PR #2: rust-lang/rfcs#3581
- Tracking Issue: rust-lang/rust#120363
Add a warn-by-default lint for items inside functions or expressions that implement methods or traits that are visible outside the function or expression. Consider ramping that lint to deny-by-default for Rust 2024, and evaluating a hard error for 2027.
Currently, tools cross-referencing uses and definitions (such as IDEs) must either search inside all function bodies and other expression-containing items to find potential definitions corresponding to uses within another function, or not cross-reference those definitions at all.
Humans cross-referencing such uses and definitions may find themselves similarly baffled by code such as the following:
trait Trait<T> {
fn method(&self) {}
}
struct Foo;
fn _foo() {
struct Bar;
impl Trait<Bar> for Foo {}
}
fn main() {
Foo.method();
}
This change helps humans limit the scope of their search and avoid looking for definitions inside other functions or items, without missing any relevant definitions. If in the future we manage to forbid it entirely within a subsequent Rust edtion, tools will be able to rely on this as well.
An "expression-containing item" is defined as any expression where an item may be defined. For example:
- Functions
- Closures
- The values assigned to
static
items or non-anonymousconst
items. - The discriminant values assigned to
enum
variants
As an exception, anonymous const
items are excluded from this
definition for the purposes of this RFC (see unresolved-questions),
as they're currently commonly used to define items within macros.
Rust will emit a warn-by-default lint when encountering an impl
nested inside
an expression-containing item (through any level of nesting), unless any of the
following are true:
- For
impl T
orimpl TraitPath for T
, the definition of typeT
is also nested inside the same expression-containing item. - For
impl T<X, Y, ..>
orimpl TraitPath for T<X, Y, ..>
, the definition of at least one ofT
,X
,Y
, .. is also nested inside the same expression- containing item. - For
impl Trait for TypePath
orimpl Trait<X, Y, ..> for TypePath
, the definition ofTrait
is also nested inside the same expression-containing item. - For
impl Trait<X, Y, ..> for TypePath
, the definition of at least one ofX
,Y
, .. is also nested inside the same expression-containing item, and the full pathTrait<X, Y, ..>
may not be inferred from outside the expression-containing item, and further that such inferences onTrait
would not be permitted even in the absense of all impls nested in expression- containing items.
For the purposes of the above rules, fundamental types such as &X
and &mut X
are considered parameterized types (T<X>
).
Rust will emit a warn-by-default lint when encountering an exported macro (e.g.
using #[macro_export]
) nested inside an expression-containing item (through
any level of nesting).
In a future edition, we may consider making this lint deny-by-default, or eventually making it a hard error. We'll evaluate the impact on the ecosystem and existing use cases before doing so.
The lint is considered to attach to the impl
token of an impl
block, or the
macro_rules!
token of a macro definition.
Some existing code makes use of this pattern, and would need to migrate to a different pattern. In particular, this pattern may occur in macro-generated code, or in code generated by tools like rustdoc. Making this change would require such code and tools to restructure to meet this requirement.
Other aspects of Rust's design attempt to enable local reasoning and avoid
global reasoning, including non-inference of function signatures, and not
having the function body affect non-opaque properties of impl Trait
uses in
the signature without reflecting those properties in the signature.
Should we flag these definitions in anonymous const
items as well? This is
used in some macro expansions for
compatibility reasons.
Is the last rule regarding parameterized trait impl
items viable to implement?
If in the future Rust provides a "standalone derive
" mechanism (e.g. derive Trait for Type
as a standalone definition separate from Type
), the impl
produced by that mechanism would be subject to the same requirements.