Description
Issue To Be Solved
Currently, we allow for C-style enumerations in Cadence, for instance:
pub enum Colour: UInt8 {
pub case Red
pub case Green
pub case Blue
}
While we offer the developer flexibility in choosing the underlying type for their enum, we restrict the possible enum values themselves to a monotonic sequence [0..]. This is unnecessarily restrictive and prevents patterns like bitmasks, enum values that map directly to foreign types, etc.
For clarity, I will use discriminant
and value
interchangeably to refer to the raw integer representation of an enum.
A similar issue, #1528, suggests that enums be extended to support strings as well. Without the ability to specify custom discriminant values, any implementation for that issue would be stuck using Cadence-formatted string values for enums. For instance,
pub enum Colour: String {
pub case Red
pub case Green
pub case Blue
}
Colour.Red.rawValue // "Red"
Colour.Green.rawValue // "Green"
Colour.Blue.rawValue // "Blue"
In this example, what happens if a script wants to marshal a string key into a string-backed enum? While it makes sense to use each case
's raw name as its string value in most cases, it's unnecessarily restrictive in situations where a foreign type already exists and uses a different naming convention. For organizations that use json-schema or OpenAPI, it may be clearer to use a consistent key scheme across components, rather than having Cadence values be the sole exception and require string conversions.
Extending enums to support strings is outside the scope of this feature, but this is a stepping stone to adding that functionality to Cadence.
Suggested Solution
Modify enum declarations to allow developers to supply custom values. If an assignment is omitted for a given enum case
, then the value is inferred to be either 0
if it's the first case
, or the previous case
+ 1.
A few examples:
Bitmasks:
pub enum KittyTraits: UInt8 {
pub case Floofy = 1 << 0 // 0x1
pub case Rude = 1 << 1 // 0x2
...
pub case Cute = 1 << 7 // 0x80
}
Starting an enum sequence from a different number:
pub enum HTTPError: UInt16 {
pub case BadRequest = 400
pub case Unauthorized // automatically set to 401
pub case PaymentRequired // 402
pub case Forbidden // 403
...
pub case InternalServerError = 500 // count resumes from this value
pub case NotImplemented // 501
...
}
Declaring enum cases in a different order from their raw values, which can improve code clarity:
pub enum TopShotPlayersChicago: UInt16 {
pub case Antetokounmpo = 37
pub case Ball = 2
pub case Bradley = 13
pub case Caruso = 6
pub case DeRozan = 11
...
}
Considerations
Allowing custom discriminant values comes with some caveats:
-
Overflow checking
Any custom values supplied by the developer need to fit within the enum's backing type. For example, 257 is not a validrawValue
for an enum backed byUInt8
. I believe we already have some logic in the checker to verify for a given integer typetype
thatnumCases <= maxValue(type)
. -
Uniqueness
No two enum tags may share the samerawValue
. This can be verified in the typechecking stage. -
Enum values must be constant expressions.
We currently restrict enums to be backed by integer types only, but allowing arbitrary discriminant values raises the question of how much computation to allow when verifying and rewriting enum definitions. We currently build lookup tables for enum constructors and switch statements, which requires all discriminant values to be statically known.
@dsainati1 has recently added view
functions to Cadence, which are an opt-in feature that allows pure functions to be statically declared as such. Purity in this case however, denotes observable side effects, not constant expressions.
Therefore, a potential approach to this problem is to require that custom enum values consist solely of integer literals and arithmetic expressions. This proposal prohibits function calls in constant contexts for the time being. In the future, once Cadence becomes a compiled language and we formalize constant expressions in the language, this restriction could be lifted to permit certain classes of functions to be called when computing enum values, similar to Rust's const fn
feature.