pre-RFC: support `use Enum::*` for function-local enums


#1

The following code works as expected:

#[derive(Debug)]
enum Enum { A, B }
fn main() {
    use Enum::*;
    println!("{:?} {:?}", A, B);
}

However, the following, with the enum moved into the function, fails:

fn main() {
    #[derive(Debug)]
    enum Enum { A, B }
    use Enum::*;
    println!("{:?} {:?}", A, B);
}

The error message: error: unresolved import `Enum::*`. Maybe a missing `extern crate Enum`? [--explain E0432]

I didn’t find any variation on self:: or super:: that makes that version work.

This makes it a bit more painful to use enums declared within a function. Some discussion on IRC suggested that function-local scopes don’t have a name, and use can’t refer to them or use anything from them.

I’d like to propose an RFC allowing the example above to work as written.

This doesn’t seem likely to break any existing code, unless that code either 1) declares a function-local enum with the same name as a module with a non-snake-case name (which would produce a warning by default) and tries to use the module from within the same function, or 2) declares enums with identical names at top-level and within a function and tries to use the top-level enum from within the same function. Both of those seem highly unlikely.

Another alternative would involve introducing a name for the function-local scope, such as use fn::E or use local::E. However, that would then raise questions about syntax for nested functions, or nested blocks, and fn::super::super:: seems excessive. I’d prefer to just have use Name within a function search upward through function scopes until it hits module scope.

Before writing this up as an RFC, I wanted to seek feedback here on the issue and this approach to solving it.


Some examples of simple stronger static typing
#2

You are correct that there is nothing that will work today for this. self refers to the nearest module, which excludes the anonymous function body scopes. I would be against changing these semantics because it would be a breaking change (however small), and because it makes a kind of sense - self is a kind of alias, and it doesn’t make sense to alias something which is anonymous. Furthermore, it would mean changing the semantics of super too, and make the semantics around paths even more complex than they are today.

Having some kind of keyword to name function scopes seems preferable, however, we’d still need to modify super and so it would still be a breaking change.

So, should we do anything? I think not. It is a very limited improvement - there is never any reason to use such imports except for enums (as far as I can tell). There are easy ways to work around this rough edge - you can move the enum out into module scope or you can create a module within the function and put the enum in there.

Finally, I believe that glob importing enum variants is simply not something you should do. It is in Rust for historical reasons as much as by design, and I have never seen code which is clearer using them rather than the qualified form. I would strongly encourage you to use that instead of the glob.


#3

use self::main::Enum ? if Enum is “also a module”, why not main too :P.

Relatedly, why is pub allowed here?

fn main() {
    pub enum Foo { }
}

the pub is a lie.


#4

If you introduce a way to refer to function scopes, modifying super doesn’t seem necessary; use super::name would still refer to the parent module of self, while (for instance) use fn::super::name would refer to the parent scope of the function. That also assumes you need to distinguish between different scopes within a function, rather than having fn just search upward through parent scopes until it finds the name.

So, this doesn’t require a breaking change at all.

(That said, I also don’t think just changing the behavior of use within a function seems likely to break anything at all, and I suspect a crater run would confirm that.)

Moving the enum out into module scope was exactly what I wanted to avoid; I wanted to keep the enum encapsulated within the function.

Creating a module within the function doesn’t seem to help; for instance:

fn main() {
    mod local {
        #[derive(Debug)]
        enum Enum { A, B }
    }
    use local::Enum::*;
    println!("{:?} {:?}", A, B);
}

This results in: error: unresolved import `local::Enum::*`. Maybe a missing `extern crate local`? [--explain E0432]

Could you give an example of the approach you had in mind there? If there’s a workaround here, that would help. I haven’t found any other than moving the enum outside the function to module scope.

“More explicit” isn’t always “clearer”; brevity can improve clarity when it removes unrelated noise and makes it easier to see more important details. For a purely local enum, qualified names seem like overkill. In this case, I’d created a local enum specifically to simplify a subsequent match statement, which proceeded to match against it many times; the name of the enum literally never appeared except in the use statement.


#5

I was thinking about if you have multiple nested functions, then you probably want super to refer to the next function out from the current one, not the next module out from the top function.

Could you give an example of the approach you had in mind there?

I was thinking that you split out a function that uses the enum inside the module, and put your use with it - i.e., you have use self::Enum::* inside local in your example. I don’t think you can refer to local from main thought, you do need another nested function.

For me I find it confusing to see unqualified enum variants - if I see A rather than Enum::A I start looking for a type called A before I remember that people use these glob imports. If the enum is purely local, then you could use a very short name: E::A, I don’t find that distracting, but I do see how some people might.


#6

If you want to support that (rather than searching), I think you’d write that as use fn::super::foo, without modifying the meaning of use super::foo.

Ah, I see. In that case, putting a module within the function doesn’t quite work (no way to reference anything inside it), but If I move the entire function inside a module that itself lives at the top-level in place of the function, I can then “use thatmodule::function” to bring that function out as though it lived in the top-level module. That seems like a rather massive hack, though.


#7

Because it can have an effect: https://github.com/rust-lang/rust/issues/31776#issuecomment-187712844


#8

Something like this should work:

fn main() {
    mod m {
        #[derive(Debug)
        enum Enum { A, B}
        use self::Enum::*;

        fn main() {
            println!("{:?} {:?}", A, B);
        }
    }
    m::main();
}

EDIT: ah, sorry, looks like it was already answered :slight_smile:


#9

Names in value namespace can’t have “children” currently. It has some benefits. For example, all path segments except for the last one need to be resolved only in one namespace. use a1::a2::...::aN; can import 2 entities at most and not 2N. Unresolvable self-conflicting paths like

mod f { struct S; }
fn f() { struct S; }
type A = f::S;

are impossible.


#10

My experience of Rust is still limited, why isn’t this working?

In Rust most things are nestable inside functions, so I’d like that code to work. For uniformity. If I move some code from global scope to function scope, I’d like it to work.

In most cases I agree that qualifying enums is better and more explicit. But I like my enum names to be sufficiently long like a concatenation of two words. And when you have a match on many enum variants, repeating each time the enum name is boring, it’s just distracting noise. So in some cases I think using the glob syntax is acceptable (another idea: if you glob import two enums (or modules) with partially overlapping variants (or names), perhaps Rust could not give an error if you use only the non overlapping variants (or functions). This is how the “with” and “import” keywords work in D language).

A way to name the current function is sometimes handy for recursion:

fn factorial(n: u64) -> u64 {
    if n < 2 { 1 } else { n * factorial(n - 1) }
}

fn main() {
    for i in 0 .. 20 {
        println!("{}", factorial(i));
    }
}

You can think of more DRY code like (avoiding repeating the name of the recursive function inside itself):

fn factorial(n: u64) -> u64 {
    if n < 2 { 1 } else { n * this(n - 1) }
}

#11

Because path like foo::bar::baz can be either relative to the current scope or absolute. When use in use items, like use foo::bar::baz, it is absolute (use foo === use ::foo).

Also, OCaml has a nice sytax for this use case, it’s called local open,

let open Enum in
println!("{:?} {:?}", A, B)

and allows to import names on the expression granularity.


#12

Thanks. If we had a Privacy checker that took into account readability (isn’t that planned?) would that change things?


#13

Relevant: https://github.com/rust-lang/rfcs/issues/959