Inline enum proposal

So, this follows a discussion in the inline class KEEP.

Inline class is the cool feature I’m looking forward since some time in order to overcome a couple of Kotlin/jvm limitations.

In the meanwhile, I’d like to suggest a bunch of features which I’d really love:

  • define the i-th enum without passing anything, the default value would be ordinal in that case.
    if you define an enum passing a value, the very next starts from that

  • inline enum class WindowFlag(val i: Int) {
      noBorder, // 0
      noAlpha(4),
      noMove // 5
    }
    
  • sealed inline enum classes. In order to have hierarchical inline enums. This means allowing an inline class to extend only another inline class (of the same value type)

What do you think, guys?

1 Like

Correct me if I understand you wrong. The keep already contains inline enum so what you are proposing is to add an automatic generation of the integer or long values for inline enums of those types.

I can think of one situation where this might be useful. This could help a lot if you want to create an enum class which values are ordered.

enum class Sorted (val i: Int): Comparable<Sorted> {
    First,
    Second,
    Third;

   fun compareTo(other: Sorted) = i.compareTo(other.i)
}

I have a few questions though?

  1. Why does this require the enum to be inline? Yeah ok, otherwise we could have multiple values for each enum, but we could declare the enumerated integer using an annotation or some other keyword.
  2. Based on my use case there is no reason for skipping values. Skipping values starts to look a lot like magic numbers. If some of them need to have a specific value I think all of them should have their value declared by the programmer.
  3. Are there any use cases I have missed?
  4. How exactly are sealed enum classes supposed to work? Can you give an example for that? Wouldn’t they brake with enumerated values?

Based on that I am not really sure if this is a feature worth adding to Kotlin. I don’t see how this one use case is stronger than the minus 100 points rule.

  1. For two main reasons:

    • Smaller memory footprint
    • Faster bitwise operations

    Basically inline enums class is how enums should have been implemented in the first place…

  2. I have an huge pressure to skip values…
    Working with low-level real time graphics means that I have to deal a lot with c/c++ libs. These libs make a massive usage of int/enum constants.
    Just to name a few:

    they rarely are contiguous and start from 0, forcing me to manually write for each of them the corresponding value…

Sealed enums will be a blessing especially for OpenGL and Vulkan. Later api version usually add enums to the same logic group.
In this way I could easily exploit type safety via a hierarchical structure.
For example, in OpenGL there are commands that are supposed to bind some kind of buffers.
Calls of last OpenGL versions accept a larger number of buffer type. This mean I could have:

inline sealed enum TargetBuffer(open val i: Int) { 

    inline enum TargetBufferA(override val i: Int) : TargetBuffer(i) { first, second, third }
    
    inline enum TargetBufferB(override val i: Int) : TargetBuffer(i) { other, anotherOne }
}

and requesting the corresponding enums when and where it’s appropriate

other cool features I’d love:

  • the possibility for enums to implement a base interface in order to avoid this code overhead per enum

enum class DrawListFlag { AntiAliasedLines, AntiAliasedFill;

val i = 1 shl ordinal

}

infix fun DrawListFlag.or(b: DrawListFlag): DrawListFlags = i or b.i
infix fun Int.or(b: DrawListFlag): DrawListFlags = or(b.i)
infix fun Int.and(b: DrawListFlag): DrawListFlags = and(b.i)
infix fun Int.has(b: DrawListFlag) = (this and b.i) != 0
infix fun Int.hasnt(b: DrawListFlag) = (this and b.i) == 0

  • give the possibility for inline classes to have vars. In this way I could pass an inline primitive/enum reference and modify it from there.

fun getApplicationPropertyBool(appKey: String, property: EVRApplicationProperty, error: KMutableProperty0<EVRApplicationError>? = null): Boolean

and call

var err = EVRApplicationError.None
val prop = getApplicationPropertyBool(appKey, property, ::err)
// check error
  • bitwise operators (the very next natural step)
1 Like

I don’t agree, being able to have enums with values of all types is pretty useful. I agree that the concept of inline enums is great and I love that it is becoming a part of kotlin but I don’t agree with your statement that it should be the only or default way for enums.

Yeah, I did not think about C/C++ interop.

I still don’t understand how sealed inline enums are supposed to work

sealed inline enum Base(val i: Int) { A, B, C }
inline enum Foo(override val i: Int) : Base(i) { 
   F1, // i guess i = 3
   F2, // i = 4
   F3 // i = 5
}
inline enum Bar(override val i: Int): Base(i) {
  B1, // i also 3 // this conflicts with Foo
}

The KEEP currently only allows for inline classes with a single val. As enums are classes in kotlin this also applies to inline enums. And I don’t think this is going to change because immutability is a requirement of a primitive. You can not change the value of 4 to something else. You can only change a variable, which is set to 4 to something else. Same is true for inline classes.

I never meant to limit inline classes to integers, dude :upside_down_face:

Yeah sorry, the sealed inline enums example was broken, I should have fixed it now

Inline classes will be directly translated by the compiler, so this

var a = Error.None

will be written under the hood as

var a = 0

Now, If I pass it to a method as a reference I’m actually able to modify it. (KMutableProperty0<Int>)

I cannot see any reason why this shall not be applied to inline enum as well

Maybe I’m confused, but I’d love to see an example where you pass an int to a function and have that int modified.

var i: Int = 0
foo(i)
// i will still be 0 here. No matter what foo does

Have you, because I still don’t understand how you resolve the values for the inheriting classes. Or are you fine with them overlapping? If I look at your example:

first == 0
second == 1
third == 2
other == 0
annotehrOne == 1

This however is wrong IMO. I think all values of TargetBuffer need to have different values for i but they don’t in your example. Or do you think that other should be 3? This is also a problem, because in bigger files you don’t see at first glance that i is not starting at 0.

Maybe I’m confused, but I’d love to see an example where you pass an int to a function and have that int modified.

Kotlin is really cool, you can actually use a delegate to take the reference and use it as a local variable…!

Have you, because I still don’t understand how you resolve the values for the inheriting classes. Or are you fine with them overlapping? If I look at your example:

They’ll have for sure different values, that was a basic example, but you need to imagine it passing GL_UNIFORM_BUFFER and similar to constructors

Nice, I did not know that was possible. But I guess this would still work even if the value inside of the inline class is a val.

Uhm, this is super interesting. Maybe some JB guy can answer already that

I just tested it

operator fun <R> KMutableProperty0<R>.setValue(host: Any?, property: KProperty<*>, value: R) = set(value)
operator fun <R> KMutableProperty0<R>.getValue(host: Any?, property: KProperty<*>): R = get()

inline class Foo(val i: Int)
var f = Foo(4)
fun modify(ref: KMutableProperty0<Foo>) {
	var a by ref
	a = Foo(1)
}
fun main(vararg args: String){
	modify(::f)
	println(f.i)
}

Sadly it crashes with this error

Exception in thread “main” java.lang.ClassCastException: tests.lwjgl.Foo cannot be cast to java.lang.Number
at tests.lwjgl.HelloWorldKt$main$1.set(HelloWorld.kt:161)
at tests.lwjgl.HelloWorldKt.setValue(HelloWorld.kt:147)
at tests.lwjgl.HelloWorldKt.modify(HelloWorld.kt:156)
at tests.lwjgl.HelloWorldKt.main(HelloWorld.kt:161)

I am however using an eap build and inline classes are experimental, so I believe this is due to the fact that there are still bugs in inline classes.

PS: if anyone wonders about the line numbers, I just threw this code into my current project which is set up already for inline classes. I did not want to go through the effort of looking up the gradlew setup again :wink:

Edit: It looks like there is already an issue about this https://youtrack.jetbrains.com/issue/KT-25325 and it will be fixed in 1.2.7

ready :wink:

inline class Sex(val value: Int) {
	companion object {
		val Unknown = Sex(0)
		val Male = Sex(1)
		val Female = Sex(2)
	}
}
2 Likes

Equality checking will result in boxed inline class.

val sex = // whatever return a `Sex`
val isMale = sex == Sex.Male

The compiled code will box integers into Sex objects to check equality. Runtime overhead is coming back again.

2 Likes

This would be really useful when sharing Kotlin code with JavaScript or specifically TypeScript. In the TypeScript world, enums (or type unions) are represented like this:
type Foo = "option1" | "option2"

Using Kotlin enums from TypeScript currently requires using foreign code-patterns in the TypeScript world.

Oh pity.

Still better than falling back to enums though

I hope this discussion is not dead :slight_smile:

But having inline enum is useful for memory management. As we all know enums are objects at runtime and for each declared enum class there is n objects created at runtime (n is a number of enum values declared).

I’m an Android developer and in Android SDK there are only a few enums. They are replaced with integer constants.

So my proposal is: let the inline enum class be an enum object at compile time, but be an integer at runtime.

Some examples of what I mean:

inline enum class MyEnum(val p1: Int, val p2: String, val p3: Boolean)  { 
    VALUE1(12, "name: value 1", true), // Only constants should be parameters
    VALUE2(42, "name: value 2", true),
    VALUE3(180, "name: value 3", false),
    VALUE4(34, "name: value 4", false),
    VALUE5(65, "name: value 5", true)
}

fun accept(myEnum: MyEnum) {
    val count = myEnum.p1
    val someName = myEnum.p2
    val someBool = myEnum.p3
    println("enum: ${myEnum.name}, count: $count, someName: $someName, someBool: $someBool")
}

fun main() {
    accept(MyEnum.VALUE1)
}

Would have to be compiled to the following equivalent:

public final class MyEnum {

    // int value of enum should be powers of 2
    // (1, 2, 4, 8, 16, etc) in base 10
    // (1, 10, 100, 1000, 10000, etc) in base 2
    public static final int VALUE1 = 1;
    public static final int VALUE2 = 2;
    public static final int VALUE3 = 4;
    public static final int VALUE4 = 8;
    public static final int VALUE5 = 16;

    private static final List<Integer> allValues = Collections.unmodifiableList(Arrays.asList(VALUE1, VALUE2, VALUE3, VALUE4, VALUE5));
    public static List<Integer> getAllValues() {
        return allValues;
    }

    // for enum.ordinal callers
    public static final int getOrdinal(int $receiver) {
        // $receiver is always some power of 2. 
        // So we just have to find log2($receiver)
        return (int) Math.log((double) $receiver) / Math.log(2d);
    }

    // for enum.name callers
    public static final String getName(int $receiver) {
        String $result;
        switch ($receiver) {
            case 1:
                $result = "VALUE1";
                break;
            case 2:
                $result = "VALUE2";
                break;
            case 4:
                $result = "VALUE3";
                break;
            case 8:
                $result = "VALUE4";
                break;
            case 16:
                $result = "VALUE5";
                break;
            default:
                // should never happen, but nonetheless
                throw new IllegalArgumentException("wrong receiver: " + $receiver);
        }
    
        return $result;
    }

    public static final int getP1(int $receiver) {
        int $result;
        switch ($receiver) {
            case 1:
                // that's why we should only have constants here.
                $result = 12;
                break;
            case 2:
                $result = 42;
                break;
            case 4:
                $result = 180;
                break;
            case 8:
                $result = 34;
                break;
            case 16:
                $result = 65;
                break;
            default:
                // should never happen, but nonetheless
                throw new IllegalArgumentException("wrong receiver: " + $receiver);
        }

        return $result;
    }

    public static final String getP2(int $receiver) {
        String $result;
        switch ($receiver) {
            case 1:
                $result = "name: value 1";
                break;
            case 2:
                $result = "name: value 2";
                break;
            case 4:
                $result = "name: value 3";
                break;
            case 8:
                $result = "name: value 4";
                break;
            case 16:
                $result = "name: value 5";
                break;
            default:
                throw new IllegalArgumentException("wrong receiver: " + $receiver);
        }

        return $result;
    }

    public static final boolean getP3(int $receiver) {
        boolean $result;
        switch ($receiver) {
            case 1:
                $result = true;
                break;
            case 2:
                $result = true;
                break;
            case 4:
                $result = false;
                break;
            case 8:
                $result = false;
                break;
            case 16:
                $result = true;
                break;
            default:
                throw new IllegalArgumentException("wrong receiver: " + $receiver);
        }

        return $result;
    }
}

public static void accept~hashcode(int myEnum) {
    int count = MyEnum.getP1(myEnum);
    String someName = MyEnum.getP2(myEnum);
    boolean someBool = MyEnum.getP3(myEnum);

    System.out.println("enum: " + MyEnum.getName(myEnum) + ", count: " + count + ", someName: " + someName + ", someBool: " + someBool);
}

public static void main(String[] args) {
    accept~hashcode(MyEnum.VALUE1);
}

EnumSets for such enums should be replaced with Set in that case.
Also it would be a good idea to write class for int flags to store lists of those enums:

inline class EnumIntFlags<T : InlineEnum<T>>(val flags: Int) {

    fun addEnum(enum: T): EnumIntFlags<T> {
        return EnumIntFlags(flags or enum.ordinal)
    }

    fun removeEnum(enum: T): EnumIntFlags<T> {
        return EnumIntFlags(flags and enum.ordinal.inv())
    }

    fun hasEnum(enum: T): Boolean {
        return flags and enum.ordinal == enum.ordinal
    }

    inline fun forEach(block: (T) -> Unit) {
        InlineEnum.allValues<T>().forEach {
            if (hasEnum(it)) {
                block(it)
            }
        }
    }

    operator fun plus(other: T) = addEnum(other)
    operator fun minus(other: T) = removeEnum(other)
    operator fun contains(other: T) = hasEnum(other)
}

Also inline enums shouldn’t be only represented by integers. It can be for instance byte if there is less than 8 values or short if there is less than 16 values and so on…

This way we can reduce amount of objects created at runtime.

What do you think, everyone?

I’d doubt that is going to save much memory if any, you’ll have to generate much more code (which does take memory) and still allocate all the objects ‘contained’ in the enum.

Also this (depending on details) is going to break reflection, and to break enums implementing interfaces.
It will also break casting an enum back and forth from object (how can the compiler know an integer was actually an enum value?).

Honestly if you’re in a scenario where the memory taken by an enum instance is an issue, just use ints yourself.

1 Like

Dear @al3c before making negative comments, please read the statement at least twice. I never said let’s replace default enums with inline ones. enum MyEnum will still work as before. I’m proposing a new feature called inline enum :slight_smile:

Same true for all kotlin features. For instance data class, inline class, delegated property, delegated interface, extensions and so on. All of them will “generate much more code (which does take memory)”

On a project where a lot of enums are declared will save much memory

No objects will be allocated as values will become primitive.

Perhaps yes but only for inline enums default enums will not change in any way.
EDIT: Please provide an example in which reflection is broken by inline enum class

Here is the same problem as inline class has. If we use inline enum as interface type then we will have to create wrapper, but when we use it explicitly then there is no need to create object and use just integer

For instance:


inline enum MyEnum : SomeInterface 

fun accept(someInterface: SomeInterface) // enum is used as an object 

fun accept(myEnum: MyEnum) // enum is used as an integer

Why it will break casting? Why inline class has no such issue?

1 Like

Yes - but your point here is that you’re trying to save memory. With this feature you’ll have a larger compiled code for a smaller memory allocation at runtime.
Since the compiled code needs to be loaded in memory as well the question is: “Is this overall balance of memory usage in favor of inline enums”?

I have no idea, I’d guess it’s close.

I’m not sure what you mean by ‘primitive’ here.
If an enum contains a field of type List<FooBar> you’ll need to create a List<FooBar> somewhere.

But also if an enum contains a the string “foobar”, “foobar” will need to be in memory.

The only thing you’re saving is the “enum” object itself which contains pointers to all of its fields.

Probably the question is what is SomeEnum::class?

(assuming inline enums behave like enums) How would the following work:

inline enum class SomeEnum(val p: Int) { A(115); }

val p = SomeEnum.A.declaringClass.getDeclaredMethod("getP")
println(p.invoke(SomeEnum.A))  // prints 115
println(p.invoke(0))  // java.lang.IllegalArgumentException: object is not an instance of declaring class

If the compiler translates SomeEnum.A to 0 then you can’t have 2 different behaviours on those 2 lines.
If the compiler doesn’t translate SomeEnum.A to 0, then what is passed to .invoke?
(if your answer is some wrapper class, read my reply below)

I think this is the core issue, if you allow for “wrappers” to be generated then you can make reflection and all the rest work.

But if you generate a wrapper then you lose all the advantages of using this feature (unless I’m missing something).
These wrappers will just be the enum class instances you’re trying not to generate.

And the compiler can’t possibly know if you’re going to use these enums as an interface - you can always use them as object.

Note that this isn’t an issue for inline classes as the goal of inline classes is type correctness and not “not using extra memory”.
Inline classes have a best effort approach to not using wrappers. Using wrappers makes them less efficient, but you keep the added type safety.

inline enum class SomeEnum { A; }
val x: Any = SomeEnum.A
x as? Integer // should be null

inline class uses wrappers in this case and all works fine.

My goal is to decrease amount of allocated objects at runtime. On a project I’m working on are many enums (somewhat 50-60 declared, each of them have up to 15 enum constants declared, and this is a large amount of objects to be allocated which dramatically consumes memory). I don’t want to use integer constants myself because I’d like to keep them clean (with no IntDef annotations whatsoever) and type safe.

If we’re talking about amount of code to be generated and then compiled then only a few of them have properties declared. In the example in my original post I was showing the worst case scenario. The most common case doesn’t have properties declared so all that code is reduced to:

inline enum class SomeEnum { 
    Value1, Value2, Value3
}
public final class SomeEnum {
    public static final int Value1 = 1;
    public static final int Value2 = 2;
    public static final int Value3 = 4;

    // This is much better than
    // public static final SomeEnum ValueN = new SomeEnum();

    // for enum.ordinal callers
    public static final int getOrdinal(int $receiver) {
        return (int) Math.log((double) $receiver) / Math.log(2d);
    }

    // for enum.name callers
    public static final String getName(int $receiver) {
        String $result;
        switch ($receiver) {
            case 1:
                $result = "Value1";
                break;
            case 2:
                $result = "Value2";
                break;
            case 4:
                $result = "Value3";
                break;
            default:
                throw new IllegalArgumentException("wrong receiver: " + $receiver);
        }
    
        return $result;
    }

    private SomeEnum() {
        // private empty constructor
    }
}

As you can see we don’t need wrapper to be generated in this particular case. I’d say we only need wrapper in one case: if the inline enum class is implementing interface

As mentioned in original post I’m proposing to limit those fields to constant values (means it should be treated the same as const val in Kotlin) so enum cannot contain List<FooBar>

Not quite. If an enum contains a string “foobar” then it will be allocated only when static method getFooBar is invoked. As in the original post I was suggesting to make all those properties to be a static method at runtime.
Anyway it’s better to keep String in memory then a full object with all the properties

If the logic in the original post is correct then I guess it should be a reference to public final class SomeEnum which contains all the integer constants

The short answer is that the compiler indeed should translate SomeEnum.A to 0.
(To 1 if to be exact, because I edited original post and proposed integer constants to be powers of 2 starting from 1)
If taking reflection into account then developer should be aware about underlying behaviour. Because if he uses getDeclaredMethod then he is accessing Java code (which is generated by the Kotlin compiler). For instance how developer should access copy method for data class using reflection?

data class MyDataClass(val p: Int)

val receiver = MyDataClass(123)
val p = MyDataClass::class.java.getDeclaredMethod("copy", /* what goes here? */)
val copyOfTheReceiver = p.invoke(receiver, /* what goes here? */)

However the goal of inline enum class is to provide type safety for integer constants at “Kotlin level” (if you can say so… :slight_smile: ), so there is no real need in reflection while using them.
Nevertheless your example will work without exceptions, but it should be modified a little bit:

inline enum class SomeEnum(val p: Int) { A(115); }

val p = SomeEnum::java.class.getDeclaredMethod("getP", Int::class.java)
// first param is null because getP is a static method
println(p.invoke(null, SomeEnum.A))  // prints 115
println(p.invoke(null, 1)) // prints 115 the same with no exceptions thrown

// However:
println(p.invoke(null, 3)) // java.lang.IllegalArgumentException: wrong receiver: 3

Not quite. As stated here developers of inline class feature were concerned with “not using extra memory”

Sometimes it is necessary for business logic to create a wrapper around some type. However, it introduces runtime overhead due to additional heap allocations. Moreover, if the wrapped type is primitive, the performance hit is terrible, because primitive types are usually heavily optimized by the runtime, while their wrappers don’t get any special treatment.

As I see it right now this is the main flaw. Because indeed they mustn’t be an Integer at compile time. But on the other hand, at runtime they are. So I’m not sure if this should be a correct behaviour or not. There are 2 options on how to fix this (IMHO):

  • Just don’t care and let it cast to Integer. With this approach if we cast it back to SomeEnum, then compiler should change it to (int) x at runtime and everything should work fine.
  • Use wrappers which I wouldn’t prefer :frowning:

To sum up all of this, my proposal is the following:

Let the inline enum class be an enumeration of integer constants which at compile time are type safe, because developer knows (and compiler ensures) that a variable of this type would only become one of predefined integer constants

Although they are ints at runtime and a variable can become whichever value at runtime, compiler should ensure that:

inline enum class SomeEnum { A, B, C }
val someEnum: SomeEnum

// this statement is exhaustive with no `else` required
when (someEnum) {
    SomeEnum.A -> ...
    SomeEnum.B -> ...
    SomeEnum.C -> ...
}

I’m double checking this once again, and it looks to me like this has been solved

val a = VkResult.SUCCESS
println(a == VkResult.SUCCESS)

gets translated to

   L0
    LINENUMBER 4 L0
    GETSTATIC vkk/VkResult.Companion : Lvkk/VkResult$Companion;
    INVOKEVIRTUAL vkk/VkResult$Companion.getSUCCESS ()I
    ISTORE 0
   L1
    LINENUMBER 5 L1
    ILOAD 0
    GETSTATIC vkk/VkResult.Companion : Lvkk/VkResult$Companion;
    INVOKEVIRTUAL vkk/VkResult$Companion.getSUCCESS ()I
    INVOKESTATIC vkk/VkResult.equals-impl0 (II)Z
   L2
    ISTORE 1
   L3
    ICONST_0
    ISTORE 2
   L4
    GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
    ILOAD 1
    INVOKEVIRTUAL java/io/PrintStream.println (Z)V

As far as I know, since there is no box-impl then there should be no boxing involved, simple and plain ints

1 Like