Search code examples
cdartdart-ffi

How to return and correctly identify a duplicate enum from C to dart using dart ffi?


I stepped over this enum in C:

typedef enum
{
  UndefinedGravity,
  ForgetGravity = 0,
  NorthWestGravity = 1,
  NorthGravity = 2,
  NorthEastGravity = 3,
  WestGravity = 4,
  CenterGravity = 5,
  EastGravity = 6,
  SouthWestGravity = 7,
  SouthGravity = 8,
  SouthEastGravity = 9
} GravityType;

I built its corresponding enum type in dart:

enum GravityType {
  UndefinedGravity(0),
  ForgetGravity(0),
  NorthWestGravity(1),
  NorthGravity(2),
  NorthEastGravity(3),
  WestGravity(4),
  CenterGravity(5),
  EastGravity(6),
  SouthWestGravity(7),
  SouthGravity(8),
  SouthEastGravity(9);

  final int value;

  const GravityType(this.value);

  static GravityType fromValue(int value) => GravityType.values.firstWhere((e) => e.value == value);
}

You can see that UndefinedGravity and ForgetGravity have the same int value in C, and theyhave the same int value in dart.

Now consider this C function:

int myFunc(...){
GravityType gt = ForgetGravity;
return gt;
}

If I call this function from dart, it will return a dart int (0 in this case). Then I can call GravityType.fromValue(returnedValue) But how can I know if it represents UndefinedGravity or ForgetGravity so I can map it properly in dart?The current implementation of fromValue is naiive and will return the first match of the int value, so how can I return the same enum as the one that was meant to be sent from C?


Solution

  • Let's take the following enum:

    // Broken example, firstType and secondType have the same value but aren't equal.
    enum MyType {
      firstType(0),
      secondType(0),
      thirdType(123);
    
      final int value;
      const MyType(this.value);
      static MyType fromValue(int value) => MyType.values.firstWhere((e) => e.value == value);
    }
    

    The ideal solution for sparse, overlapping enum values is to declare them as static const:

    enum MyType {
      firstType(0),
      thirdType(123);
    
      // Make the overlapping value an alias of the first
      static const secondType = firstType;
    
      final int value;
      const MyType(this.value);
      static MyType fromValue(int value) => MyType.values.firstWhere((e) => e.value == value);
    }
    

    Now MyType.firstType == MyType.secondType returns true, secondType feels the same as a real enum value but is really just an alias.

    One more problem you might notice is that print(MyType.secondType) confusingly prints MyType.firstType, this can be solved by making a new value with a more descriptive name:

    enum MyType {
      firstOrSecondType(0),
      thirdType(123);
    
      // Both values are now an alias of firstOrSecondType
      static const firstType = firstOrSecondType;
      static const secondType = firstOrSecondType;
    
      final int value;
      const MyType(this.value);
      static MyType fromValue(int value) => MyType.values.firstWhere((e) => e.value == value);
    }
    

    Enums in C do not store their name, their literal values are ints, so no information is lost by giving both firstType and secondType the same name.