Skip to content

Conversation

@Voultapher
Copy link
Contributor

@Voultapher Voultapher commented Apr 7, 2025

Currently all core and std macros are automatically added to the prelude via #[macro_use]. However a situation arose where we want to add a new macro assert_matches but don't want to pull it into the standard prelude for compatibility reasons. By explicitly exporting the macros found in the core and std crates we get to decide on a per macro basis and can later add them via the rust_20xx preludes.

Closes #53977
Unlocks #137487

Reference PR:

Stabilization report lib

Everything N/A or already covered by lang report except, breaking changes: The unstable and never intended for public use format_args_nl macro is no longer publicly accessible as requested by @petrochenkov. Affects <10 crates including dependencies.

Stabilization report lang

Summary

Explicitly export core and std macros.

This change if merged would change the code injected into user crates to no longer include #[macro_use] on extern crate core and extern crate std. This change is motivated by a near term goal and a longer term goal. The near term goal is to allow a macro to be defined at the std or core crate root but not have it be part of the implicit prelude. Such macros can then be separately promoted to the prelude in a new edition. Specifically this is blocking the stabilization of assert_matches #137487. The longer term goal is to gradually deprecate #[macro_use]. By no longer requiring it for standard library usage, this serves as a step towards that goal. For more information see #53977.

PR link: #139493

Tracking:

Reference PRs:

cc @rust-lang/lang @rust-lang/lang-advisors

What is stabilized

Stabilization:

  • #[macro_use] is no longer automatically included in the crate root module. This allows the explicit import of macros in the core and std prelude e.g. pub use crate::dbg;. No code that was previously not accepted will be accepted now.

  • ambiguous_panic_imports lint. No code that was previously not accepted will be accepted now. Code that previously passed without warnings, but included the following or equivalent - only pertaining to core vs std panic - will now receive a warning:

    #![no_std]
    extern crate std;
    use std::prelude::v1::*;
    fn xx() {
        panic!(); // resolves to core::panic
        //~^ WARNING `panic` is ambiguous
        //~| WARNING this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
    }

    This lint is tied to a new exception to the name resolution logic in compiler/rustc_resolve/src/ident.rs similar to an exception added for Glob import causes ambiguity on nightly #145575. Specifically this only happens if the import of two builtin macros is ambiguous and they are named sym::panic. I.e. this can only happen for core::panic and std::panic. While there are some tiny differences in what syntax is allowed in std::panic vs core::panic in editions 2015 and 2018, see. The behavior at runtime will always be the same if it compiles, implying minimal risk in what specific macro is resolved. At worst some closed source project not captured by crater will stop compiling because a different panic is resolved than previously and they were using obscure syntax like panic!(&String::new()).

Design

N/A

Reference

What updates are needed to the Reference? Link to each PR. If the Reference is missing content needed for describing this feature, discuss that.

RFC history

What RFCs have been accepted for this feature?

N/A

Answers to unresolved questions

N/A

Post-RFC changes

What other user-visible changes have occurred since the RFC was accepted? Describe both changes that the lang team accepted (and link to those decisions) as well as changes that are being presented to the team for the first time in this stabilization report.

N/A

Key points

What decisions have been most difficult and what behaviors to be stabilized have proved most contentious? Summarize the major arguments on all sides and link to earlier documents and discussions.

  • Nothing was really contentious.

Nightly extensions

Are there extensions to this feature that remain unstable? How do we know that we are not accidentally committing to those?

N/A

Doors closed

What doors does this stabilization close for later changes to the language? E.g., does this stabilization make any other RFCs, lang experiments, or known in-flight proposals more difficult or impossible to do later?

No known doors are closed.

Feedback

Call for testing

Has a "call for testing" been done? If so, what feedback was received?

No.

Nightly use

Do any known nightly users use this feature? Counting instances of #![feature(FEATURE_NAME)] on GitHub with grep might be informative.

N/A

Implementation

Major parts

Summarize the major parts of the implementation and provide links into the code and to relevant PRs.

See, e.g., this breakdown of the major parts of async closures:

The key change is compiler/rustc_builtin_macros/src/standard_library_imports.rs removing the macro_use inject and the v1.rs preludes now explicitly pub useing the macros https://github.com/rust-lang/rust/pull/139493/files#diff-a6f9f476d41575b19b399c6d236197355556958218fd035549db6d584dbdea1d + https://github.com/rust-lang/rust/pull/139493/files#diff-49849ff961ebc978f98448c8990cf7aae8e94cb03db44f016011aa8400170587.

Coverage

Summarize the test coverage of this feature.

Consider what the "edges" of this feature are. We're particularly interested in seeing tests that assure us about exactly what nearby things we're not stabilizing. Tests should of course comprehensively demonstrate that the feature works. Think too about demonstrating the diagnostics seen when common mistakes are made and the feature is used incorrectly.

Within each test, include a comment at the top describing the purpose of the test and what set of invariants it intends to demonstrate. This is a great help to our review.

Describe any known or intentional gaps in test coverage.

Contextualize and link to test folders and individual tests.

A variety of UI tests including edge cases have been added.

Outstanding bugs

What outstanding bugs involve this feature? List them. Should any block the stabilization? Discuss why or why not.

An old bug is made more noticeable by this change #145577 but it was recommended to not block on it #139493 (comment).

Outstanding FIXMEs

What FIXMEs are still in the code for that feature and why is it OK to leave them there?

// Turn ambiguity errors for core vs std panic into warnings.
// FIXME: Remove with lang team approval.

https://github.com/rust-lang/rust/pull/139493/files#diff-c046507afdba3b0705638f53fffa156cbad72ed17aa01d96d7bd1cc10b8d9bce

Tool changes

What changes must be made to our other tools to support this feature. Has this work been done? Link to any relevant PRs and issues.

  • rustfmt
  • rust-analyzer
  • rustdoc (both JSON and HTML)
  • cargo
  • clippy
  • rustup
  • docs.rs

No known changes needed or expected.

Breaking changes

If this stabilization represents a known breaking change, link to the crater report, the analysis of the crater report, and to all PRs we've made to ecosystem projects affected by this breakage. Discuss any limitations of what we're able to know about or to fix.

Breaking changes:

  • It's possible for user code to invoke an ambiguity by defining their own macros with standard library names and glob importing them, e.g. use nom::* importing nom::dbg. In practice this happens rarely based on crater data. The 3 public crates where this was an issue, have been fixed. The ambiguous panic import is more common and affects a non-trivial amount of the public - and likely private - crate ecosystem. To avoid a breaking change, a new future incompatible lint was added ambiguous_panic_imports see Tracking Issue for future-incompatibility lint ambiguous_panic_imports #147319. This allows current code to continue compiling, albeit with a new warning. Future editions of Rust make this an error and future versions of Rust can choose to make this error. Technically this is a breaking change, but crater gives us the confidence that the impact will be at worst a new warning for 99+% of public and private crates.

    #![no_std]
    extern crate std;
    use std::prelude::v1::*;
    fn xx() {
        panic!(); // resolves to core::panic
        //~^ WARNING `panic` is ambiguous
        //~| WARNING this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
    }
  • Code using #![no_implicit_prelude] and Rust edition 2015 will no longer automatically have access to the prelude macros. The following works on nightly by would stop working with this change:

    #![no_implicit_prelude]
    // Uncomment to fix error.
    // use std::{panic, vec};
    fn main() {
        let _ = vec![3, 6];
        panic!();
    }

Crater found no instance of this pattern in use. Affected code can fix the issue by directly importing the macros. The new behavior matches the behavior of #![no_implicit_prelude] in Rust editions 2018 and beyond and it's intuitive meaning.

Crater report:

Crater analysis:

  • Discussed in breaking changes.

PRs to affected crates:

Type system, opsem

Compile-time checks

What compilation-time checks are done that are needed to prevent undefined behavior?

Link to tests demonstrating that these checks are being done.

N/A

Type system rules

What type system rules are enforced for this feature and what is the purpose of each?

N/A

Sound by default?

Does the feature's implementation need specific checks to prevent UB, or is it sound by default and need specific opt-in to perform the dangerous/unsafe operations? If it is not sound by default, what is the rationale?

N/A

Breaks the AM?

Can users use this feature to introduce undefined behavior, or use this feature to break the abstraction of Rust and expose the underlying assembly-level implementation? Describe this if so.

N/A

Common interactions

Temporaries

Does this feature introduce new expressions that can produce temporaries? What are the scopes of those temporaries?

N/A

Drop order

Does this feature raise questions about the order in which we should drop values? Talk about the decisions made here and how they're consistent with our earlier decisions.

N/A

Pre-expansion / post-expansion

Does this feature raise questions about what should be accepted pre-expansion (e.g. in code covered by #[cfg(false)]) versus what should be accepted post-expansion? What decisions were made about this?

N/A

Edition hygiene

If this feature is gated on an edition, how do we decide, in the context of the edition hygiene of tokens, whether to accept or reject code. E.g., what token do we use to decide?

N/A

SemVer implications

Does this feature create any new ways in which library authors must take care to prevent breaking downstreams when making minor-version releases? Describe these. Are these new hazards "major" or "minor" according to RFC 1105?

No.

Exposing other features

Are there any other unstable features whose behavior may be exposed by this feature in any way? What features present the highest risk of that?

No.

History

List issues and PRs that are important for understanding how we got here.

Acknowledgments

Summarize contributors to the feature by name for recognition and so that those people are notified about the stabilization. Does anyone who worked on this not think it should be stabilized right now? We'd like to hear about that if so.

More or less solo developed by @Voultapher with some help from @petrochenkov.

Open items

List any known items that have not yet been completed and that should be before this is stabilized.

None.

@rustbot
Copy link
Collaborator

rustbot commented Apr 7, 2025

r? @ChrisDenton

rustbot has assigned @ChrisDenton.
They will have a look at your PR within the next two weeks and either review your PR or reassign to another reviewer.

Use r? to explicitly pick a reviewer

@rustbot rustbot added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. T-libs Relevant to the library team, which will review and decide on the PR/issue. labels Apr 7, 2025
@Voultapher
Copy link
Contributor Author

r? @Amanieu

@rust-log-analyzer

This comment has been minimized.

@rust-log-analyzer

This comment has been minimized.

@Voultapher
Copy link
Contributor Author

@Amanieu the tidy issue highlights an annoying and unforeseen side-effect of this change. The vec module is now part of the prelude. In effect this means that for example this code:

fn xx(i: vec::IntoIter<i32>) {
    let _ = i.as_slice();
}

fn main() {}

that currently doesn't compile on stable would now compile. Initially I thought this would cause name collisions if users define their own vec module but so far I wasn't able to produce those, it seems to always prefer the local module. But regardless, I think we don't want to allow access to a standard library namespace without going through std, alloc or core. AFAIK there is no way to pub use only the macro and not the module namespace without modifications. I have two ideas how to tackle this, maybe we can rename vec to vec_xx internally and have separate use expressions or we have to add another crate that we can #[macro_use] inject into the prelude that only contains the vec macro. Thoughts?

@traviscross
Copy link
Contributor

@petrochenkov
Copy link
Contributor

There's an issue for this change - #53977.

@dtolnay
Copy link
Member

dtolnay commented Apr 8, 2025

@Voultapher, avoiding the vec module re-export can be done like this:

#[macro_export]
macro_rules! myvec {
    () => {};
}

pub mod myvec {
    pub struct Vec;
}

pub mod prelude {
    // Bad: re-exports both macro and type namespace
    // pub use crate::myvec;
    
    mod vec_macro_only {
        #[allow(hidden_glob_reexports)]
        mod myvec {}
        pub use crate::*;
    }
    pub use self::vec_macro_only::myvec;
}

fn main() {
    prelude::myvec!();
    let _: prelude::myvec::Vec; // error
}

https://play.rust-lang.org/?version=stable&mode=debug&edition=2024&gist=5e50828c593e04ba0e98f48c9d8696b4

@Voultapher
Copy link
Contributor Author

I've applied the suggestion by @dtolnay local tests seem promising. @Kobzol could we please do a timer run to see if this PR impacts compile-times.

@petrochenkov
Copy link
Contributor

env and panic (and maybe something else now?) need to be treated in the same way as vec.

@rust-log-analyzer

This comment has been minimized.

@Kobzol
Copy link
Member

Kobzol commented Apr 8, 2025

@Voultapher Based on the CI failure I think that a try build would fail now.

@Voultapher
Copy link
Contributor Author

Ok, I'll try to get the CI passing first.

@Voultapher
Copy link
Contributor Author

@petrochenkov I went through all macros and searched the docs and env and panic seem to be the only other ones affected.

@rust-log-analyzer

This comment has been minimized.

@Voultapher
Copy link
Contributor Author

@Amanieu this program previously worked:

use std::*;

fn main() {
    panic!("panic works")
}

and now runs into:

error[E0659]: `panic` is ambiguous
   --> src/main.rs:4:5
    |
4   |     panic!("panic works")
    |     ^^^^^ ambiguous name
    |
    = note: ambiguous because of a conflict between a name from a glob import and an outer scope during import or macro resolution
note: `panic` could refer to the macro imported here
   --> src/main.rs:1:5
    |
1   | use std::*;
    |     ^^^^^^
    = help: consider adding an explicit import of `panic` to disambiguate
    = help: or use `crate::panic` to refer to this macro unambiguously
note: `panic` could also refer to the macro defined here
   --> rust/library/std/src/prelude/mod.rs:157:13
    |
157 |     pub use super::v1::*;
    |             ^^^^^^^^^

I don't see how we can resolve that without changing language import rules and or special casing the prelude import.

@Amanieu
Copy link
Member

Amanieu commented Apr 9, 2025

@petrochenkov Do you have any ideas about that?

@petrochenkov petrochenkov self-assigned this Apr 9, 2025
@petrochenkov
Copy link
Contributor

Could you add a test making sure that the modules vec, env and panic are not in prelude?

@petrochenkov
Copy link
Contributor

@petrochenkov Do you have any ideas about that?

The ambiguity wouldn't happen if it was the same panic in std root and in the stdlib prelude.
However, std and core have two different panic macros.

Previously #[macro_use] extern crate std; would add the std's panic to macro_use prelude, and #[macro_use] extern crate core; would add the core's panic.
This PR always adds the core's panic.

@petrochenkov petrochenkov added S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Apr 10, 2025
@traviscross
Copy link
Contributor

traviscross commented Nov 16, 2025

The question is, "what is the exact mechanism and set of rules that cause this to be accepted -- which resolution is preferred and under what circumstances -- and are these being newly added or exposed to stable by this PR?".

That is, if we're adding a rule to name resolution such as, "if panic is glob imported from std/core as a #[prelude_import] and separately glob imported from std/core not as a #[prelude_import], then prefer X (and issue a FCW)", the "prefer X" part of it and the exact preconditions need to be documented in the stabilization report. Thinking about it now, I'd actually like to see this in the Reference as well. There's just a stub there now for name resolution, but that's no reason not to add this rule there; simply add whatever the exact rule is here to that stub chapter (and it'll be incorporated later with the other name resolution rules as the chapter is filled out by @yaahc).

Above, in an example, you suggest that the resolution in this case will be in favor of core::panic:

#![no_std]
extern crate std;
use std::prelude::v1::*;
fn xx() {
    panic!(); // resolves to core::panic
    //~^ WARNING `panic` is ambiguous
    //~| WARNING this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
}

In testing, for Rust 2018, I'm seeing this expansion:

#![feature(prelude_import)]
#![no_std]
extern crate core;
#[prelude_import]
use core::prelude::rust_2018::*;
extern crate std;
use std::prelude::v1::*;
fn xx() {
     // resolves to core::panic
    { ::std::rt::begin_panic("explicit panic") };
}

(And similarly in Rust 2015.)

What's happening here?

@Voultapher
Copy link
Contributor Author

I've updated the stabilization report..

@bors
Copy link
Collaborator

bors commented Nov 19, 2025

☔ The latest upstream changes (presumably #148434) made this pull request unmergeable. Please resolve the merge conflicts.

@yaahc
Copy link
Member

yaahc commented Nov 24, 2025

@traviscross can you explain how you got that expansion? I tried reproducing your results on play.rust-lang.org and got a result consistent with what @Voultapher described:

https://play.rust-lang.org/?version=nightly&mode=debug&edition=2018&gist=73d1744acdbfac3330817139a512c40f

Notably, your expansion lacks #[macro_use]. My best guess is you did the expansion using a compiler with the changes from this PR included, is that right?


As for explaining things in the reference and what the hack is doing, I think I'm at the point where I have a good explanation though I want to go ahead and pull @Voultapher's code and test some expectations to make sure I'm correct on everything.

The main question I'm digging into right now is "Is it true that we didn't previously have panic resolution ambiguity because the macrouseprelude is higher priority than stdlibprelude?"

My gut says no. Unless I'm missing something we don't currently export the panic macro from the stdlibprelude at all, only the macrouseprelude, so prior to this change when resolving the panic macro in the example given we'd only ever get one candidate, the one from the macrouseprelude.

Even if there were two candidates, I think it would behave as described but not for the reason given. It's not that "one is higher priority than the other". Its true that the macrouseprelude is visited first and is treated as the innermost candidate but if they both have a candidate what matters is whether or not shadowing is permitted. I'm pretty sure it would be permitted but only because one of the resolutions would be via a glob import, I thiiink the macrouseprelude resolution would count as an "item in the same scope" for the purposes of https://doc.rust-lang.org/reference/items/use-declarations.html#r-items.use.glob.shadowing.

This change moving macros out of the macrouseprelude and into the various stdlibpreludes is what causes the ambiguities. In the problematic example we're going from:

  • implicit core macro use prelude (core::panic lives here)
  • explicit std prelude glob import (no panics live here)

to

  • implicit core prelude glob import (core::panic lives here)
  • explicit std prelude glob import (std::panic lives here)

which then immediately runs afoul of the globvsglob ambiguity error: https://github.com/rust-lang/reference/pull/2055/files#diff-38ffafa88c66eaae73091845711d30bcd6058fcce156d28ebbf692916a55bcc6R101

As I understand things the only thing the "hack" in this PR is doing is downgrading the ambiguity error to a warning specifically when both candidates are from the builtin panic macros. One thing I want to test to verify this is if I can trigger the warning downgrade logic independently of the preludes. I expect that by creating two new modules, one of which imports std::panic, and the other importing core::panic, if I glob import both and call panic it should also downgrade that ambiguity error to a warning (I also want to see if it matters if I re-export the macros themselves under different names or if I define custom macros with the same name, my guess is that it won't matter and everything will be well behaved as implemented).

@traviscross
Copy link
Contributor

can you explain how you got that expansion? ...My best guess is you did the expansion using a compiler with the changes from this PR included, is that right?

That's correct. The expansion was produced by the code in this PR.

@traviscross
Copy link
Contributor

traviscross commented Nov 24, 2025

Thanks @yaahc for investigating here. One observation for your consideration on that:

The stabilization report says:

No code that was previously not accepted will be accepted now.

However, this program is rejected on nightly but accepted (with a warning) on this branch:

use ::core::*;
fn f() {
    panic!();
}

Similarly, this is rejected on nightly but accepted (with a warning) under this branch:

#![no_std]
extern crate std;
use ::std::*;
fn f() {
    panic!();
}

@traviscross
Copy link
Contributor

This code goes from failing on nightly to passing on this PR in Rust 2018:

#![no_std]
extern crate std;
use ::std::prelude::v1::*;
fn f() {
    panic!(std::string::String::new());
}

@Voultapher
Copy link
Contributor Author

Voultapher commented Nov 25, 2025

Good catch, indeed there are programs that are now accepted that weren't previously, I got that part wrong. I'll update the stabilization report to reflect this, but I'm unsure how best to do it.

== extern_prelude_flag_binding
&& extern_prelude_item_binding.is_some()
&& extern_prelude_item_binding != Some(innermost_binding);
let is_issue_145575_hack = || {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was this changed to a closure to avoid evaluating the expression if kind is None?

Comment on lines +67 to +68
// These macros needs special handling, so that we don't export it *and* the modules of the same
// name. We only want the macro in the prelude.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clarification because I was confused at first about what this was doing until I read the comment thread:

Suggested change
// These macros needs special handling, so that we don't export it *and* the modules of the same
// name. We only want the macro in the prelude.
// These macros needs special handling, so that we don't export it *and* the modules of the same
// name. We only want the macros in the prelude so we shadow the original modules with private
// modules with the same names.

@yaahc
Copy link
Member

yaahc commented Dec 1, 2025

As I understand things the only thing the "hack" in this PR is doing is downgrading the ambiguity error to a warning specifically when both candidates are from the builtin panic macros. One thing I want to test to verify this is if I can trigger the warning downgrade logic independently of the preludes. I expect that by creating two new modules, one of which imports std::panic, and the other importing core::panic, if I glob import both and call panic it should also downgrade that ambiguity error to a warning (I also want to see if it matters if I re-export the macros themselves under different names or if I define custom macros with the same name, my guess is that it won't matter and everything will be well behaved as implemented).

building on this:

So it turns check of orig_ident.name matches sym::panic does depend specifically upon the name being looked up, not the name of the underlying resolution candidates. That means with this change

This produces an error:

#![no_std]

extern crate std;
mod m1 {
    pub use std::prelude::v1::panic as p;
}

mod m2 {
    pub use core::prelude::v1::panic as p;
}

fn xx() {
    use m1::*;
    use m2::*;
    p!();
}

And this triggers the future compat warning downgrade because both macros are considered builtins and they're being resolved through the panic ident:

#![no_std]

extern crate std;
mod m1 {
    pub use std::prelude::v1::env as panic;
}
use m1::*;

fn xx() {
    panic!();
}

@yaahc
Copy link
Member

yaahc commented Dec 1, 2025

which candidate is being selected when we downgrade an ambiguity to a warning?

The answer is "the first candidate visited" which will always be the explicit glob import, never the prelude import (assuming there is a prelude import, I'm going to explore how #[no_implicit_prelude] impacts behavior next).

So in addition to the example @traviscross gave where , where the code fails on nightly but passes on 2018 w/ this PR. The inverse example w/ the prelude import coming from std and the explicit glob coming from core:

//@ edition: 2018
use ::core::prelude::v1::*;

#[allow(unused)]
fn f() {
    panic!(std::string::String::new());
}

passes on nightly because name resolution selects std::panic but becomes an error w/ this PR when it downgrades the ambiguity error to a warning and selects core::panic via the manual glob import causing a type mismatch error on the panic argument.

@yaahc
Copy link
Member

yaahc commented Dec 1, 2025

How does #[no_implicit_prelude] impact behavior?

At first I though it would allow us to have multiple manual glob imports of panic macros and trigger some expansion order dependent behavior but it looks like that is not the case. Here are the specifics of how #[no_implicit_prelude] impacts macro visitation order:

  • MacroUsePrelude is visited unconditionally in the 2015 edition, and conditionally based on no_implicit_prelude in later editions
  • StdLibPrelude is always visited for the macro namespace

So after this change, we will always have a panic candidate in scope regardless of `#[no_implicit_prelude].

This does mean that the following code, which compiles currently:

#![no_implicit_prelude]

mod m1 {
    macro_rules! panic {
        () => {};
    }
    
    pub(crate) use panic;
}

fn foo() {
    use m1::*;
    panic!();
}

produces an ambiguity error w/ this change (globvsouter though, not globvsglob).

edit: correction on an earlier point I made. I thought this change was causing us to hit the globvsglob ambiguity error, but it looks like it's actually always triggering globvsouter. It looks like globvsglob only ever happens during import resolution. I thought maybe moving the manual glob import into the top level would turn it into a globvsglob because that is the scope where the implicit prelude glob import is inserted but it doesn't matter. visit_scopes visits the prelude import as a separate scope after visiting the top level module and associated glob import so it will always produce a globvsouter error.


The 2015 stuff shouldn't matter because in practice it means that #[no_implicit_prelude] has no impact on macro resolution before or after this change in that edition. With or without it both MacroUsePrelude and StdLibPrelude are visited unconditionally.

@yaahc
Copy link
Member

yaahc commented Dec 1, 2025

Based on the edit in the previous comment, I wanted to see if I could trigger the globvsglob error even when this change attempts to downgrade the ambiguity to a warning and it turns out yes you can.

mod m1 {
    pub use core::prelude::v1::*;
}

mod m2 {
    pub use std::prelude::v1::*;
}

fn foo() {
    use m1::*;
    use m2::*;

    panic!();
}

This code is currently accepted and produces an error w/ this change. Prior to this change it would not find a panic candidate in either glob import, so it would have a single unambiguous panic candidate and compile with no issue. After this change it first resolves both the m1 and m2 globs and records that those globs are ambiguous wrt the panic ident. Then, when it tries to resolve the panic macro invocation it finds two candidates, core::panic from the first manual glob, and std::panic from the prelude. It then downgrades this to a warning and selects core::panic via the glob which was previously marked as ambiguous as the final candidate. This then causes the globvsglob ambiguity error to be emitted because panic was resolved through the ambiguous glob import in foo.

Notably, if you change the order of the glob imports it no longer produces the globvsouter downgraded warning. Instead both candidates end up resolving to std::panic and it only produces the globvsglob warning because the globs in foo are still ambiguous.

@yaahc
Copy link
Member

yaahc commented Dec 1, 2025

Good catch, indeed there are programs that are now accepted that weren't previously, I got that part wrong. I'll update the stabilization report to reflect this, but I'm unsure how best to do it.

Here's my attempt at rewriting the associated bulletpoint


  • ambiguous_panic_imports lint:

This lint is tied to a new exception to the name resolution logic in compiler/rustc_resolve/src/ident.rs similar to an exception added for #145575.

This change introduces a new ambiguity warning downgrade path when encountering ambiguous resolution candidates for a panic macro invocation. This is required because std and core export different panic macros, so in cases where users already glob import the prelude manually while resolving panic through the macrouseprelude they will now potentially have two ambiguous glob imports in scope which do not resolve to the same panic! macro definition. For other macros added to the prelude the different globs will all resolve to the same candidate and will not be treated as ambiguous.

This change downgrades that ambiguity error to a warning when both candidates are builtin macros and they're being resolved via the ident panic. When this error is downgraded to a warning the first candidate visited will be selected as the final candidate. This can potentially cause the compiler to select a different candidate when resolving panic than it did prior to the change, which could in turn cause compilation errors in code compiled with under the 2015 or 2018 editions if resolution changes from std::panic to core::panic and the invocation provides only a single argument which is not of type &str: #139493 (comment). Changing a valid core::panic single arg invocation to use std::panic should produce no change in behavior. This change can cause the compiler to accept previously invalid invocations of core::panic if they're instead resolved to std::panic: #139493 (comment)

#![no_std]
extern crate std;
use std::prelude::v1::*;
fn xx() {
    panic!(); // resolves to std::panic
    //~^ WARNING `panic` is ambiguous
    //~| WARNING this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
}

This lint can also trigger in some cases that were previously an error allowing them to compile with a warning: #139493 (comment). In all cases where this lint triggers, the code that is allowed to compile will have a future incompatibility warning associated with it, regardless of whether the code was previously allowed to compile or not.

This lint covers all of the common ambiguity errors that crater caught being introduced by this change. It is possible to produce other ambiguity errors not covered by this lint exception that were previously unambiguous prior to this change: #139493 (comment). This issue has not been observed in the wild.

@yaahc
Copy link
Member

yaahc commented Dec 1, 2025

One last thing I'm still trying to understand is why we can't just select binding instead of innermost_binding when we downgrade the ambiguity error to a warning.

I know this ties into the issue identified here: #65721 (comment) but I don't fully understand the issue or how it affects this PR.

My current conclusion is that I think in this specific situation we may be able to get away with it because we're actually dealing with the inverse situation from the one vadim highlighted in the above link. Instead of wanting to tiebreak in favor of the manual glob import we want to always tiebreak to the prelude when downgrading ambiguity. My understanding isthat in the other direction we have to prove that we never speculatively resolve to the prelude before materializing the candidate from the manual glob import and then finalize the resolution as from the glob import, but in this change's scenario we know that the panic macro from the prelude will always have been materialized, and we only need to insure we don't greedily speculatively resolve to the glob import when it has also been materialized and catch this specific ambiguity case pre-finalization.

I have a WIP PR demonstrating this that I'll push shortly

Edit: https://github.com/yaahc/rust/pull/5/commits

Here's a PR showing the two commits I setup, the first one adds test cases for all the examples we've recently discussed in this PR and shows how they currently behave with this PR as written. The second commit shows some additions to this PR I think may be warranted and how they impact the test results. I've not gone and explicitly documented how these tests behave on nightly rust, this is left as an exercise for the reader, bonus points if you go back and leave comments on the test cases w/ the info (I gotta go home).

I did add one additional testcase, I realized that its possible to trigger the ambiguity warning downgrade w/o involving the prelude so long as the different user glob imports are not in the same scope, in which case they trigger the glob vs outer logic and the associated warning downgrade.

@traviscross
Copy link
Contributor

Thanks @yaahc for the detailed analysis.

In particular, this answers a lot of questions for me:

mod m {
    pub use core::env as panic;
}
use m::*;
fn f() {
    panic!("PATH");
    //[nightly]~^ error: `panic` is ambiguous
    //[this-pr]~^ warning: `panic` is ambiguous
}

It'd be good to include this in the Reference along with the rule for it.

@traviscross
Copy link
Contributor

On the no_impllicit_prelude attribute behavior, this does surprise me and seems undesirable:

#![no_implicit_prelude]

fn f() {
    panic!();
    //[nightly]~^ error: cannot find macro `panic` in this scope
    //[this-pr]~^ OK
}

Comparatively, of course:

#![no_implicit_prelude]

fn f() {
    drop(()); //~ error: cannot find function `drop` in this scope
}

Since the goal of this PR is to move macros to path-based resolution, I'd expect them, if possible, to work in the same way as other names resolved via path-based resolution with respect to no_implicit_prelude.

@traviscross
Copy link
Contributor

traviscross commented Dec 2, 2025

In talking with @yaahc, she described the following (undocumented and maybe surprising) truths about the behavior today:

The no_implicit_prelude attribute does not suppress the implicit glob import of the standard library prelude -- only the visitation of the prelude scopes when iterating through the available scopes for name resolution candidates. E.g., the expansion of:

#![no_implicit_prelude]
fn f() {
    assert!(true);
}

Is this:

#![feature(prelude_import)]
#![no_implicit_prelude]
#[macro_use]
extern crate std;
#[prelude_import]
use std::prelude::rust_2024::*;
fn f() { if !true { ::core::panicking::panic("assertion failed: true") }; }

(Some macros are exported by path from the prelude today, e.g. assert, asm, etc.)

The no_implicit_prelude attribute suppresses visitation of macro names in the macro_use prelude, but it does not suppress visitation of macro names in the standard library prelude. It does suppress visitation of all other names in the standard library prelude.

This behavior was introduced in #62086, and specifically:

https://github.com/rust-lang/rust/pull/62086/files#diff-3cff50afc88a11e641a6851dd19471a7e6470f05dd0cddaf2271e5cdd9936b73L2222

Speculating, this may have been done because, prior to that PR, certain macros such as assert, asm, etc., were built-ins. These macros were always available, even when no_implicit_prelude was specified, as these were simply made available by the compiler and were not part of a prelude.

So as to not break code, what the PR did was add these macros to the standard library prelude and add a hack that unconditionally visits the standard library prelude, even when no_implicit_prelude is present, when searching for name resolution candidates in the macro namespace.

As applied here, then, the unfortunate part would be extending the surface area of this hack to all macros, not just the built-in ones. What maybe we could do, then, to avoid this, is to make this hack more specific. We could check whether a macro is one of the ones that is currently (today) exported from the prelude when deciding whether to resolve the name from the standard library prelude.

@Voultapher
Copy link
Contributor Author

Voultapher commented Dec 3, 2025

#![no_implicit_prelude]

fn f() {
    panic!();
    //[nightly]~^ error: cannot find macro `panic` in this scope
    //[this-pr]~^ OK
}

@traviscross are you sure this is the behavior? I don't have access to my dev machine right now and can't check it but it seems to be the opposite of my previous testing. As noted in the stabilization, it interacts with #![no_implicit_prelude] but only for Rust 2015 in my testing the behavior was the same for the other versions between nightly and this PR, namely panic! was not visible when using #![no_implicit_prelude].

#![no_implicit_prelude]
// Uncomment to fix error.
// use std::{panic, vec};
fn main() {
    let _ = vec![3, 6];
    panic!(); // Ok on nighlty but error with this PR.
}

@traviscross
Copy link
Contributor

are you sure this is the behavior?

Retested. Yes, this is the behavior I see in testing. For this code:

#![no_implicit_prelude]
fn f() {
    panic!();
    //[nightly]~^ error: cannot find macro `panic` in this scope
    //[this-pr]~^ OK
}

Run with:

rustc +stage1 --edition=2024 --crate-type=rlib src/lib.rs

It compiles without error on my build of the branch of this PR. On nightly, it produces this error:

error: cannot find macro `panic` in this scope
 --> src/lib.rs:3:5
  |
3 |     panic!();
  |     ^^^^^
  |
help: consider importing this macro
  |
2 + use std::panic;
  |

@traviscross traviscross force-pushed the explicitly-export-core-and-std-macros branch from ed65c75 to af5665b Compare December 3, 2025 09:25
@rustbot
Copy link
Collaborator

rustbot commented Dec 3, 2025

This PR was rebased onto a different main commit. Here's a range-diff highlighting what actually changed.

Rebasing is a normal part of keeping PRs up to date, so no action is needed—this note is just to help reviewers.

@rustbot

This comment has been minimized.

@traviscross
Copy link
Contributor

I pushed up a test demonstrating the behavior.

Voultapher and others added 3 commits December 3, 2025 09:30
This demonstrates that, under the behavior introduced in PR 139493,
that the `panic` macro is resolved even with `no_implicit_prelude` and
without the macro being explicitly imported.  This happens due to a
hack introduced in PR 62086 that causes macros in the standard library
prelude to be resolved even when `no_implicit_prelude` is present.
The fact that PR 139493 adds `panic` to the standard library prelude
then triggers this behavior.

In Rust 2015, this code was already accepted for a different reason
(due to resolution from the macro prelude not being suppressed with
`no_implicit_prelude`.)

It may not be desirable to extend the scope of this hack to macros
for which it did not already have effect.
@traviscross traviscross force-pushed the explicitly-export-core-and-std-macros branch from f3cd775 to 82371a1 Compare December 3, 2025 09:31
@Voultapher
Copy link
Contributor Author

Thanks for the test. Strange ... very strange I'll look into it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A-rustdoc-search Area: Rustdoc's search feature I-lang-nominated Nominated for discussion during a lang team meeting. I-lang-radar Items that are on lang's radar and will need eventual work or consideration. needs-fcp This change is insta-stable, or significant enough to need a team FCP to proceed. P-lang-drag-1 Lang team prioritization drag level 1. https://rust-lang.zulipchat.com/#narrow/channel/410516-t-lang perf-regression Performance regression. S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. T-lang Relevant to the language team T-libs Relevant to the library team, which will review and decide on the PR/issue. T-rustdoc-frontend Relevant to the rustdoc-frontend team, which will review and decide on the web UI/UX output.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Do not apply #[macro_use] to implicitly injected extern crate std;, use standard library prelude instead