<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Eddie Burns's Blog]]></title><description><![CDATA[Personal blog for Eddie Burns (the software engineer, not the actor/director). 

Opinions are my own and do not reflect those of my employer.]]></description><link>https://burnsthoughts.dev</link><generator>RSS for Node</generator><lastBuildDate>Wed, 03 Jun 2026 23:51:19 GMT</lastBuildDate><atom:link href="https://burnsthoughts.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Battling with Go's Generics]]></title><description><![CDATA[A while back, I needed a small Go utility to fetch and unmarshal protobuf files.
Basically, I needed to read various types serialized protobuf files from S3. We had a half-dozen types of protobufs, and a bunch of boilerplate to read the different fil...]]></description><link>https://burnsthoughts.dev/battling-with-gos-generics</link><guid isPermaLink="true">https://burnsthoughts.dev/battling-with-gos-generics</guid><category><![CDATA[Programming Blogs]]></category><category><![CDATA[Go Language]]></category><category><![CDATA[coding]]></category><dc:creator><![CDATA[Edward Burns]]></dc:creator><pubDate>Wed, 10 Dec 2025 19:09:07 GMT</pubDate><content:encoded><![CDATA[<p>A while back, I needed a small Go utility to fetch and unmarshal <a target="_blank" href="https://protobuf.dev/">protobuf</a> files.</p>
<p>Basically, I needed to read various types serialized protobuf files from S3. We had a half-dozen types of protobufs, and a bunch of boilerplate to read the different file types.</p>
<p>I wanted a simple utility, which could be used like:</p>
<pre><code class="lang-go">fooFetcher := NewProtobufFetcher[FooProtobufRecord](bucket) 
protos, err := fooFetcher.fetch(filePaths) <span class="hljs-comment">// returns list of type *FooProtobufRecord</span>
</code></pre>
<p>The caller would specify the proto type, and the tool would unmarshal each file to that record type. The specifics aren’t terribly important, but I wanted it to be really fast.</p>
<p>And to make it fast, I wanted to use generics instead of reflection. Sounds easy, right?</p>
<h3 id="heading-not-so-fast">Not so fast!</h3>
<p>Surprisingly, it was… not so easy.</p>
<p>In my library, I wanted to convert raw bytes into a Go object using the Go protobuf library’s <code>Unmarshal</code> method:</p>
<pre><code class="lang-go"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">Unmarshal</span><span class="hljs-params">(b []<span class="hljs-keyword">byte</span>, m Message)</span> <span class="hljs-title">error</span></span>
</code></pre>
<p>The argument <code>m</code> is what we’re writing <em>into</em>, and must be a pointer to a protobuf type. (It’s a type alias to the <code>protoiface.MessageV1</code> interface.)</p>
<p>So I had some requirements for the library:</p>
<ul>
<li><p>The library user should specify the type of protobuf record using generics</p>
</li>
<li><p>We library needs to instantiate the generic type in order to unmarshal a file into it</p>
</li>
</ul>
<h3 id="heading-naive-attempt-use-a-generic-value-type">Naive Attempt: Use a Generic value type</h3>
<pre><code class="lang-go"><span class="hljs-keyword">type</span> ProtoFetcher[T any] <span class="hljs-keyword">struct</span> {
    ...
}

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">NewProtobufQuerier</span>[<span class="hljs-title">T</span> <span class="hljs-title">any</span>] <span class="hljs-params">()</span></span> {
    <span class="hljs-keyword">return</span> &amp;ProtoFetcher[T]{
        ...
    }
}

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(q *ProtobufFetcher[T])</span> <span class="hljs-title">unmarshal</span><span class="hljs-params">(b []<span class="hljs-keyword">byte</span>)</span> <span class="hljs-params">(*T, error)</span></span> {
    protoMsg := <span class="hljs-built_in">new</span>(T) 
    err := proto.Unmarshal(b, &amp;protoMsg) <span class="hljs-comment">// invalid: Can't use 't' (type *T) as the type Message</span>
    ...

    <span class="hljs-keyword">return</span> msg, <span class="hljs-literal">nil</span>
}
</code></pre>
<p>This code fails on the call to unmarshal, because the <code>Unmarshal</code> function expects a type which implements protobuf’s <code>Message</code> interface. The type <code>[T any]</code> won’t work, we have to be restrict it somehow.</p>
<p>Worse, the <code>new</code> function expects a <em>concrete</em> type, not a pointer type.</p>
<p>So now we need both a generic that’s both:</p>
<ul>
<li><p>a concrete type</p>
</li>
<li><p>able to be converted into a pointer type implements the <code>Message</code> interface.</p>
</li>
</ul>
<h3 id="heading-solution-type-constraints">Solution: Type Constraints</h3>
<p>How can define such a weird constraint? This leads us down the dark rabbit hole of <a target="_blank" href="https://go.dev/blog/intro-generics#type-sets">type constraints</a>.</p>
<p>Type constraints are a deep subject, but the key point is that interfaces can be to restrict the set of types we allow. (I recommend <a target="_blank" href="https://go.dev/blog/intro-generics">this article</a> for more info.)</p>
<p>To setup our type constraint, we define a new interface:</p>
<pre><code class="lang-go"><span class="hljs-keyword">type</span> ProtoMessageType[T any] <span class="hljs-keyword">interface</span> {
    *T
    proto.Message
}
</code></pre>
<p>This interface says: <em>The type T can be anything whose pointer type implements proto.Message.</em></p>
<p>So far, not too crazy.</p>
<p>Our struct, on the other hand, becomes pretty crazy:</p>
<pre><code class="lang-go"><span class="hljs-keyword">type</span> ProtobufFetcher[T any, PT ProtoMessageType[T]] <span class="hljs-keyword">struct</span> {
    ...
}
</code></pre>
<p>Here <code>T</code> is the value type, while <code>PT</code> is the “pointer type” to the same value.</p>
<p>Now the unmarshal function looks like:</p>
<pre><code class="lang-go"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(q *ProtobufFetcher[T, PT])</span> <span class="hljs-title">unmarshal</span><span class="hljs-params">(b []<span class="hljs-keyword">byte</span>)</span> <span class="hljs-params">(PT, error)</span></span> {
    <span class="hljs-keyword">var</span> msg PT
    msg = <span class="hljs-built_in">new</span>(T)

    err := proto.Unmarshal(b, msg)
    <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
        <span class="hljs-keyword">return</span> <span class="hljs-literal">nil</span>, err
    }
    <span class="hljs-keyword">return</span> msg, <span class="hljs-literal">nil</span>
}
</code></pre>
<p>Aside from the bizarre type signature, not too bad! But let’s discuss some oddities.</p>
<p>The <code>new</code> function returns a pointer type of our concrete type <code>T</code>. We have to pre-declare the destination type <code>msg</code> as PT, because otherwise Go will just treat it as <code>*T</code>, which is <em>sort of</em> <em>the same thing</em> but without our type constraint.</p>
<p>At last, we can unmarshal the bytes into the proto object, and return the correct pointer type <code>PT</code>:</p>
<pre><code class="lang-go">err := proto.Unmarshal(b, msg)
<span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
    <span class="hljs-keyword">return</span> <span class="hljs-literal">nil</span>, err
}
<span class="hljs-keyword">return</span> msg, <span class="hljs-literal">nil</span>
</code></pre>
<h3 id="heading-result">Result</h3>
<p>We now have a utility that produces protobuf records of a generic type.</p>
<p>It’s slick to use:</p>
<pre><code class="lang-go">fetcher := NewProtobufFetcher[FooProtoRecord]()
records, err := fetch.FetchProtos(ctx, paths)
</code></pre>
<p>Note there’s a bit of extra <a target="_blank" href="https://go.dev/blog/intro-generics#type-sets">type inference magic</a> here, which let’s the user just specify the value type.</p>
<h3 id="heading-was-it-worth-the-complexity">Was it worth the complexity?</h3>
<p>Honestly, I’m not sure. The library is nice to use, but the bizarre <code>[T, PT]</code> generic types are confusing. I struggled to remember how it worked even as I wrote this.</p>
<p>This level of complexity counter to the spirit of Go. If I did it all over again, I might just create a type registry and do type conversions at runtime.</p>
<p>But it was an interesting learning experience, and I learned a bit about the sharp-edges of Go’s generics.</p>
]]></content:encoded></item></channel></rss>