Skip to content

Cannot refer to item inside function from module inside function #79260

Open

Description

@davidhewitt

I tried this code (https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=8c1861ee7e07799c5897f37f0aa2f849):

fn main() {
    mod foo {
        use super::*;
        
        fn foobar() {
            bar();
        }
    }
    
    fn bar () {}
}

This fails to compile:

error[E0425]: cannot find function `bar` in this scope
 --> src/main.rs:6:13
  |
6 |             bar();
  |             ^^^ not found in this scope

warning: unused import: `super::*`
 --> src/main.rs:3:13
  |
3 |         use super::*;
  |             ^^^^^^^^
  |
  = note: `#[warn(unused_imports)]` on by default

rustc --version --verbose:

rustc 1.48.0 (7eac88abb 2020-11-16)
binary: rustc
commit-hash: 7eac88abb2e57e752f3302f02be5f3ce3d7adfb4
commit-date: 2020-11-16
host: x86_64-unknown-linux-gnu
release: 1.48.0
LLVM version: 11.0

Also reproduces on current nightly (as of 2020-11-21).


I suspect that what's going on is that use super::* doesn't actually resolve to the scope inside fn main, but instead the surrounding scope fn main is itself in.

I tried to search around other issues to find whether this is expected.

This is a super weird special case. It seems plausible (at least to me) that use super::* from a module inside a function should bring other items local to that function into scope.

Activity

  1. davidhewitt commented on Nov 21, 2020

    @davidhewitt
    ContributorAuthor

    (If the lang team agrees that this code should compile, I'm willing to attempt a patch to fix.)

  2. jonas-schievink commented on Nov 21, 2020

    @jonas-schievink
    Contributor

    Note that fixing this would break this code:

    fn main() {
        mod foo {
            use super::*;
            
            fn foobar() {
                bar();
            }
        }
    }
    
    fn bar() {}
  3. davidhewitt commented on Nov 21, 2020

    @davidhewitt
    ContributorAuthor

    I'm not convinced it would break that code; bar is already a valid identifier inside fn main and so I would argue use super::*; inside main::foo should resolve fn bar in that case too.

  4. davidhewitt commented on Nov 21, 2020

    @davidhewitt
    ContributorAuthor

    I can find another example of this which is easy to reproduce and seems like a much more likely case to trigger!

    Write a doctest which declares a module inside it:

    /// ```rust
    /// struct Foo;
    ///
    /// mod foobar {
    ///     use super::*;
    ///
    ///     fn bar() -> Foo { Foo }
    /// }
    ///
    /// ```
    fn foo() {}

    This doctest will fail to compile:

    ---- src/lib.rs - foo (line 1) stdout ----
    error[E0412]: cannot find type `Foo` in this scope
     --> src/lib.rs:7:17
      |
    8 |     fn bar() -> Foo { Foo }
      |                 ^^^ not found in this scope
    
    error[E0425]: cannot find value `Foo` in this scope
     --> src/lib.rs:7:23
      |
    8 |     fn bar() -> Foo { Foo }
      |                       ^^^ not found in this scope
    

    I would guess that the doctest framework wraps the doctest code inside a function, so we see exactly the same issue as in my original report.

  5. davidhewitt commented on Nov 21, 2020

    @davidhewitt
    ContributorAuthor

    Ah, but I think this variation of @jonas-schievink 's example will break:

    fn main() {
        mod foo {
            use super::*;
            
            fn foobar() {
                bar();
            }
        }
    
        fn bar() {
            println!("inner bar");
        }
    }
    
    fn bar() {
        println!("outer bar");
    }

    At the moment it always prints "outer bar", but if we fixed my original example this would either be ambiguous or print "inner bar", I asssume.

    (We could perhaps make it backwards compatble by letting items outside the function take precedence, and then switch the order to prefer items inside the function on an edition change.)

  6. added
    A-resolveArea: Name/path resolution done by `rustc_resolve` specifically
    T-langRelevant to the language team
    on Nov 21, 2020
  7. Havvy commented on Nov 22, 2020

    @Havvy
    Contributor

    I am against changing the meaning of super.

    I also wouldn't call this a bug. Everything is working as it originally is intended to. super refers to the parent module, not the parent item scope. Functions allow defining items that are local to the function and a module is non-local, even if it's declared inside the function.

    IMO, if you need a module, just like if you need an impl, it should not be defined in a function.

  8. petrochenkov commented on Nov 22, 2020

    @petrochenkov
    Contributor

    We probably need some way to refer to block-that-are-parent-of-modules, but super is not that way.
    super has a well-defined meaning - parent mod, similarly to how self is the current module and not current block or something.

    Besides being a breaking change, path resolving to an unnamed block is some new possibility that will cause a number of subsequent issues.
    What would happen here, for example

    use super as foo; // referes to a block
    let bar = foo;
  9. petrochenkov commented on Nov 22, 2020

    @petrochenkov
    Contributor

    My preferred solution to this issue is probably #[transparent] mod foo { ... } that prevent the module from introducing a "name barrier".
    It's something that is more useful in various situations, and fits more naturally into the existing system.

    UPD: Example.

    struct S;
    
    #[transparent]
    mod m {
        fn f() {
            let s = S; // OK
        }
    }
  10. davidhewitt commented on Nov 22, 2020

    @davidhewitt
    ContributorAuthor

    #[transparent] attribute is a very compelling proposal 👍. It would be a shame that it would be needed in some doctest examples. Still great as a 100% backwards-compatible opt-in design.

    I wrote a PR to experiment with what I proposed above (#79309) - I've added an alternatives section with #[transparent].

  11. AndreySmirnov81 commented on Sep 25, 2023

    @AndreySmirnov81

    I would like to see this result:

    1. For Rust before 2024 edition

    fn main() {
        mod foo {
            use super::*;
            
            fn foobar() {
                bar(); // <- error[E0425]: cannot find function `bar` in this scope
            }
        }
    
        fn bar() {
            println!("inner bar");
        }
    }

    error[E0425]: cannot find function bar in this scope

    2. For Rust before 2024 edition

    fn main() {
        mod foo {
            use super::*;
            
            fn foobar() {
                bar();
            }
        }
    
        fn bar() {
            println!("inner bar");
        }
    }
    
    fn bar() {
        println!("outer bar");
    }

    outer bar

    3. For Rust before 2024 edition

    fn main() {
        #[enable_fn_scope_from_inner_module] // <- !!!
        mod foo {
            use super::*;
            
            fn foobar() {
                bar();
            }
        }
    
        fn bar() {
            println!("inner bar");
        }
    }
    
    fn bar() {
        println!("outer bar");
    }

    inner bar

    4. For Rust before 2024 edition

    #![enable_fn_scope_from_inner_module] // <- !!!
    
    fn main() {
        mod foo {
            use super::*;
            
            fn foobar() {
                bar();
            }
        }
    
        fn bar() {
            println!("inner bar");
        }
    }
    
    fn bar() {
        println!("outer bar");
    }

    inner bar

    5. For Rust 2024 edition

    fn main() {
        mod foo {
            use super::*;
            
            fn foobar() {
                bar();
            }
        }
    
        fn bar() { // <- obscures the outer bar
            println!("inner bar");
        }
    }
    
    fn bar() {
        println!("outer bar");
    }

    inner bar

  12. AndreySmirnov81 commented on Oct 26, 2023

    @AndreySmirnov81

    Alternatively, for this non-working example:

    изображение

    we can use this:

    изображение

    However, there were difficulties with rust-analyzer: rust-lang/rust-analyzer#15805

  13. Veetaha commented on Aug 11, 2024

    @Veetaha
    Contributor

    Here is a real use case for this. I'd like to generate a child module in a proc macro where I'd place the definition of the builder stuct (for privacy of its fields). The builder's fields should reference types from the surrounding scope.

    fn example() {
        struct Password(String);
    
        // Suppose a `#[builder]` proc macro was placed on this struct
        struct User {
            password: Password,
        }
    
        // This module is generated by the proc-macro
        mod user_builder {                  
            use super::*;                   
    
            pub(super) struct UserBuilder { 
                password: Option<Password>, 
            }                               
        }                                   
    }

    I made an article about this problem here: https://elastio.github.io/bon/blog/the-weird-of-function-local-types-in-rust

  14. sunshowers commented on Oct 12, 2024

    @sunshowers
    Contributor

    Another, similar use case for this is with a new feature in our HTTP server Dropshot called API traits. Applying this proc macro on the trait causes a bunch of support functions (and soon at least one macro) to be generated. The options are:

    • Put the generated items at the top-level, polluting the top-level namespace in an undesirable way.
    • Hang the items off of a type (e.g. enum SupportFunctions {}, then impl SupportFunctions { ... }). This would work for functions but not for macros.
    • Put them in a support module. That's what we've chosen, but this runs into the issue described here.

    The proposal to have a way to mark a module as transparent in this comment above would solve this issue, and I think it would be worth pursuing.

  15. Veetaha commented on Oct 12, 2024

    @Veetaha
    Contributor

    @sunshowers I've since discovered another option to workaround this problem, if your goal is to make the generated symbols private. You can just randomize the names of your generated symbols using some simple randomness source like std's RandomState (example).

    This way the users won't be able to use the generated names, and even if they do, they won't be able to compile/re-compile the code if they do that. I also recommend marking these symbols with #[deprecated] to further complicate their usage.

  16. sunshowers commented on Oct 12, 2024

    @sunshowers
    Contributor

    @sunshowers I've since discovered another option to workaround this problem, if your goal is to make the generated symbols private. You can just randomize the names of your generated symbols using some simple randomness source like std's RandomState (example).

    This way the users won't be able to use the generated names, and even if they do, they won't be able to compile/re-compile the code if they do that. I also recommend marking these symbols with #[deprecated] to further complicate their usage.

    Ah, in my case I'd like the generated symbols to be public.

  17. yaahc commented on Oct 13, 2025

    @yaahc
    Member

    I'm thinking about trying to go and implement #[transparent] for fun and education1. This came across my path when @estebank's coworker ran into the same issue #t-lang-docs/reference > name resolution @ 💬

    I'm assuming this change will require updates to each of the various stages of name resolution in order to be feature complete, but off of the top of my head I think https://github.com/yaahc/rust/blob/8111a2d6da405e9684a8a83c2c9d69036bf23f12/compiler/rustc_resolve/src/ident.rs#L344-L345 is where the change for late resolution (most resolution) would need to occur. I'm pretty sure I just add an unstable attribute, check if its present on a module when resolving idents in lexical scope, and if it is we simply don't abandon the ribs yet.

    I think for early resolution (imports & macros) the change would need to happen in hygenic_lexical_parent, basically add another check similar to https://github.com/yaahc/rust/blob/8111a2d6da405e9684a8a83c2c9d69036bf23f12/compiler/rustc_resolve/src/ident.rs#L236-L238 which instead looks for Def(mod) module kinds that are transparent, which would then be treated as a valid lexical parent.

    I have no idea what this would look like for type dependent name resolution since I haven't really explored that much yet.

    Footnotes

    1. This is motivated by me wanting a better understanding of name resolution as part of my effort to document it in the rust reference. I'm not however interested in pushing for stabilization or any of the actual hard work associated with making it generally usable ↩

  18. added
    I-lang-radarItems that are on lang's radar and will need eventual work or consideration.
    on Oct 23, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-resolveArea: Name/path resolution done by `rustc_resolve` specificallyC-bugCategory: This is a bug.I-lang-radarItems that are on lang's radar and will need eventual work or consideration.T-langRelevant to the language team

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions