Compiler / duplicate error

Aside: Yes, I agree this should not be done. I'm just curious now about why the compiler behaves the way it does.

compiles fine:

struct Foo {}
#[allow(non_snake_case)]
fn Foo() {}

compiler error:

struct Foo
#[allow(non_snake_case)]
fn Foo() {}

Why does dropping the {} cause a compiler error ?

I'm guessing you meant that struct Foo; (note the semicolon) and fn Foo() {} do collide, right?

The reason for this is that, currently, in Rust, within a given "module", there is not one but three namespaces. That is, you can have a name, such as Foo in your example, represent different "entities" provided these belong to these three different namespaces:

  • The type namespace

    This includes things such as type definitions struct Foo …, enum Foo { … }, union Foo { … }, as well as traits, and modules.

  • The value namespace

    This includes constants, statics, and functions (which can be viewed as constants).

    Inside a function's body, this also includes local variables.

  • The macro namespace

    This includes function-like macros, as well as derive (proc-)macros and proc-macro attributes (for some reason they don't use different namespaces :woman_shrugging:).

  • (there may technically be some "extra namespaces" such as lifetimes, but that's more from the point of view of the compiler rather than that of a user, for whom 'foo and foo are visibly distinct things).

  • See the reference for more info.

So, in your case, fn Foo() {} populates the value namespace.

And while struct Foo {} populates the type namespace only, struct Foo; populates both the value and type namespaces. We thus end up with a collision of the name Foo in the value namespace.

7 Likes

Yes, my mistake.

Why is this? Why does struct Foo; populate the value namespace as well ?

EDIT: Clearly the compiler agrees with you. I just don't understand why the compiler sees struct Foo in the value namespace as well.

So, I wanted to update my post but you answered too fast :wink:, so will post it separately.

The case of struct (and enum variants) definitions

In order to perform a pure definition using struct (and/or, equivalently, enum s variants), one has to use braces:

struct Foo {}

enum Bar {
    Baz {},
}

That being said, Rust allows for two other syntaxes there, the no-brace syntax, as well as the parenthesized syntax:

// No braces (nor parens)
struct Foo;

// Parenthesized
struct Foo(…)

enum Bar {
    Parenthesized(…),
    Nothing,
}

Those syntaxes allow to, on top of definiting the struct type or the enum variant, to also define handy shorthands:

  • when using parenthesis,

    we get to have "functions" with which to construct the struct / the enum variant:

    struct Foo();
    
    // Works
    const _type_checks: fn() -> Foo = Foo;
    

    it is as if we had written:

    struct Foo {}
    
    const fn Foo () -> Foo
    {
        Foo {}
    }
    
    // Hence why this works:
    const _type_checks: fn() -> Foo = Foo;
    
    • Except for struct Foo() also introducing the Foo() pattern, which the function syntax does not do.

    Same for enum variants, only these "constructor functions" are (type-)namespaced within the name of the enum.

  • when using neither braces nor parenthesis

    we then get to have "constants" with which to construct the struct / the enum variant

    struct Foo;
    
    // Works
    const _type_checks: Foo = Foo;
    

    it is as if we had written:

    struct Foo {}
    
    const Foo: Foo = Foo {};
    
    // Hence why this works:
    const _type_checks: Foo = Foo;
    

Funny stuff from all this:

  • You can match against None {} or Some { .. }, and use None {} to construct it;

  • Supporting the pattern-specific Some(<subpat>) magic is specific to tuple struct / enum variants, and cannot be emulated using fns;

  • The global value that a fn foo(…)… generates can easily be shadowed inside a function body / cannot be used as a pattern; whereas when writing "an equivalent" const foo: impl Fn… definition, foo becomes a pattern and non-shadowable within a function's body.

2 Likes

Interesting, let's see if I got the high level bits:

paren case

struct Foo()

generates for us a global function of

name: Foo
type: fn () -> Foo

this is perfectly normal because we have times have

pub struct Point(i32, i32);
let p = Point(2, 3);
// here we are relying on existence of
// Point [in value space] :: i32 -> i32 -> Point [ in type space]

nothing case

pub struct Foo;

so it turns out this also generates for us a global constant

name: Foo
type: Foo

I mean, why not, might as well as auto generate for the user -- and this means something already exists in the 'value space' with key of Foo, which conflicts with our definition of fn Foo() { ... }

This is pretty much it right? Thanks for detailed explainations.

2 Likes

Yes.

Lifetimes are indeed a fourth namespace. Labels are in that namespace too.

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.