Rust: type items do not work with enums

Created on 13 Jun 2015  路  28Comments  路  Source: rust-lang/rust

type items don't work with enums.

#[derive(Debug)]
enum Foo {
    Bar,
    Baz,
}
type Loz = Foo;
fn main() {
    let y = Loz::Bar;
    println!("{:?}", y);
}

On the stable channel the following error occurs:

<anon>:8:13: 8:21 error: type `Foo` does not implement any method in scope named `Bar` <anon>:8 let y = Loz::Bar; ^~~~~~~~ error: aborting due to previous error playpen: application terminated with error code 101```

A-resolve C-feature-request P-medium T-compiler T-lang

Most helpful comment

It seems to me that it is entirely reasonable to expect this to work. It鈥檚 not just a matter of documentation.

All 28 comments

type only creates a type alias - it does not change resolution. Maybe documentation should be improved through.

The error in nightly is better:

<anon>:7:13: 7:21 error: no associated item named `Bar` found for type `Foo` in the current scope
<anon>:7     let y = Loz::Bar;

enum constructors are not associated items.

It seems to me that it is entirely reasonable to expect this to work. It鈥檚 not just a matter of documentation.

@chris-morgan

Maybe. However, associated items work rather differently from resolve-items - e.g. use Trait::Item doesn't work, but is the correct way with enums.

/cc @rust-lang/lang, should this work, or should this give a better error?

I think it's a somewhat tough question. The "integration" of enum variants and other kinds of associated items is, well, there isn't any, though it sort of feels like it'd be nice if there was. I certainly think is expected behavior for the moment, and I think that any fix would require some careful thought to be sure.

Yeah, this is definitely not a minor fix. I'd leave as is. I guess we could document better. I would only like to see a better error message if it can be done without making name resolution more awful.

Similarly, associated types too can resolve constructing functions, but not enum variants: http://is.gd/pnn7sC

It'd be very convenient to be able to think about enum variants in the same way we think about type constructing functions.

I posted a related issue earlier, but @mitaa kindly pointed me here, so I'll close that in favour of posting the parts of my issue that have not been covered here yet below.

In examples like the one above this normally isn't a huge issue as it is possible to pub use Foo as Loz instead, which accomplishes the same thing in practise.

However, in practise I often come across cases where pub use does not suffice. The most pressing use-case I've come across is when Foo has a type parameter, and I want to create an alias of Foo with the type parameter filled, i.e.

#[derive(Debug)]
pub enum Foo<T> { Bar(T), Baz }
pub type Loz = Foo<i32>;

pub fn qux() -> Loz {
    Loz::Bar(0) // error
}

fn main() {
    println!("{:?}", qux());
}

_playpen_

Here I want to expose Loz from my API as if it were a Foo<i32> when it comes to its use as a type as well as variant access. Doing a type alias (as above) allows for using Loz in signatures without issues (see the fn qux signature) however I cannot access the variants using Loz, meaning I would have to _also_ require a user to import Foo in case they wish to access variants. Alternatively, if I instead pub use foo::Foo as Loz I can now access variants using Loz without issue, however the type parameter is no longer filled, meaning the fn qux signature no longer works and I'm unable to express through my API that Loz should always be Foo<i32> and not Foo<T>.

Another similarly-annoying case is in impls, as you can't do Self::Bar (Self behaves like a type-alias). Do we want this?

+1 on this not being very intuitive. Hit to today and took a bit to figure out what's going on.

Being able to fill in partial types would be really nice, or at least a better error message that Enums can't be used with type.

What's the latest on this?

This is _really_ unintuitive, especially as many crates define type Result<T> = Result<T, CrateError>. You'd EXPECT Result::Err() or Result::Ok() to work.

Nominating for lang team discussion.

My opinion is that this is a bug. I think of variants as associated items. It happens that, for historical reasons, variants are handled in resolve. We've been on a long and slow path towards refactoring that and we may be getting within striking distance of fixing it.

cc @jseyfried

Retagged for compiler team: the lang team agrees that this is "just a bug". We want to understand enum variants as inherent associated items, and hence they should work through aliases.

c.f. #31179, esp. https://github.com/rust-lang/rust/pull/31179#issuecomment-174449078 and the following two comments.

We want to understand enum variants as inherent associated items

Sigh, I dislike this decision for mixing concerns and introducing more complexity, but I agree that the current rules are not the most convenient and are hard to explain to new users, so I'd like to implement this, maybe it'll convince me the "associated item" approach is not _that_ bad.

Some details for the "associated item" interpretation:

enum E { V }
type Alias = E;
impl E { const V: u8 = 0; }

// Variants have higher priority than other associated items
E::V - resolves to variant
Alias::V - resolves to variant
<E>::V - STILL resolves to variant in "associated item" interpretation
<Alias>::V - STILL resolves to variant in "associated item" interpretation
Drawback - there seems to be no way to refer to the inherent associated constant V
Drawback - this is a breaking change
Alternative - variants on non-aliases have higher priority and variants on aliases have lower priority (horrible)
Alternative - make "duplication" between variants and inherent items an error, similarly to how inherent item duplication is already an error

// Both variant "type" and variant constructors are associated items
Alias::V { .. } - valid, resolves to a variant type
Alias::V - valid, resolves to a variant constructor
Catch - we don't have inherent associated types yet and resolution for non-UFCS associated types is really bad

// Variants work at "type level", so all aliases are substituted, including Self and associated types
Type::AssociatedType::Variant - works
Self::Variant - works
Catch - some normalization can suddenly result in a variant type? (not sure)

// The type parameter question - still unclear
type Alias<T> = Option<T>;
Option::<u8>::None // Currently prohibited
Option::None::<u8> // Ok
Alias::<u8>::None // ?
Alias::None::<u8> // ?
Alias::<u8>::None::<u8> // ??
Alias::<u8>::None::<u16> // ???

Drawback - this is a breaking change

Note that associated constants are not stable.

I think I'd be inclined to make this example an error. It's a coherence violation, just like this is.

Probably a more interesting case for backcompat though is this one, which would also be illegal in my mind:

enum Foo { V(u8) }

impl Foo {
    fn V(x: u8) { }
}

Note that we are currently in the process of phasing in #22889 here.

FWIW, I ran into this recently with a little crate providing a tri-state enum. The README has the following example code:

pub use tristate::TriState as Spam;

trait Classify {
    fn classify(&self) -> Spam;
}

impl Classify for Message { /* ... */ }

// ...

match message.classify() {
    Spam::Yes     => /* ... */,
    Spam::No      => /* ... */,
    Spam::Unknown => /* ... */
}

Consider this my +1 for making this work with type aliases! :D

Is this problem related?

struct VecA { x: u32, y: u32 }
impl VecA {
    fn new() -> Self {
        Self { x: 0, y: 0 } // OK
    }
}
struct VecB(u32, u32);
impl VecB {
    fn new() -> Self {
        Self(0, 0) // Error
    }
}
fn main() {}

Any progress on this yet?

@leonardo-m

That's even less likely to be fixed - it's a call to a constructor function, and Self isn't even present in the value namespace, so you can't be expected to do

struct S(u32);
impl S {
    fn foo(&self) -> Self {
        let make_self = Self;
        make_self(Self.0);  
    }
}

Another similarly-annoying case is in impls, as you can't do Self::Bar (Self behaves like a type-alias).

As a lowly Rust noob, +1 on this. It's entirely against the intuitive behaviour that you'd expect, given how structs work.

Since an RFC has already been proposed, and #31179 implements this, it should be worth merging.
cc @nikomatsakis

I just hit this thing, when I wanted an alias of generic enum with concrete type parameter, as @mitchmindtree demonstrated in his example. I'm pretty fluent in Rust, yet I thought "WTF?!" and was confused for few minutes, then tried minimal example on playpen and then found this issue.

So apparently, it's not just confusing but also wastes time. I now have to rewrite things...

Was this page helpful?
0 / 5 - 0 ratings