Head/tail pattern with NamedTuple

I want to create NamedTuples from a parametric type where the parameter is the desired type. Fields are computed using fieldtypes (in the MWE below, makezero). From reading code in Base, I came up with the following solution, is this the right way?

# make a field -- note that this is just for the MWE
makezero(::Type{T}) where T = zero(T)

"Make a `T` of zeros."
struct ZeroMaker{T <: Union{Tuple, NamedTuple}} end

_makezeros(::Type{Tuple{}}) = ()
_makezeros(::Type{T}) where T <: Tuple =
    (makezero(Base.tuple_type_head(T)), _makezeros(Base.tuple_type_tail(T))...)

# this is a pattern I was familiar with
makezeros(::ZeroMaker{T}) where T <: Tuple = _makezeros(T)

# I am asking the question about this part
makezeros(::ZeroMaker{NamedTuple{N,S}}) where {N, S} =
    NamedTuple{N}(_makezeros(S))

This works fine, is there anything I could do simpler/differently?

julia> T = Tuple{Int, Float64}
Tuple{Int64,Float64}

julia> NT = NamedTuple{(:a, :b), T}
NamedTuple{(:a, :b),Tuple{Int64,Float64}}

julia> @code_typed makezeros(ZeroMaker{T}())
CodeInfo(
 1 1 ─     return (0, 0.0)                                                                      │
) => Tuple{Int64,Float64}

julia> @code_typed makezeros(ZeroMaker{NT}())
CodeInfo(
 1 1 ─     return $(QuoteNode((a = 0, b = 0.0)))                                                │
) => NamedTuple{(:a, :b),Tuple{Int64,Float64}}

Did you ever find a better solution to this?

No, this is perfect. You can see it in action in eg StataDTAFiles.jl. Yields very efficient code.