User-defined Value Types

@maurelian suggested a feature for Solidity that has a very good implementation cost / usefulness ratio in my opinion. It would be great to get some feedback on it from Solidity users:

(issue description copied here for convenience)

It should be possible to define a user-defined type that is identical to a provided value type (including operators, members, etc.), but cannot be implicitly converted to any other type, and can only be explicitly converted to and from the underlying value type.

This has the effect that arithmetic operations are impossible to perform across these types.

Example (syntax under discussion):

typedef DistanceInMeters uint;
typedef DistanceInInch uint;
typedef Price fixed128x10;

DistanceInMeters distanceToDestination;
Price buyerPrice;
Price sellerPrice;

In the above, you can do buyerPrice - sellerPrice, but you cannot do buyerPrice * distanceToDestination.

4 Likes

It looks good and very straightforward solution. What’s about expressions like buyerPrice + 1? Would 1 be converted to Price type? And how to convert two types from different codebases: Would it work like PriceA(PriceB(1)) or with intermediate type conversion, like PriceA(uint(PriceB(1))?

Hi, I would love to see this. It would also be great if these type were exported as part of the abi too so type safety could be preserved at the calling side (in language that can support it)

We could also go further with conversion function between the type.

A language that I implement this entirely at complile time is haxe, they call them ‘abstract type’ see Abstract - Haxe - The Cross-platform Toolkit

Just for clarity in communication, these are called Nominal Types. I’m a huge fan of it as it adds robustness to the type system.

2 Likes

So it looks like there is some interest in this feature, great!

IMO, buyerPrice + 1 would not be possible, you would need to do buyerPrice + Price(1) and there is also no explicit conversion without going through the underlying type, although I guess it would be fine to allow this if the underlying type is the same.

Are there any suggestions for the syntax?

Assuming it doesn’t create a bunch of implementation difficulty, I have a weak preference for literals being implicitly converted, just not variables. So buyerPrice + 1 is fine, but buyerPrice + someConstantWithDifferentType is not.

If operators are inherited the feature loses most of its value IMO.

As an example you shouldn’t be able to add timestamps to one another. On the other hand, subtraction makes sense but the result is not a timestamp.

I agree with @hrkrshnn in #11531 that this should be more like Haskell’s newtype. Personally I think arithmetic operators don’t need to be overloaded for this, and normal functions should be used.


I’ve long wanted structs with private fields, but if this feature is easier to build, with newtype semantics it would solve some of the same problems.

I think buyerPrice + 1 should be implicitly converted, because it makes code more readable and less noisy.

What’s about syntax. I’d like to use type instead of typedef to define custom types. And rename current type into reflect as “reflection” is widely adopted name for this kind of functionality and better suits for function name.

I completely agree with this.

I think the hack mentioned by @hrkrshnn in that issue shows that the newtype pattern would be useful for types other than numeric values. Note that this hack is actually used in practice, for example, by Balancer v2.

It seems there are somehow conflicting opinions about operators, but I think it could be possible to find a common ground. For example by explicitly specifying which operators are included from the underlying type (see the issue).

Would it be useful to allow to define “custom operators”? For example an operator that is implemented through a user-defined function and defines “timestamp - timestamp → timedifference”?

I am not sure any more about defining arithmetic operators on such types. Mainly about the question on whether safemath should apply for such operators. If it does, should unchecked blocks use wrapping arithmetic?

Wrote about it in the issue: User-defined value types ¡ Issue #11531 ¡ ethereum/solidity ¡ GitHub

Would it be useful to allow to define “custom operators”?

If we decide to define operators on custom types, explicitly defining it should be the way to go. And somehow the behaviour of operators in unchecked blocks should also be made clear from the declaration.

Renaming the current type would be a breaking change and also an unnecessary change, in my opinion. Ideally, we could find a name that’s intuitive and doesn’t require breaking existing contracts.

Yeah I think if people want arithmetic operators it makes sense to be able to derive them, but I think it should be done by explicitly opting in to each operator as suggested in the issue.

Regarding unchecked for these operators I think it would make sense to inherit the same semantics that all operators currently have.

“Custom operators” as suggested by @chriseth don’t sound like a good idea to me at the moment, because it’s not very clear how they would behave in an unchecked block.

We have been discussing this issue in the latest meeting and did not really come to a conclusion. It would be very helpful for us if some people in this thread could share example code that would benefit from this feature. It does not have to be code that actually makes use of the feature, but code that you think could be rewritten using the feature would already be very very valuable for us.

Two examples from an upcoming Timers module:

Ah, thanks for that example! So you are also using using Timers for Timestamp, right?

Perhaps an example where disallowing operators is important is for library fixed points.

Say, a library designs a fixed point type based on uint.

type FixedPoint = uint deriving(==);

means that users wouldn’t accidentally use +, or * operator on these types.

… although + would be totally fine :wink:

Yeah, using Timers for Timers.Timestamp actually.

We recently had some more ideas about operators and the using statement. Since we for now will implement user-defined value types simply without operators, I created a new thread for this more expansive and less specified topic: User-defined types and operators