You can also try a generated function like this to avoid the Vector{Any} of fields. Might be faster:
@generated function read_seq_generated(io, ::Type{T}) where T
types = fieldtypes(T)
quote
Base.Cartesian.@ncall $(length(types)) $T i -> read(io, $types[i])
end
end
read(stream, T) isn’t entirely safe either, you rely on the stream’s bytes being neatly arranged in read-supported Julia types. For example, all primitive types are multiple of bytes, but there’s no guarantee that a data stream will adhere to that, maybe each datum only needs 1.5 bytes and you’re expected to bitmask and portably match the platform’s endianness.
A brief explainer, what a generated function can do that a normal generic function can’t is create a function body expression based on the argument types at the call. So read_pad_interpret has the same source code no matter the T, but read_seq_generated will look at Adummy and use the @ncall macro to write the equivalent of Adummy(read(io, types[1]), read(io, types[2]), read(io, types[3]) ... etc which is close to how you might manually write the most specific efficient call for Adummy. I want to hazard a guess that the indexing is done at compile-time because fieldtypes is a tuple, so it should be like if you wrote in the field types by hand too, but if it’s not I think you could write :($T( $(( :(read(io, $ft)) for ft in fieldtypes(T) )...) ))