Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support Equatable or HashableStruct subtypes #3246

Open
sisyphusSmiling opened this issue Apr 16, 2024 · 6 comments
Open

Support Equatable or HashableStruct subtypes #3246

sisyphusSmiling opened this issue Apr 16, 2024 · 6 comments

Comments

@sisyphusSmiling
Copy link
Contributor

sisyphusSmiling commented Apr 16, 2024

Issue to be solved

Dictionary values must be hashable and equatable. With Cadence 1.0, there was the introduction of the HashableStruct super type, however the implementation of HashableStruct is limited to a subset of primitive types.

It would be very helpful for anyone to be able to define a type that conforms to HashableStruct or at least EquatableStruct if it existed. One such use case is the EVMAddress type. In both Solidity and Cadence, it's a common pattern to index mappings on addresses. With the introduction of Flow EVM, it would be very useful to also index mappings on EVMAddress but this is not possible at the moment as the type is not hashable or equatable.

More broadly, it would be even more useful to leverage the implementation of HashableStruct to define custom comparators, but that might be too broad of a scope for this single issue.

Suggested Solution

Perhaps HashableStruct could be a native interface or an FVM contract (like Crypto) could be introduced to house similar interfaces which a type could implement with a single method, such as:

access(all) fun equals(_ other: {HashableStruct}): Bool

In the concrete use case mentioned above of EVMAddress, this would look like:

access(all) struct EVMAddress : HashableStruct {
  // ...
  access(all) fun equals(_ other: {HashableStruct}): Bool {
    if self.getType() != other.getType() {
      return false
    }
    let otherAddress = other as! EVMAddress
    return self.bytes == otherAddress.bytes
  }
}
@sisyphusSmiling sisyphusSmiling changed the title Support Equatable or HashablStruct subtypes Support Equatable or HashableStruct subtypes Apr 16, 2024
@turbolent
Copy link
Member

This was a previously proposed feature: d3191a3#diff-75060942f377544ff403d04b6d3691f7cfeca9f76fea62c8b5c8b1a858a6f555L555-L707

Adding EquatableStruct should be fairly easy.

Exposing HashableStruct as an interface that user-defined types can implement is a bit more complicated, as the hash of a value also needs to include a unique type indicator. For example, internally, HashableValue's HashInput generates the input for the hasher. We might want to have a similar API in Cadence. For example, in Swift, the function is func hash(into hasher: inout Hasher)

@bluesign
Copy link
Contributor

exposing HashableStruct as an interface that user-defined types can implement is a bit more complicated

I think this is very very hard, if not impossible.

@turbolent
Copy link
Member

One other aspect I can think of is the type changing / being updated

@turbolent
Copy link
Member

exposing HashableStruct as an interface that user-defined types can implement is a bit more complicated

I think this is very very hard, if not impossible.

@bluesign How so? Because of the type input requirement I noted above, or because of something else?

@bluesign
Copy link
Contributor

bluesign commented Apr 19, 2024

@bluesign How so? Because of the type input requirement I noted above, or because of something else?

hash should be something that cannot change over time in my opinion. Imagine I have some HashableStruct, I wrote a hash function returns 42. Put it into a dictionary, then I changed hash function to return 0.

same problem stays if we give some data as hashInput on Cadence side too, what happens if I change the hashInput function.

@sisyphusSmiling
Copy link
Contributor Author

sisyphusSmiling commented May 21, 2024

@bluesign good point, this isn't possible with any of the current features of Cadence. What if we had a function modifier called immutable which denotes that the function and its contents cannot be changed in future updates and can only reference deterministic actions or other immutable functions and values. In the case of struct fields, let would be implicitly immutable since it cannot change or be removed.

Returning to the concrete case of the EVMAddress, it would look something like

access(all) struct EVMAddress: Addressable, HashableStruct {
  access(all) let bytes: [UInt8; 20]
  // ...
  access(all) immutable fun hash(): String {
    return self.bytes.toVariableSizedArray().encodeHex()
  }  
}

I don't know if the effort required to implement the immutable modifier, but I would think something like this would avoid the problem you raised.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants