An equivalence of the dot syntax for enums of Swift in Rust?

I think the dot syntax for enums of Swift is useful for removing duplicate codes. It seems there are not discussions about this yet.

As for a Swift example, please see here or below.

enum CompassPoint {
    case north
    case south
    case east
    case west
}
var directionToHead = CompassPoint.west // explicit when declared
directionToHead = .south // inferred when having context
switch directionToHead {
case .north:
    print("Lots of planets have a north")
case .south:
    print("Watch out for penguins")
case .east:
    print("Where the sun rises")
case .west:
    print("Where the skies are blue")
}

As Rust uses :: instead of . to access a value in an enum, I am expecting something like

enum Coin {
    Penny,
    Nickel,
    Dime,
    Quarter,
}

let mut coin = Coin::Nickel;
coin = ::Penny;
match coin {
    ::Penny => 1,
    ::Nickel => 5,
    ::Dime => 10,
    ::Quarter => 25,
};

The use case that needs such a syntax sugar is somewhere enums are frequently used. I think you can see the benefit from the above match example.

For another example, when I worked with wgpu APIs, there are a lot Descriptor types, which are used to configure the specs needed to create another complex struct. They used a lot of enums, and worst, the enums sometimes lie deep in the API hierarchy. See the code below:

let sampler = device.create_sampler(&SamplerDescriptor {
            address_mode_u: AddressMode::ClampToEdge,
            address_mode_v: AddressMode::ClampToEdge,
            address_mode_w: AddressMode::ClampToEdge,
            mag_filter: FilterMode::Linear,
            min_filter: FilterMode::Linear,
            mipmap_filter: FilterMode::Nearest,
            compare: Some(CompareFunction::LessEqual),
            lod_min_clamp: -100.0,
            lod_max_clamp: 100.0,
            ..Default::default()
        });

From the attribute names, we can see the meaning and infer a specific enum should be used, so something like below is straightforward to me, although : :: looks not elegant. With such a syntax sugar, we don't need to peak into the doc of this struct and find out which enum to use, which should give some efficiency boost.

let sampler = device.create_sampler(&SamplerDescriptor {
            address_mode_u: ::ClampToEdge,
            address_mode_v: ::ClampToEdge,
            address_mode_w: ::ClampToEdge,
            mag_filter: ::Linear,
            min_filter: ::Linear,
            mipmap_filter: ::Nearest,
            compare: Some(::LessEqual), 
            lod_min_clamp: -100.0,
            lod_max_clamp: 100.0,
            ..Default::default() // probably `::default()` as well?
        });

Adding this syntax sugar should not break the existing code, but it affects backward compatibility of code.

Although we can do something like use a::b::c::SomeEnum::* as a quick fix, but this has a side effect that brings unnecessary imports into a scope, and we cannot easily handle some edge cases where two imported enums have enum values of the same names.

So, it seems reasonable to me. But, does the community have some strong reasons not to do so?

Edit: As pointed out by @steffahn, :: is used in Rust, but an alternative seems good.

1 Like

In case you aren't aware, prefix :: already has a meaning in Rust.

https://doc.rust-lang.org/reference/paths.html#path-qualifiers

There's been previous discussions in this forum on this kind of proposal with syntax such as e. g. _::Foo. I'm on my phone right now and I don't have a link at hand to those previous discussions.

6 Likes

Well then, sorry that I don't know that. _::Foo seems good enough, but the only drawback is that this may give an illusion that they are using the same enum, but they are not actually.

And please let me know if you can find the previous discussions. Thanks!

This thread, I think

1 Like

There's also this thread which I'm hoping to write an RFC for at some point.

1 Like

Oh my god, it's been around like for more than two years, as I traced along with this thread.

But, if we forget about backward compatibility anyway, ::Foo seems good enough, because :: can hint the IDE that we want an enum value without the confusion of _::Foo nor the over-simplicity of simply Foo with a context.

With a context, ::Foo seems not likely to be confused with ::Foo(which is a struct) both semantically and syntactically, since we can already handle something like

enum SomeEnum{
   Foo, Bar
}

pub struct Foo{
    pub num : i8
}

// somewhere 
use a::SomeEnum::*;
use a::Foo;
fn main(){
   let a = Foo;
   let b = Foo{ num : 1};
}

Any opinions?

I'm afraid this is a private thread, as I cannot view it.

I messed up the link — it should work now.

Here's another thread:

This is a bit off topic, but I have a different opinion on your thread. Although in the name elision, it's straightforward that it cleans the code, but I think it hinders code readability to some degree. For an enum, we usually only care about its values, not its type, but for a struct, we care about more, like its methods and trait implementations, which means we need to know which type it is. The destructing example you wrote in the thread is fine for me, but I don't like it to be applied to constructing an instance of a type because of the aforementioned reason.

For example, I will hesitate to write my code as this

let sampler = device.create_sampler(& _{
            address_mode_u: AddressMode::ClampToEdge,
            address_mode_v: AddressMode::ClampToEdge,
            address_mode_w: AddressMode::ClampToEdge,
            mag_filter: FilterMode::Linear,
            min_filter: FilterMode::Linear,
            mipmap_filter: FilterMode::Nearest,
            compare: Some(CompareFunction::LessEqual),
            lod_min_clamp: -100.0,
            lod_max_clamp: 100.0,
            ..Default::default()
        });

because I (and somebody else inspecting my code) will lose the context if we don't check the method signature for argument types. The type required here is not necessary for the compiler, but for us. Imagine later when we need to refactor code and store this descriptor as a template, if we wrote it like _{...}, we don't have a clue which type it is unless we dig into .create_sample() method.

Let's keep this thread on topic :slight_smile: All I will say is that your concern was addressed in that thread and will be if/when I write a full RFC.

I find the dot notation used by swift difficult to read. I think it's much easier to read when you specify the type at the call site then if you allow the compiler to infer it. Local use as statements can be used to make easier to read by the human, but avoid ridiculously long paths.

fn create_sampler() -> Sampler {
    use AddressMode as A;
    use FilterMode as F;
    let sampler = device.create_sampler(&SampleDescriptor {
        address_mode_u: A::ClampToEdge,
        address_mode_v: A::ClampToEdge,
        address_mode_w: A::ClampToEdge,
        mag_filter: F::Linear,
        min_filter: F::Linear,
        mipmap_filter: F::Nearest,
        compare: Some(CompareFunction::LessEqual),
        lod_min_clamp: -100.0,
        lod_max_clamp: 100.0,
        ..Default::default()
    });
}

}
3 Likes

In addition, the same local use statements can be used to elide the type completely:

enum MyEnum {
    Variant0, 
    Variant1, 
} 

fn foo() {
    use MyEnum::*;
    match value {
        Variant0 => {}, 
        Variant1 => {} 
    } 
} 

Given that with this it's pretty trivial to get the desired behavior, personally I consider that good enough.

4 Likes

Of course this is perfectly working code too, and pretty easy to get while refactoring:

enum MyEnum {
    Variant0,
    Variant1,
    VariantLoki,
} 

fn foo() {
    use MyEnum::*;
    match value {
        Variant0 => {}, 
        Variant1 => {},
        Variant2 => {},
    } 
} 

But all of this is a direct rehash of previous discussions on the topic.

That can be said for this thread in its entirety.