move-language / move

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Replace Or Supplement std::type_name with std::struct_tag

PaulFidika opened this issue · comments

So currently if you want to do type introspection, you use the std::type_name::get() function to get a TypeName, then convert it into a string, and then parse that string to the type-name up into one of its component pieces (i.e., package-id or module-name). We've written a bunch of utility functions that do this parsing:

https://github.com/capsule-craft/capsules/blob/master/packages/sui_utils/sources/encode.move

However, what would be simpler and more useful would be to skip strings altogether, and simply return StructTags, like this:

struct StructTag has store, copy, drop {
        package_id: address,
        module_name: string::String,
        struct_name: string::String,
        generics: vector<string::String>
    }

Note that it might have been interesting to make make struct_tag.generics be of type vector instead, but this sort of recursive type definition is not allowed, so the generics will just have to be strings.

For Sui Move, it might be interesting to have package_id be a sui::object::ID instead of an address, but for the Move core IDs do not exist.

We've implemented StructTags here:

https://github.com/capsule-craft/capsules/blob/master/packages/sui_utils/sources/struct_tag.move

Currently these are created using string-parsing, but if StructTags were created natively within Move it would presumably be a lot cheaper to construct them than having to parse ascii strings. These StructTags can be used to compare object-types rather easily; i.e., are they from the same package, the same module, are they the same struct but with different generics, etc.

In addition to being faster and simpler to work with, than TypeNames StructTags are also smaller to store. Addresses are encoded as hex in ascii, meaning that a 32 byte address uses up 64 bytes of storage as a hex-ascii string. I honestly can't really think of any downsides.

As an edge-case, for primitive types like address, bool, or u64, we could return this:

StructTag {
   package_id: 0x0,
   module_name: "",
   struct_name: "u64",
   generics: [ ]
}

Or we could make package_id be optional and just leave it empty in this case.

Quick thoughts:

  • We did think about something similar to this when building type_name, but wanted to bias toward reflection API's that encourage simple, easy-to-reason about usage (e.g., comparison of typenames). We also wanted a representation that would work for both structs and primitive types--felt a bit uncomfortable with patterns like introducing "dummy struct tags" for primitive types
  • Clearly, the existence of type_name::into_string makes it possible to go deeper down the reflection rabbit hole, even if it's (somewhat intentionally) inconvenient
  • I'm not necessarily opposed to something like an in-Move representation of StructTag, but I'd like to understand what sort of operations you'd like to do on a StructTag (if there are only a few) and see if we can implement those more directly via native functions on TypeName if so. If that's possible, I think it will make it much easier for programmers and static analysis tools to reason about our reflection APIs

Sure, usually I use it like:

  1. finding the package-ID of a type, and then storing that somewhere. For example creating a Type<T> object (Damir calls these Display<T>); knowing the package from which T originated is key because you need to present a publish-receipt from T in order to create it.

  2. For Type T, I figure out its package id, module name, and then construct a string and take the hash of <package-id>::<module_name>::Witness and convert it into an address. This is that module's authority-address. This allows modules to "sign" transactions. I.e., if I have a tx_authority which contains a module's authority address, then that module must have produced an instance of Witness and used it to sign the transaction. This is like a more general, dynamic way to produce 'proofs' that a module is signing off on a certain action, rather than presenting the Witness directly itself.