How do I detect generics with reflection?

I wish to write a generic-ish serializer that converts Zig values into “equivalent” Lua values. I already have a semi-functional version of such serializer that handles ints, floats, bools, strings and structs that contain those. However, I also want to have special serialization routines for certain generic containers as well, for example: serializing ArrayList(T) as a normal Lua array, or serializing StringHashMap(T) as a Lua table. Those types, to my knowledge, are generic, and I couldn’t find a way to determine if whatever type of anytype a function receives is derived from some form of generic type. Is there a way to do this?

Hi!

There’s no sure way of detecting whether the type of anytype parameter is generic. I think you’re better off restricting your serializer to support only a few generic types from std, treating each of them as separate special cases.

But how would I do that? Currently I have one big switch statement over the type info of whatever I want to serialize. When it comes to the .Struct variant, what exactly can I do to check that its an ArrayList of some other type? I could try sprinkling some @hasField around to check for some fields from these types, but I can’t imagine such way being robust or futureproof.

I’m afraid it’ll have to be something like that. I’d try getting the string name of the type with @typeName(@TypeOf(value)) and then searching that string to determine if its one of the generic types you’re expecting.

Or even better, make your serializer accept the type to serialize, like here: s2s/s2s.zig at master · ziglibs/s2s · GitHub

1 Like

An interesting case is the json serializer. It defines Value that can be Array = ArrayList(Value) or ObjectMap = StringArrayHashMap(Value).
You can do something similar for the Lua values.
WriteStream.write have the type info switch and the json encoding is defined in std.json.stringify.WriteStream

1 Like

Or even better, make your serializer accept the type to serialize, like here: s2s/s2s.zig at master · ziglibs/s2s · GitHub

I’m not sure I understand the purpose of accepting type along with the anytype value if the type of the value could be derived with @TypeOf. Perhaps it would be better to provide inner type of the container and check every supported container with that type internally, but that wouldn’t scale, especially since containers can have arbitrarily many inner types. Besides, serialization is a recursive process, and I don’t have the luxury to specify inner types of a struct field.

An interesting case is the json serializer.

I don’t think it does anything special either. It does have an interesting property of checking if type has some special serialization function, and then invoke that function to do serialization. This could work for my own types, but since Zig doesn’t support method extensions/monkey patching, I’m afraid this solution won’t work with types from standard library.

I’d try getting the string name of the type with @typeName(@TypeOf(value)) and then searching that string to determine if its one of the generic types you’re expecting.

I suppose I could do something like that if there isn’t a better way. :frowning_face:

If you know what you’re looking for, you can try to reconstruct it and test for equality. For ArrayListAligned and ArrayList:

  1. Check for a struct @typeInfo(T) == .Struct
  2. Non-tuple !info.Struct.is_tuple
  3. Check for an items field @hasField(T, "items")
  4. Check for it to be a pointer @typeInfo(@TypeOf(@field(@as(T, undefined), "items"))) == .Pointer
  5. Get its alignment and child type
  6. Check for equality T == ArrayListAligned(ptr.child, ptr.alignment)

If then you’ll know.

Honestly, it’s probably not worth the hassle. It’s a lot of boilerplate and guesswork for not much. But it should work (not on my computer rn, someone please test it).

2 Likes

since you’re targeting a specific “foreign” language, the pattern i’ve seen used in countless serializers would have you define a suite of Zig types named LuaInt, LuaBool, LuaString, and so forth – each effectively an alias to the equivalent in Zig…

you could then define your own LuaArray(T) and LuaTable(T) generic types, which would internally use a Zig ArrayList(T) and StringHashMap(T) in their respective implementation…

once you’ve created these “wrapper” types, it is trivial to add a distinquished named constant (eg., LUA_TYPE) to the implementing struct; your serialize would then be looking for .Struct types that also have a container-level decl named LUA_TYPE for instance – easily discoverable through zig reflection…

these wrapper types are also a great place to implement the actual serialization/deserialization routines… another aspect of this approach (some would call it a feature, others a bug) is that you’ll expose methods with names/signatures of your choosing for manipulating these LUA container…

i’ve recently been reading about ziggy-pydust, which strives to make the zig-rendition-of-python-idioms as “pythonic” as possible… zig’s comptime metaprogramming in fact goes a long way towards achieving this objective…