<?xml version="1.0" encoding="utf-8"?><?xml-stylesheet type="text/xsl" href="rss.xsl"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/">
    <channel>
        <title>ant blog</title>
        <link>https://skudra.net/blog</link>
        <description>Design notes and releases for ant, a typed ORM for Go.</description>
        <lastBuildDate>Wed, 11 Nov 2026 00:00:00 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <language>en</language>
        <copyright>Copyright © 2026 the ant authors</copyright>
        <item>
            <title><![CDATA[Field pointers as column names: compile-checked Go queries without codegen]]></title>
            <link>https://skudra.net/blog/field-pointers-as-column-names</link>
            <guid>https://skudra.net/blog/field-pointers-as-column-names</guid>
            <pubDate>Wed, 11 Nov 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[How ant uses pointers to struct fields as column references, why the chain needed Go 1.27's generic methods, what it costs, and where it still fails.]]></description>
            <content:encoded><![CDATA[<p>Go database libraries make you pick one of two trades. GORM and Bun take column names as strings, so <code>"agee &gt; ?"</code> builds fine and fails the first time it runs. ent and sqlc check every column at build time, but they get there through a schema file or a folder of SQL, plus a generate step that has to run before your code compiles.</p>
<p>I wanted the checking without the generator. Go 1.27 added generic methods, and that turned out to be the missing piece: a chainable query API can now take pointers to your own struct fields as column names. <code>ant.Gt(&amp;Users.Age, 18)</code> names the <code>age</code> column, and the compiler checks both that <code>Age</code> exists and that <code>18</code> is the right type for it. Nothing is generated.</p>
<p>This post is about how that works, what it costs at runtime, and the places where it still fails after compiling. The library is <a href="https://github.com/skudra-go/ant" target="_blank" rel="noopener noreferrer" class="">ant</a>, which I maintain.</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-same-query-in-four-libraries">The same query in four libraries<a href="https://skudra.net/blog/field-pointers-as-column-names#the-same-query-in-four-libraries" class="hash-link" aria-label="Direct link to The same query in four libraries" title="Direct link to The same query in four libraries" translate="no">​</a></h2>
<p>Users over 18 with their posts, sorted by name, 10 at a time.</p>
<div class="theme-tabs-container tabs-container tabList__CuJ"><ul role="tablist" aria-orientation="horizontal" class="tabs"><li role="tab" tabindex="0" aria-selected="true" class="tabs__item tabItem_LNqP tabs__item--active">ant</li><li role="tab" tabindex="-1" aria-selected="false" class="tabs__item tabItem_LNqP">GORM 1.31</li><li role="tab" tabindex="-1" aria-selected="false" class="tabs__item tabItem_LNqP">Bun 1.3</li><li role="tab" tabindex="-1" aria-selected="false" class="tabs__item tabItem_LNqP">ent 0.14</li></ul><div class="margin-top--md"><div role="tabpanel" class="tabItem_Ymn6"><div class="language-go codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#e6eaf2;--prism-background-color:var(--ant-stage)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-go codeBlock_bY9V thin-scrollbar" style="color:#e6eaf2;background-color:var(--ant-stage)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#e6eaf2"><span class="token keyword" style="color:#c4b5fd">var</span><span class="token plain"> Users </span><span class="token operator" style="color:#cdd4e0">=</span><span class="token plain"> ant</span><span class="token punctuation" style="color:#9aa5b8">.</span><span class="token plain">Fields</span><span class="token punctuation" style="color:#9aa5b8">[</span><span class="token plain">User</span><span class="token punctuation" style="color:#9aa5b8">]</span><span class="token punctuation" style="color:#9aa5b8">(</span><span class="token punctuation" style="color:#9aa5b8">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#e6eaf2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#e6eaf2"><span class="token plain">users</span><span class="token punctuation" style="color:#9aa5b8">,</span><span class="token plain"> err </span><span class="token operator" style="color:#cdd4e0">:=</span><span class="token plain"> db</span><span class="token punctuation" style="color:#9aa5b8">.</span><span class="token plain">Users</span><span class="token punctuation" style="color:#9aa5b8">.</span><span class="token plain"></span><br></div><div class="token-line" style="color:#e6eaf2"><span class="token plain">	</span><span class="token function" style="color:#e7deff">Include</span><span class="token punctuation" style="color:#9aa5b8">(</span><span class="token operator" style="color:#cdd4e0">&amp;</span><span class="token plain">Users</span><span class="token punctuation" style="color:#9aa5b8">.</span><span class="token plain">Posts</span><span class="token punctuation" style="color:#9aa5b8">)</span><span class="token punctuation" style="color:#9aa5b8">.</span><span class="token plain"></span><br></div><div class="token-line" style="color:#e6eaf2"><span class="token plain">	</span><span class="token function" style="color:#e7deff">Where</span><span class="token punctuation" style="color:#9aa5b8">(</span><span class="token plain">ant</span><span class="token punctuation" style="color:#9aa5b8">.</span><span class="token function" style="color:#e7deff">Gt</span><span class="token punctuation" style="color:#9aa5b8">(</span><span class="token operator" style="color:#cdd4e0">&amp;</span><span class="token plain">Users</span><span class="token punctuation" style="color:#9aa5b8">.</span><span class="token plain">Age</span><span class="token punctuation" style="color:#9aa5b8">,</span><span class="token plain"> </span><span class="token number" style="color:#f3b0cb">18</span><span class="token punctuation" style="color:#9aa5b8">)</span><span class="token punctuation" style="color:#9aa5b8">)</span><span class="token punctuation" style="color:#9aa5b8">.</span><span class="token plain"></span><br></div><div class="token-line" style="color:#e6eaf2"><span class="token plain">	</span><span class="token function" style="color:#e7deff">OrderBy</span><span class="token punctuation" style="color:#9aa5b8">(</span><span class="token operator" style="color:#cdd4e0">&amp;</span><span class="token plain">Users</span><span class="token punctuation" style="color:#9aa5b8">.</span><span class="token plain">Name</span><span class="token punctuation" style="color:#9aa5b8">)</span><span class="token punctuation" style="color:#9aa5b8">.</span><span class="token plain"></span><br></div><div class="token-line" style="color:#e6eaf2"><span class="token plain">	</span><span class="token function" style="color:#e7deff">Limit</span><span class="token punctuation" style="color:#9aa5b8">(</span><span class="token number" style="color:#f3b0cb">10</span><span class="token punctuation" style="color:#9aa5b8">)</span><span class="token punctuation" style="color:#9aa5b8">.</span><span class="token plain"></span><br></div><div class="token-line" style="color:#e6eaf2"><span class="token plain">	</span><span class="token function" style="color:#e7deff">All</span><span class="token punctuation" style="color:#9aa5b8">(</span><span class="token plain">ctx</span><span class="token punctuation" style="color:#9aa5b8">)</span><br></div></code></pre></div></div></div><div role="tabpanel" class="tabItem_Ymn6" hidden=""><div class="language-go codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#e6eaf2;--prism-background-color:var(--ant-stage)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-go codeBlock_bY9V thin-scrollbar" style="color:#e6eaf2;background-color:var(--ant-stage)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#e6eaf2"><span class="token keyword" style="color:#c4b5fd">var</span><span class="token plain"> users </span><span class="token punctuation" style="color:#9aa5b8">[</span><span class="token punctuation" style="color:#9aa5b8">]</span><span class="token plain">User</span><br></div><div class="token-line" style="color:#e6eaf2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#e6eaf2"><span class="token plain">err </span><span class="token operator" style="color:#cdd4e0">:=</span><span class="token plain"> db</span><span class="token punctuation" style="color:#9aa5b8">.</span><span class="token function" style="color:#e7deff">WithContext</span><span class="token punctuation" style="color:#9aa5b8">(</span><span class="token plain">ctx</span><span class="token punctuation" style="color:#9aa5b8">)</span><span class="token punctuation" style="color:#9aa5b8">.</span><span class="token plain"></span><br></div><div class="token-line" style="color:#e6eaf2"><span class="token plain">	</span><span class="token function" style="color:#e7deff">Preload</span><span class="token punctuation" style="color:#9aa5b8">(</span><span class="token string" style="color:#a7dcc6">"Posts"</span><span class="token punctuation" style="color:#9aa5b8">)</span><span class="token punctuation" style="color:#9aa5b8">.</span><span class="token plain"></span><br></div><div class="token-line" style="color:#e6eaf2"><span class="token plain">	</span><span class="token function" style="color:#e7deff">Where</span><span class="token punctuation" style="color:#9aa5b8">(</span><span class="token string" style="color:#a7dcc6">"age &gt; ?"</span><span class="token punctuation" style="color:#9aa5b8">,</span><span class="token plain"> </span><span class="token number" style="color:#f3b0cb">18</span><span class="token punctuation" style="color:#9aa5b8">)</span><span class="token punctuation" style="color:#9aa5b8">.</span><span class="token plain"></span><br></div><div class="token-line" style="color:#e6eaf2"><span class="token plain">	</span><span class="token function" style="color:#e7deff">Order</span><span class="token punctuation" style="color:#9aa5b8">(</span><span class="token string" style="color:#a7dcc6">"name"</span><span class="token punctuation" style="color:#9aa5b8">)</span><span class="token punctuation" style="color:#9aa5b8">.</span><span class="token plain"></span><br></div><div class="token-line" style="color:#e6eaf2"><span class="token plain">	</span><span class="token function" style="color:#e7deff">Limit</span><span class="token punctuation" style="color:#9aa5b8">(</span><span class="token number" style="color:#f3b0cb">10</span><span class="token punctuation" style="color:#9aa5b8">)</span><span class="token punctuation" style="color:#9aa5b8">.</span><span class="token plain"></span><br></div><div class="token-line" style="color:#e6eaf2"><span class="token plain">	</span><span class="token function" style="color:#e7deff">Find</span><span class="token punctuation" style="color:#9aa5b8">(</span><span class="token operator" style="color:#cdd4e0">&amp;</span><span class="token plain">users</span><span class="token punctuation" style="color:#9aa5b8">)</span><span class="token punctuation" style="color:#9aa5b8">.</span><span class="token plain"></span><br></div><div class="token-line" style="color:#e6eaf2"><span class="token plain">	Error</span><br></div></code></pre></div></div></div><div role="tabpanel" class="tabItem_Ymn6" hidden=""><div class="language-go codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#e6eaf2;--prism-background-color:var(--ant-stage)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-go codeBlock_bY9V thin-scrollbar" style="color:#e6eaf2;background-color:var(--ant-stage)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#e6eaf2"><span class="token keyword" style="color:#c4b5fd">var</span><span class="token plain"> users </span><span class="token punctuation" style="color:#9aa5b8">[</span><span class="token punctuation" style="color:#9aa5b8">]</span><span class="token plain">User</span><br></div><div class="token-line" style="color:#e6eaf2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#e6eaf2"><span class="token plain">err </span><span class="token operator" style="color:#cdd4e0">:=</span><span class="token plain"> db</span><span class="token punctuation" style="color:#9aa5b8">.</span><span class="token function" style="color:#e7deff">NewSelect</span><span class="token punctuation" style="color:#9aa5b8">(</span><span class="token punctuation" style="color:#9aa5b8">)</span><span class="token punctuation" style="color:#9aa5b8">.</span><span class="token plain"></span><br></div><div class="token-line" style="color:#e6eaf2"><span class="token plain">	</span><span class="token function" style="color:#e7deff">Model</span><span class="token punctuation" style="color:#9aa5b8">(</span><span class="token operator" style="color:#cdd4e0">&amp;</span><span class="token plain">users</span><span class="token punctuation" style="color:#9aa5b8">)</span><span class="token punctuation" style="color:#9aa5b8">.</span><span class="token plain"></span><br></div><div class="token-line" style="color:#e6eaf2"><span class="token plain">	</span><span class="token function" style="color:#e7deff">Relation</span><span class="token punctuation" style="color:#9aa5b8">(</span><span class="token string" style="color:#a7dcc6">"Posts"</span><span class="token punctuation" style="color:#9aa5b8">)</span><span class="token punctuation" style="color:#9aa5b8">.</span><span class="token plain"></span><br></div><div class="token-line" style="color:#e6eaf2"><span class="token plain">	</span><span class="token function" style="color:#e7deff">Where</span><span class="token punctuation" style="color:#9aa5b8">(</span><span class="token string" style="color:#a7dcc6">"age &gt; ?"</span><span class="token punctuation" style="color:#9aa5b8">,</span><span class="token plain"> </span><span class="token number" style="color:#f3b0cb">18</span><span class="token punctuation" style="color:#9aa5b8">)</span><span class="token punctuation" style="color:#9aa5b8">.</span><span class="token plain"></span><br></div><div class="token-line" style="color:#e6eaf2"><span class="token plain">	</span><span class="token function" style="color:#e7deff">Order</span><span class="token punctuation" style="color:#9aa5b8">(</span><span class="token string" style="color:#a7dcc6">"name"</span><span class="token punctuation" style="color:#9aa5b8">)</span><span class="token punctuation" style="color:#9aa5b8">.</span><span class="token plain"></span><br></div><div class="token-line" style="color:#e6eaf2"><span class="token plain">	</span><span class="token function" style="color:#e7deff">Limit</span><span class="token punctuation" style="color:#9aa5b8">(</span><span class="token number" style="color:#f3b0cb">10</span><span class="token punctuation" style="color:#9aa5b8">)</span><span class="token punctuation" style="color:#9aa5b8">.</span><span class="token plain"></span><br></div><div class="token-line" style="color:#e6eaf2"><span class="token plain">	</span><span class="token function" style="color:#e7deff">Scan</span><span class="token punctuation" style="color:#9aa5b8">(</span><span class="token plain">ctx</span><span class="token punctuation" style="color:#9aa5b8">)</span><br></div></code></pre></div></div></div><div role="tabpanel" class="tabItem_Ymn6" hidden=""><div class="language-go codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#e6eaf2;--prism-background-color:var(--ant-stage)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-go codeBlock_bY9V thin-scrollbar" style="color:#e6eaf2;background-color:var(--ant-stage)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#e6eaf2"><span class="token plain">users</span><span class="token punctuation" style="color:#9aa5b8">,</span><span class="token plain"> err </span><span class="token operator" style="color:#cdd4e0">:=</span><span class="token plain"> client</span><span class="token punctuation" style="color:#9aa5b8">.</span><span class="token plain">User</span><span class="token punctuation" style="color:#9aa5b8">.</span><span class="token function" style="color:#e7deff">Query</span><span class="token punctuation" style="color:#9aa5b8">(</span><span class="token punctuation" style="color:#9aa5b8">)</span><span class="token punctuation" style="color:#9aa5b8">.</span><span class="token plain"></span><br></div><div class="token-line" style="color:#e6eaf2"><span class="token plain">	</span><span class="token function" style="color:#e7deff">WithPosts</span><span class="token punctuation" style="color:#9aa5b8">(</span><span class="token punctuation" style="color:#9aa5b8">)</span><span class="token punctuation" style="color:#9aa5b8">.</span><span class="token plain"></span><br></div><div class="token-line" style="color:#e6eaf2"><span class="token plain">	</span><span class="token function" style="color:#e7deff">Where</span><span class="token punctuation" style="color:#9aa5b8">(</span><span class="token plain">user</span><span class="token punctuation" style="color:#9aa5b8">.</span><span class="token function" style="color:#e7deff">AgeGT</span><span class="token punctuation" style="color:#9aa5b8">(</span><span class="token number" style="color:#f3b0cb">18</span><span class="token punctuation" style="color:#9aa5b8">)</span><span class="token punctuation" style="color:#9aa5b8">)</span><span class="token punctuation" style="color:#9aa5b8">.</span><span class="token plain"></span><br></div><div class="token-line" style="color:#e6eaf2"><span class="token plain">	</span><span class="token function" style="color:#e7deff">Order</span><span class="token punctuation" style="color:#9aa5b8">(</span><span class="token plain">user</span><span class="token punctuation" style="color:#9aa5b8">.</span><span class="token function" style="color:#e7deff">ByName</span><span class="token punctuation" style="color:#9aa5b8">(</span><span class="token punctuation" style="color:#9aa5b8">)</span><span class="token punctuation" style="color:#9aa5b8">)</span><span class="token punctuation" style="color:#9aa5b8">.</span><span class="token plain"></span><br></div><div class="token-line" style="color:#e6eaf2"><span class="token plain">	</span><span class="token function" style="color:#e7deff">Limit</span><span class="token punctuation" style="color:#9aa5b8">(</span><span class="token number" style="color:#f3b0cb">10</span><span class="token punctuation" style="color:#9aa5b8">)</span><span class="token punctuation" style="color:#9aa5b8">.</span><span class="token plain"></span><br></div><div class="token-line" style="color:#e6eaf2"><span class="token plain">	</span><span class="token function" style="color:#e7deff">All</span><span class="token punctuation" style="color:#9aa5b8">(</span><span class="token plain">ctx</span><span class="token punctuation" style="color:#9aa5b8">)</span><br></div></code></pre></div></div></div></div></div>
<p><a href="https://github.com/skudra-go/ant/tree/main/tools/compare" target="_blank" rel="noopener noreferrer" class=""><code>tools/compare</code></a> runs all four against the same PostgreSQL database and checks that they return the same rows. Here is what each one catches at build time. I tested every cell by compiling the mistake:</p>
<table><thead><tr><th>Mistake</th><th style="text-align:center">ant</th><th style="text-align:center">GORM</th><th style="text-align:center">Bun</th><th style="text-align:center">ent</th><th style="text-align:center">sqlc</th></tr></thead><tbody><tr><td>Misspelled column, <code>agee</code></td><td style="text-align:center">compile error</td><td style="text-align:center">runtime</td><td style="text-align:center">runtime</td><td style="text-align:center">compile error</td><td style="text-align:center"><code>sqlc generate</code></td></tr><tr><td><code>"18"</code> for an <code>int</code></td><td style="text-align:center">compile error</td><td style="text-align:center">runtime</td><td style="text-align:center">runtime</td><td style="text-align:center">compile error</td><td style="text-align:center">compile error</td></tr><tr><td>Misspelled relation, <code>Psots</code></td><td style="text-align:center">compile error</td><td style="text-align:center">runtime</td><td style="text-align:center">runtime</td><td style="text-align:center">compile error</td><td style="text-align:center"><code>sqlc generate</code></td></tr><tr><td>Column of another model</td><td style="text-align:center"><code>antvet</code></td><td style="text-align:center">runtime</td><td style="text-align:center">runtime</td><td style="text-align:center">compile error</td><td style="text-align:center"><code>sqlc generate</code></td></tr><tr><td>No generate step</td><td style="text-align:center">yes</td><td style="text-align:center">yes</td><td style="text-align:center">yes</td><td style="text-align:center">no</td><td style="text-align:center">no</td></tr></tbody></table>
<p>GORM is moving in this direction too. It has a <a href="https://gorm.io/docs/the_generics_way.html" target="_blank" rel="noopener noreferrer" class="">generics API</a> and an optional <a href="https://github.com/go-gorm/cli" target="_blank" rel="noopener noreferrer" class="">CLI</a> that generates typed field helpers. That is codegen, but it is opt-in, and if you already run GORM it is the cheapest way to get some of these checks.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-the-compiler-says">What the compiler says<a href="https://skudra.net/blog/field-pointers-as-column-names#what-the-compiler-says" class="hash-link" aria-label="Direct link to What the compiler says" title="Direct link to What the compiler says" translate="no">​</a></h2>
<p>These are from <code>go build</code> on Go 1.27.3, with only the file path trimmed.</p>
<p>A misspelled field:</p>
<div class="language-go codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#e6eaf2;--prism-background-color:var(--ant-stage)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-go codeBlock_bY9V thin-scrollbar" style="color:#e6eaf2;background-color:var(--ant-stage)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#e6eaf2"><span class="token plain">db</span><span class="token punctuation" style="color:#9aa5b8">.</span><span class="token plain">Users</span><span class="token punctuation" style="color:#9aa5b8">.</span><span class="token function" style="color:#e7deff">Where</span><span class="token punctuation" style="color:#9aa5b8">(</span><span class="token plain">ant</span><span class="token punctuation" style="color:#9aa5b8">.</span><span class="token function" style="color:#e7deff">Gt</span><span class="token punctuation" style="color:#9aa5b8">(</span><span class="token operator" style="color:#cdd4e0">&amp;</span><span class="token plain">Users</span><span class="token punctuation" style="color:#9aa5b8">.</span><span class="token plain">Agee</span><span class="token punctuation" style="color:#9aa5b8">,</span><span class="token plain"> </span><span class="token number" style="color:#f3b0cb">18</span><span class="token punctuation" style="color:#9aa5b8">)</span><span class="token punctuation" style="color:#9aa5b8">)</span><br></div></code></pre></div></div>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#e6eaf2;--prism-background-color:var(--ant-stage)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#e6eaf2;background-color:var(--ant-stage)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#e6eaf2"><span class="token plain">Users.Agee undefined (type *User has no field or method Agee)</span><br></div></code></pre></div></div>
<p>A value of the wrong type:</p>
<div class="language-go codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#e6eaf2;--prism-background-color:var(--ant-stage)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-go codeBlock_bY9V thin-scrollbar" style="color:#e6eaf2;background-color:var(--ant-stage)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#e6eaf2"><span class="token plain">db</span><span class="token punctuation" style="color:#9aa5b8">.</span><span class="token plain">Users</span><span class="token punctuation" style="color:#9aa5b8">.</span><span class="token function" style="color:#e7deff">Where</span><span class="token punctuation" style="color:#9aa5b8">(</span><span class="token plain">ant</span><span class="token punctuation" style="color:#9aa5b8">.</span><span class="token function" style="color:#e7deff">Gt</span><span class="token punctuation" style="color:#9aa5b8">(</span><span class="token operator" style="color:#cdd4e0">&amp;</span><span class="token plain">Users</span><span class="token punctuation" style="color:#9aa5b8">.</span><span class="token plain">Age</span><span class="token punctuation" style="color:#9aa5b8">,</span><span class="token plain"> </span><span class="token string" style="color:#a7dcc6">"18"</span><span class="token punctuation" style="color:#9aa5b8">)</span><span class="token punctuation" style="color:#9aa5b8">)</span><br></div></code></pre></div></div>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#e6eaf2;--prism-background-color:var(--ant-stage)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#e6eaf2;background-color:var(--ant-stage)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#e6eaf2"><span class="token plain">cannot use "18" (untyped string constant) as int value in argument to ant.Gt</span><br></div></code></pre></div></div>
<p>A misspelled relation:</p>
<div class="language-go codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#e6eaf2;--prism-background-color:var(--ant-stage)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-go codeBlock_bY9V thin-scrollbar" style="color:#e6eaf2;background-color:var(--ant-stage)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#e6eaf2"><span class="token plain">db</span><span class="token punctuation" style="color:#9aa5b8">.</span><span class="token plain">Users</span><span class="token punctuation" style="color:#9aa5b8">.</span><span class="token function" style="color:#e7deff">Include</span><span class="token punctuation" style="color:#9aa5b8">(</span><span class="token operator" style="color:#cdd4e0">&amp;</span><span class="token plain">Users</span><span class="token punctuation" style="color:#9aa5b8">.</span><span class="token plain">Psots</span><span class="token punctuation" style="color:#9aa5b8">)</span><br></div></code></pre></div></div>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#e6eaf2;--prism-background-color:var(--ant-stage)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#e6eaf2;background-color:var(--ant-stage)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#e6eaf2"><span class="token plain">Users.Psots undefined (type *User has no field or method Psots)</span><br></div></code></pre></div></div>
<p>A relation used as a value in a condition. <code>Eq</code> wants a <code>comparable</code> value, and a slice isn't one:</p>
<div class="language-go codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#e6eaf2;--prism-background-color:var(--ant-stage)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-go codeBlock_bY9V thin-scrollbar" style="color:#e6eaf2;background-color:var(--ant-stage)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#e6eaf2"><span class="token plain">db</span><span class="token punctuation" style="color:#9aa5b8">.</span><span class="token plain">Users</span><span class="token punctuation" style="color:#9aa5b8">.</span><span class="token function" style="color:#e7deff">Where</span><span class="token punctuation" style="color:#9aa5b8">(</span><span class="token plain">ant</span><span class="token punctuation" style="color:#9aa5b8">.</span><span class="token function" style="color:#e7deff">Eq</span><span class="token punctuation" style="color:#9aa5b8">(</span><span class="token operator" style="color:#cdd4e0">&amp;</span><span class="token plain">Users</span><span class="token punctuation" style="color:#9aa5b8">.</span><span class="token plain">Posts</span><span class="token punctuation" style="color:#9aa5b8">,</span><span class="token plain"> </span><span class="token boolean" style="color:#f3b0cb">nil</span><span class="token punctuation" style="color:#9aa5b8">)</span><span class="token punctuation" style="color:#9aa5b8">)</span><br></div></code></pre></div></div>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#e6eaf2;--prism-background-color:var(--ant-stage)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#e6eaf2;background-color:var(--ant-stage)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#e6eaf2"><span class="token plain">[]Post does not satisfy comparable</span><br></div></code></pre></div></div>
<p>None of these messages mention ant. They are ordinary Go errors about ordinary Go fields, and your editor shows them as you type. Rename <code>Age</code> with gopls and every query that uses it follows.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="how-usersage-becomes-a-column">How &amp;Users.Age becomes a column<a href="https://skudra.net/blog/field-pointers-as-column-names#how-usersage-becomes-a-column" class="hash-link" aria-label="Direct link to How &amp;Users.Age becomes a column" title="Direct link to How &amp;Users.Age becomes a column" translate="no">​</a></h2>
<p><code>ant.Fields[User]()</code> returns a probe: a <code>*User</code> that holds no data. Its only job is to have an address. When you write <code>&amp;Users.Age</code>, you get a pointer into that probe at the offset of <code>Age</code>. ant turns that pointer back into a column in three steps.</p>
<ol>
<li class=""><strong>One probe per type.</strong> The first call to <code>Fields[User]()</code> reflects over <code>User</code> once, caches the model (columns, relations, field offsets), allocates one <code>User</code> with <code>new</code>, and records its base address in a package-level list. Later calls return the same pointer.</li>
<li class=""><strong>Find the probe.</strong> Given a pointer, ant scans the list for the probe whose memory range, from its base to base plus <code>unsafe.Sizeof(User{})</code>, contains the address.</li>
<li class=""><strong>Match offset and type.</strong> The address minus the base is the field's offset. ant looks for a field with that offset and with the pointer's static type, <code>reflect.TypeFor[V]()</code>.</li>
</ol>
<p>The type check in step 3 is there because offsets alone are ambiguous. A struct and its first field share an address: <code>&amp;Users.Base</code> and <code>&amp;Users.Base.ID</code> point at the same byte. The type parameter on <code>Resolve</code> tells them apart for free, since the compiler already knows <code>V</code>.</p>
<p>Here is the real code, cut down. The full version is in <a href="https://github.com/skudra-go/ant/blob/f10b2e5766b7d34fc013dc944a37ffe91bfd625b/internal/meta/meta.go#L744-L816" target="_blank" rel="noopener noreferrer" class=""><code>internal/meta/meta.go</code></a>.</p>
<div class="language-go codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#e6eaf2;--prism-background-color:var(--ant-stage)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-go codeBlock_bY9V thin-scrollbar" style="color:#e6eaf2;background-color:var(--ant-stage)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#e6eaf2"><span class="token keyword" style="color:#c4b5fd">func</span><span class="token plain"> Probe</span><span class="token punctuation" style="color:#9aa5b8">[</span><span class="token plain">T any</span><span class="token punctuation" style="color:#9aa5b8">]</span><span class="token punctuation" style="color:#9aa5b8">(</span><span class="token punctuation" style="color:#9aa5b8">)</span><span class="token plain"> </span><span class="token operator" style="color:#cdd4e0">*</span><span class="token plain">T </span><span class="token punctuation" style="color:#9aa5b8">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#e6eaf2"><span class="token plain">	model </span><span class="token operator" style="color:#cdd4e0">:=</span><span class="token plain"> Of</span><span class="token punctuation" style="color:#9aa5b8">[</span><span class="token plain">T</span><span class="token punctuation" style="color:#9aa5b8">]</span><span class="token punctuation" style="color:#9aa5b8">(</span><span class="token punctuation" style="color:#9aa5b8">)</span><span class="token plain"> </span><span class="token comment" style="color:#8590a3;font-style:italic">// reflected once per type, then cached</span><span class="token plain"></span><br></div><div class="token-line" style="color:#e6eaf2"><span class="token plain">	</span><span class="token comment" style="color:#8590a3;font-style:italic">// (under mu: return the existing probe for model, if there is one)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#e6eaf2"><span class="token plain">	p </span><span class="token operator" style="color:#cdd4e0">:=</span><span class="token plain"> </span><span class="token function" style="color:#e7deff">new</span><span class="token punctuation" style="color:#9aa5b8">(</span><span class="token plain">T</span><span class="token punctuation" style="color:#9aa5b8">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#e6eaf2"><span class="token plain">	probes </span><span class="token operator" style="color:#cdd4e0">=</span><span class="token plain"> </span><span class="token function" style="color:#e7deff">append</span><span class="token punctuation" style="color:#9aa5b8">(</span><span class="token plain">probes</span><span class="token punctuation" style="color:#9aa5b8">,</span><span class="token plain"> probe</span><span class="token punctuation" style="color:#9aa5b8">{</span><span class="token plain">unsafe</span><span class="token punctuation" style="color:#9aa5b8">.</span><span class="token function" style="color:#e7deff">Pointer</span><span class="token punctuation" style="color:#9aa5b8">(</span><span class="token plain">p</span><span class="token punctuation" style="color:#9aa5b8">)</span><span class="token punctuation" style="color:#9aa5b8">,</span><span class="token plain"> </span><span class="token function" style="color:#e7deff">uintptr</span><span class="token punctuation" style="color:#9aa5b8">(</span><span class="token plain">unsafe</span><span class="token punctuation" style="color:#9aa5b8">.</span><span class="token function" style="color:#e7deff">Pointer</span><span class="token punctuation" style="color:#9aa5b8">(</span><span class="token plain">p</span><span class="token punctuation" style="color:#9aa5b8">)</span><span class="token punctuation" style="color:#9aa5b8">)</span><span class="token punctuation" style="color:#9aa5b8">,</span><span class="token plain"> model</span><span class="token punctuation" style="color:#9aa5b8">}</span><span class="token punctuation" style="color:#9aa5b8">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#e6eaf2"><span class="token plain">	</span><span class="token keyword" style="color:#c4b5fd">return</span><span class="token plain"> p</span><br></div><div class="token-line" style="color:#e6eaf2"><span class="token plain"></span><span class="token punctuation" style="color:#9aa5b8">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#e6eaf2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#e6eaf2"><span class="token plain"></span><span class="token keyword" style="color:#c4b5fd">func</span><span class="token plain"> Resolve</span><span class="token punctuation" style="color:#9aa5b8">[</span><span class="token plain">V any</span><span class="token punctuation" style="color:#9aa5b8">]</span><span class="token punctuation" style="color:#9aa5b8">(</span><span class="token plain">ptr </span><span class="token operator" style="color:#cdd4e0">*</span><span class="token plain">V</span><span class="token punctuation" style="color:#9aa5b8">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#9aa5b8">(</span><span class="token plain">Ref</span><span class="token punctuation" style="color:#9aa5b8">,</span><span class="token plain"> </span><span class="token builtin" style="color:#a5c4f7">bool</span><span class="token punctuation" style="color:#9aa5b8">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#9aa5b8">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#e6eaf2"><span class="token plain">	addr </span><span class="token operator" style="color:#cdd4e0">:=</span><span class="token plain"> </span><span class="token function" style="color:#e7deff">uintptr</span><span class="token punctuation" style="color:#9aa5b8">(</span><span class="token plain">unsafe</span><span class="token punctuation" style="color:#9aa5b8">.</span><span class="token function" style="color:#e7deff">Pointer</span><span class="token punctuation" style="color:#9aa5b8">(</span><span class="token plain">ptr</span><span class="token punctuation" style="color:#9aa5b8">)</span><span class="token punctuation" style="color:#9aa5b8">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#e6eaf2"><span class="token plain">	t </span><span class="token operator" style="color:#cdd4e0">:=</span><span class="token plain"> reflect</span><span class="token punctuation" style="color:#9aa5b8">.</span><span class="token plain">TypeFor</span><span class="token punctuation" style="color:#9aa5b8">[</span><span class="token plain">V</span><span class="token punctuation" style="color:#9aa5b8">]</span><span class="token punctuation" style="color:#9aa5b8">(</span><span class="token punctuation" style="color:#9aa5b8">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#e6eaf2"><span class="token plain">	mu</span><span class="token punctuation" style="color:#9aa5b8">.</span><span class="token function" style="color:#e7deff">RLock</span><span class="token punctuation" style="color:#9aa5b8">(</span><span class="token punctuation" style="color:#9aa5b8">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#e6eaf2"><span class="token plain">	</span><span class="token keyword" style="color:#c4b5fd">defer</span><span class="token plain"> mu</span><span class="token punctuation" style="color:#9aa5b8">.</span><span class="token function" style="color:#e7deff">RUnlock</span><span class="token punctuation" style="color:#9aa5b8">(</span><span class="token punctuation" style="color:#9aa5b8">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#e6eaf2"><span class="token plain">	</span><span class="token keyword" style="color:#c4b5fd">for</span><span class="token plain"> </span><span class="token boolean" style="color:#f3b0cb">_</span><span class="token punctuation" style="color:#9aa5b8">,</span><span class="token plain"> p </span><span class="token operator" style="color:#cdd4e0">:=</span><span class="token plain"> </span><span class="token keyword" style="color:#c4b5fd">range</span><span class="token plain"> probes </span><span class="token punctuation" style="color:#9aa5b8">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#e6eaf2"><span class="token plain">		</span><span class="token keyword" style="color:#c4b5fd">if</span><span class="token plain"> addr </span><span class="token operator" style="color:#cdd4e0">&lt;</span><span class="token plain"> p</span><span class="token punctuation" style="color:#9aa5b8">.</span><span class="token plain">base </span><span class="token operator" style="color:#cdd4e0">||</span><span class="token plain"> addr </span><span class="token operator" style="color:#cdd4e0">&gt;=</span><span class="token plain"> p</span><span class="token punctuation" style="color:#9aa5b8">.</span><span class="token plain">base</span><span class="token operator" style="color:#cdd4e0">+</span><span class="token plain">p</span><span class="token punctuation" style="color:#9aa5b8">.</span><span class="token plain">model</span><span class="token punctuation" style="color:#9aa5b8">.</span><span class="token plain">Type</span><span class="token punctuation" style="color:#9aa5b8">.</span><span class="token function" style="color:#e7deff">Size</span><span class="token punctuation" style="color:#9aa5b8">(</span><span class="token punctuation" style="color:#9aa5b8">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#9aa5b8">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#e6eaf2"><span class="token plain">			</span><span class="token keyword" style="color:#c4b5fd">continue</span><span class="token plain"></span><br></div><div class="token-line" style="color:#e6eaf2"><span class="token plain">		</span><span class="token punctuation" style="color:#9aa5b8">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#e6eaf2"><span class="token plain">		off </span><span class="token operator" style="color:#cdd4e0">:=</span><span class="token plain"> addr </span><span class="token operator" style="color:#cdd4e0">-</span><span class="token plain"> p</span><span class="token punctuation" style="color:#9aa5b8">.</span><span class="token plain">base</span><br></div><div class="token-line" style="color:#e6eaf2"><span class="token plain">		</span><span class="token keyword" style="color:#c4b5fd">for</span><span class="token plain"> </span><span class="token boolean" style="color:#f3b0cb">_</span><span class="token punctuation" style="color:#9aa5b8">,</span><span class="token plain"> f </span><span class="token operator" style="color:#cdd4e0">:=</span><span class="token plain"> </span><span class="token keyword" style="color:#c4b5fd">range</span><span class="token plain"> p</span><span class="token punctuation" style="color:#9aa5b8">.</span><span class="token plain">model</span><span class="token punctuation" style="color:#9aa5b8">.</span><span class="token plain">Fields </span><span class="token punctuation" style="color:#9aa5b8">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#e6eaf2"><span class="token plain">			</span><span class="token keyword" style="color:#c4b5fd">if</span><span class="token plain"> f</span><span class="token punctuation" style="color:#9aa5b8">.</span><span class="token plain">Offset </span><span class="token operator" style="color:#cdd4e0">==</span><span class="token plain"> off </span><span class="token operator" style="color:#cdd4e0">&amp;&amp;</span><span class="token plain"> f</span><span class="token punctuation" style="color:#9aa5b8">.</span><span class="token plain">Type </span><span class="token operator" style="color:#cdd4e0">==</span><span class="token plain"> t </span><span class="token punctuation" style="color:#9aa5b8">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#e6eaf2"><span class="token plain">				</span><span class="token keyword" style="color:#c4b5fd">return</span><span class="token plain"> Ref</span><span class="token punctuation" style="color:#9aa5b8">{</span><span class="token plain">Model</span><span class="token punctuation" style="color:#9aa5b8">:</span><span class="token plain"> p</span><span class="token punctuation" style="color:#9aa5b8">.</span><span class="token plain">model</span><span class="token punctuation" style="color:#9aa5b8">,</span><span class="token plain"> Field</span><span class="token punctuation" style="color:#9aa5b8">:</span><span class="token plain"> f</span><span class="token punctuation" style="color:#9aa5b8">}</span><span class="token punctuation" style="color:#9aa5b8">,</span><span class="token plain"> </span><span class="token boolean" style="color:#f3b0cb">true</span><span class="token plain"></span><br></div><div class="token-line" style="color:#e6eaf2"><span class="token plain">			</span><span class="token punctuation" style="color:#9aa5b8">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#e6eaf2"><span class="token plain">		</span><span class="token punctuation" style="color:#9aa5b8">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#e6eaf2"><span class="token plain">		</span><span class="token comment" style="color:#8590a3;font-style:italic">// relations are matched the same way</span><span class="token plain"></span><br></div><div class="token-line" style="color:#e6eaf2"><span class="token plain">	</span><span class="token punctuation" style="color:#9aa5b8">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#e6eaf2"><span class="token plain">	</span><span class="token keyword" style="color:#c4b5fd">return</span><span class="token plain"> Ref</span><span class="token punctuation" style="color:#9aa5b8">{</span><span class="token punctuation" style="color:#9aa5b8">}</span><span class="token punctuation" style="color:#9aa5b8">,</span><span class="token plain"> </span><span class="token boolean" style="color:#f3b0cb">false</span><span class="token plain"></span><br></div><div class="token-line" style="color:#e6eaf2"><span class="token plain"></span><span class="token punctuation" style="color:#9aa5b8">}</span><br></div></code></pre></div></div>
<p>Resolution costs about 21 ns and allocates nothing. That is <code>BenchmarkProbeResolution</code> in <code>tests/benchmarks</code>, run five times on a Ryzen 5 3600 with Go 1.27.3, with a handful of probes registered. The scan is linear, so an application with hundreds of models pays more per lookup, but it is still small next to a database round trip.</p>
<p>The part people will rightly poke at: this only works because the Go runtime does not move heap objects. The probe's address has to stay put for the life of the process. Go's garbage collector doesn't compact or move objects today, and nothing in the language promises it never will. ant keeps the original <code>unsafe.Pointer</code> in the list so the probe stays reachable, and <a href="https://github.com/skudra-go/ant/blob/f10b2e5766b7d34fc013dc944a37ffe91bfd625b/internal/meta/meta_test.go#L167" target="_blank" rel="noopener noreferrer" class=""><code>TestProbeSurvivesGC</code></a> forces two collections and checks that the address didn't change. If a future Go release adds a moving collector, that test is where I'll find out, and the design would need pinning or a different identity scheme.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="why-this-needed-go-127">Why this needed Go 1.27<a href="https://skudra.net/blog/field-pointers-as-column-names#why-this-needed-go-127" class="hash-link" aria-label="Direct link to Why this needed Go 1.27" title="Direct link to Why this needed Go 1.27" translate="no">​</a></h2>
<p>A fair objection first: <code>ant.Gt(&amp;Users.Age, 18)</code> is a plain generic function, <code>func Gt[V Ordered](field *V, value V) Cond</code>. That has compiled since Go 1.18. Conditions were never the problem.</p>
<p>The chain was. <code>OrderBy</code> needs to know the type of the field it's given, so it can require an orderable one. <code>Include</code> needs the element type of the relation, so the optional shaping function gets a typed <code>*Query[Post]</code>. Before 1.27, a method could not declare type parameters of its own. Try it with <code>go 1.26</code> in <code>go.mod</code> and the compiler says:</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#e6eaf2;--prism-background-color:var(--ant-stage)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#e6eaf2;background-color:var(--ant-stage)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#e6eaf2"><span class="token plain">generic method requires go1.27 or later</span><br></div></code></pre></div></div>
<p>So the API would have had to use package-level functions, which nest inside out:</p>
<div class="language-go codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#e6eaf2;--prism-background-color:var(--ant-stage)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-go codeBlock_bY9V thin-scrollbar" style="color:#e6eaf2;background-color:var(--ant-stage)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#e6eaf2"><span class="token comment" style="color:#8590a3;font-style:italic">// before 1.27: the reader starts in the middle</span><span class="token plain"></span><br></div><div class="token-line" style="color:#e6eaf2"><span class="token plain">users</span><span class="token punctuation" style="color:#9aa5b8">,</span><span class="token plain"> err </span><span class="token operator" style="color:#cdd4e0">:=</span><span class="token plain"> ant</span><span class="token punctuation" style="color:#9aa5b8">.</span><span class="token function" style="color:#e7deff">OrderBy</span><span class="token punctuation" style="color:#9aa5b8">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#e6eaf2"><span class="token plain">	ant</span><span class="token punctuation" style="color:#9aa5b8">.</span><span class="token function" style="color:#e7deff">Include</span><span class="token punctuation" style="color:#9aa5b8">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#e6eaf2"><span class="token plain">		db</span><span class="token punctuation" style="color:#9aa5b8">.</span><span class="token plain">Users</span><span class="token punctuation" style="color:#9aa5b8">.</span><span class="token function" style="color:#e7deff">Where</span><span class="token punctuation" style="color:#9aa5b8">(</span><span class="token plain">ant</span><span class="token punctuation" style="color:#9aa5b8">.</span><span class="token function" style="color:#e7deff">Gt</span><span class="token punctuation" style="color:#9aa5b8">(</span><span class="token operator" style="color:#cdd4e0">&amp;</span><span class="token plain">Users</span><span class="token punctuation" style="color:#9aa5b8">.</span><span class="token plain">Age</span><span class="token punctuation" style="color:#9aa5b8">,</span><span class="token plain"> </span><span class="token number" style="color:#f3b0cb">18</span><span class="token punctuation" style="color:#9aa5b8">)</span><span class="token punctuation" style="color:#9aa5b8">)</span><span class="token punctuation" style="color:#9aa5b8">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#e6eaf2"><span class="token plain">		</span><span class="token operator" style="color:#cdd4e0">&amp;</span><span class="token plain">Users</span><span class="token punctuation" style="color:#9aa5b8">.</span><span class="token plain">Posts</span><span class="token punctuation" style="color:#9aa5b8">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#e6eaf2"><span class="token plain">	</span><span class="token punctuation" style="color:#9aa5b8">)</span><span class="token punctuation" style="color:#9aa5b8">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#e6eaf2"><span class="token plain">	</span><span class="token operator" style="color:#cdd4e0">&amp;</span><span class="token plain">Users</span><span class="token punctuation" style="color:#9aa5b8">.</span><span class="token plain">Name</span><span class="token punctuation" style="color:#9aa5b8">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#e6eaf2"><span class="token plain"></span><span class="token punctuation" style="color:#9aa5b8">)</span><span class="token punctuation" style="color:#9aa5b8">.</span><span class="token function" style="color:#e7deff">Limit</span><span class="token punctuation" style="color:#9aa5b8">(</span><span class="token number" style="color:#f3b0cb">10</span><span class="token punctuation" style="color:#9aa5b8">)</span><span class="token punctuation" style="color:#9aa5b8">.</span><span class="token function" style="color:#e7deff">All</span><span class="token punctuation" style="color:#9aa5b8">(</span><span class="token plain">ctx</span><span class="token punctuation" style="color:#9aa5b8">)</span><br></div></code></pre></div></div>
<div class="language-go codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#e6eaf2;--prism-background-color:var(--ant-stage)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-go codeBlock_bY9V thin-scrollbar" style="color:#e6eaf2;background-color:var(--ant-stage)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#e6eaf2"><span class="token comment" style="color:#8590a3;font-style:italic">// 1.27: top to bottom</span><span class="token plain"></span><br></div><div class="token-line" style="color:#e6eaf2"><span class="token plain">users</span><span class="token punctuation" style="color:#9aa5b8">,</span><span class="token plain"> err </span><span class="token operator" style="color:#cdd4e0">:=</span><span class="token plain"> db</span><span class="token punctuation" style="color:#9aa5b8">.</span><span class="token plain">Users</span><span class="token punctuation" style="color:#9aa5b8">.</span><span class="token plain"></span><br></div><div class="token-line" style="color:#e6eaf2"><span class="token plain">	</span><span class="token function" style="color:#e7deff">Include</span><span class="token punctuation" style="color:#9aa5b8">(</span><span class="token operator" style="color:#cdd4e0">&amp;</span><span class="token plain">Users</span><span class="token punctuation" style="color:#9aa5b8">.</span><span class="token plain">Posts</span><span class="token punctuation" style="color:#9aa5b8">)</span><span class="token punctuation" style="color:#9aa5b8">.</span><span class="token plain"></span><br></div><div class="token-line" style="color:#e6eaf2"><span class="token plain">	</span><span class="token function" style="color:#e7deff">Where</span><span class="token punctuation" style="color:#9aa5b8">(</span><span class="token plain">ant</span><span class="token punctuation" style="color:#9aa5b8">.</span><span class="token function" style="color:#e7deff">Gt</span><span class="token punctuation" style="color:#9aa5b8">(</span><span class="token operator" style="color:#cdd4e0">&amp;</span><span class="token plain">Users</span><span class="token punctuation" style="color:#9aa5b8">.</span><span class="token plain">Age</span><span class="token punctuation" style="color:#9aa5b8">,</span><span class="token plain"> </span><span class="token number" style="color:#f3b0cb">18</span><span class="token punctuation" style="color:#9aa5b8">)</span><span class="token punctuation" style="color:#9aa5b8">)</span><span class="token punctuation" style="color:#9aa5b8">.</span><span class="token plain"></span><br></div><div class="token-line" style="color:#e6eaf2"><span class="token plain">	</span><span class="token function" style="color:#e7deff">OrderBy</span><span class="token punctuation" style="color:#9aa5b8">(</span><span class="token operator" style="color:#cdd4e0">&amp;</span><span class="token plain">Users</span><span class="token punctuation" style="color:#9aa5b8">.</span><span class="token plain">Name</span><span class="token punctuation" style="color:#9aa5b8">)</span><span class="token punctuation" style="color:#9aa5b8">.</span><span class="token plain"></span><br></div><div class="token-line" style="color:#e6eaf2"><span class="token plain">	</span><span class="token function" style="color:#e7deff">Limit</span><span class="token punctuation" style="color:#9aa5b8">(</span><span class="token number" style="color:#f3b0cb">10</span><span class="token punctuation" style="color:#9aa5b8">)</span><span class="token punctuation" style="color:#9aa5b8">.</span><span class="token plain"></span><br></div><div class="token-line" style="color:#e6eaf2"><span class="token plain">	</span><span class="token function" style="color:#e7deff">All</span><span class="token punctuation" style="color:#9aa5b8">(</span><span class="token plain">ctx</span><span class="token punctuation" style="color:#9aa5b8">)</span><br></div></code></pre></div></div>
<p>I compiled the first version against stub types with <code>go 1.26</code>, so it is a real alternative, not a strawman. It works. But it reads in the opposite order to the SQL it builds, it can't be extended one line at a time, and typing <code>db.Users.</code> in an editor offers nothing useful, because the operations aren't methods. The other way out was methods that take <code>any</code>, which gives up the checks that were the point.</p>
<p>The methods that need their own type parameters are <code>OrderBy</code>, <code>Include</code>, <code>IncludeOne</code>, <code>Join</code> and its variants, <code>Find</code>, <code>With</code>, <code>Column</code>, <code>Omit</code>, <code>GroupBy</code>, <code>Load</code>, <code>Tx</code> on the context, and most of the model builder used for configuration.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="writing-back-change-tracking">Writing back: change tracking<a href="https://skudra.net/blog/field-pointers-as-column-names#writing-back-change-tracking" class="hash-link" aria-label="Direct link to Writing back: change tracking" title="Direct link to Writing back: change tracking" translate="no">​</a></h2>
<!-- -->
<p>Field pointers cover reading and configuration. The other half of the design is how ant writes, and there it doesn't ask you which fields changed. It remembers what it loaded.</p>
<p>GORM's <code>Updates</code> with a struct <a href="https://gorm.io/docs/update.html" target="_blank" rel="noopener noreferrer" class="">skips zero-valued fields</a>:</p>
<div class="language-go codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#e6eaf2;--prism-background-color:var(--ant-stage)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-go codeBlock_bY9V thin-scrollbar" style="color:#e6eaf2;background-color:var(--ant-stage)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#e6eaf2"><span class="token plain">db</span><span class="token punctuation" style="color:#9aa5b8">.</span><span class="token function" style="color:#e7deff">Model</span><span class="token punctuation" style="color:#9aa5b8">(</span><span class="token operator" style="color:#cdd4e0">&amp;</span><span class="token plain">user</span><span class="token punctuation" style="color:#9aa5b8">)</span><span class="token punctuation" style="color:#9aa5b8">.</span><span class="token function" style="color:#e7deff">Updates</span><span class="token punctuation" style="color:#9aa5b8">(</span><span class="token plain">User</span><span class="token punctuation" style="color:#9aa5b8">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#e6eaf2"><span class="token plain">	Name</span><span class="token punctuation" style="color:#9aa5b8">:</span><span class="token plain">   </span><span class="token string" style="color:#a7dcc6">"Ada"</span><span class="token punctuation" style="color:#9aa5b8">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#e6eaf2"><span class="token plain">	Age</span><span class="token punctuation" style="color:#9aa5b8">:</span><span class="token plain">    </span><span class="token number" style="color:#f3b0cb">0</span><span class="token punctuation" style="color:#9aa5b8">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#e6eaf2"><span class="token plain">	Active</span><span class="token punctuation" style="color:#9aa5b8">:</span><span class="token plain"> </span><span class="token boolean" style="color:#f3b0cb">false</span><span class="token punctuation" style="color:#9aa5b8">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#e6eaf2"><span class="token plain"></span><span class="token punctuation" style="color:#9aa5b8">}</span><span class="token punctuation" style="color:#9aa5b8">)</span><br></div></code></pre></div></div>
<div class="language-sql codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#e6eaf2;--prism-background-color:var(--ant-stage)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-sql codeBlock_bY9V thin-scrollbar" style="color:#e6eaf2;background-color:var(--ant-stage)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#e6eaf2"><span class="token keyword" style="color:#c4b5fd">UPDATE</span><span class="token plain"> </span><span class="token string" style="color:#a7dcc6">"users"</span><span class="token plain"> </span><span class="token keyword" style="color:#c4b5fd">SET</span><span class="token plain"> </span><span class="token string" style="color:#a7dcc6">"updated_at"</span><span class="token operator" style="color:#cdd4e0">=</span><span class="token string" style="color:#a7dcc6">'…'</span><span class="token punctuation" style="color:#9aa5b8">,</span><span class="token string" style="color:#a7dcc6">"name"</span><span class="token operator" style="color:#cdd4e0">=</span><span class="token string" style="color:#a7dcc6">'Ada'</span><span class="token plain"> </span><span class="token keyword" style="color:#c4b5fd">WHERE</span><span class="token plain"> </span><span class="token string" style="color:#a7dcc6">"id"</span><span class="token plain"> </span><span class="token operator" style="color:#cdd4e0">=</span><span class="token plain"> </span><span class="token number" style="color:#f3b0cb">1</span><span class="token plain"></span><br></div><div class="token-line" style="color:#e6eaf2"><span class="token plain"></span><span class="token comment" style="color:#8590a3;font-style:italic">-- no age, no active</span><br></div></code></pre></div></div>
<p>GORM documents this, and <code>Select("Age", "Active")</code> or a map writes them. In ant you change the loaded entity and call <code>SaveChanges</code>:</p>
<div class="language-go codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#e6eaf2;--prism-background-color:var(--ant-stage)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-go codeBlock_bY9V thin-scrollbar" style="color:#e6eaf2;background-color:var(--ant-stage)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#e6eaf2"><span class="token plain">user</span><span class="token punctuation" style="color:#9aa5b8">,</span><span class="token plain"> err </span><span class="token operator" style="color:#cdd4e0">:=</span><span class="token plain"> db</span><span class="token punctuation" style="color:#9aa5b8">.</span><span class="token plain">Users</span><span class="token punctuation" style="color:#9aa5b8">.</span><span class="token function" style="color:#e7deff">Find</span><span class="token punctuation" style="color:#9aa5b8">(</span><span class="token plain">ctx</span><span class="token punctuation" style="color:#9aa5b8">,</span><span class="token plain"> id</span><span class="token punctuation" style="color:#9aa5b8">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#e6eaf2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#e6eaf2"><span class="token plain">user</span><span class="token punctuation" style="color:#9aa5b8">.</span><span class="token plain">Age </span><span class="token operator" style="color:#cdd4e0">=</span><span class="token plain"> </span><span class="token number" style="color:#f3b0cb">0</span><span class="token plain"></span><br></div><div class="token-line" style="color:#e6eaf2"><span class="token plain">user</span><span class="token punctuation" style="color:#9aa5b8">.</span><span class="token plain">Active </span><span class="token operator" style="color:#cdd4e0">=</span><span class="token plain"> </span><span class="token boolean" style="color:#f3b0cb">false</span><span class="token plain"></span><br></div><div class="token-line" style="color:#e6eaf2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#e6eaf2"><span class="token plain">err </span><span class="token operator" style="color:#cdd4e0">=</span><span class="token plain"> db</span><span class="token punctuation" style="color:#9aa5b8">.</span><span class="token function" style="color:#e7deff">SaveChanges</span><span class="token punctuation" style="color:#9aa5b8">(</span><span class="token plain">ctx</span><span class="token punctuation" style="color:#9aa5b8">)</span><br></div></code></pre></div></div>
<div class="language-sql codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#e6eaf2;--prism-background-color:var(--ant-stage)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-sql codeBlock_bY9V thin-scrollbar" style="color:#e6eaf2;background-color:var(--ant-stage)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#e6eaf2"><span class="token keyword" style="color:#c4b5fd">UPDATE</span><span class="token plain"> </span><span class="token string" style="color:#a7dcc6">"users"</span><span class="token plain"> </span><span class="token keyword" style="color:#c4b5fd">SET</span><span class="token plain"> </span><span class="token string" style="color:#a7dcc6">"age"</span><span class="token plain"> </span><span class="token operator" style="color:#cdd4e0">=</span><span class="token plain"> $</span><span class="token number" style="color:#f3b0cb">1</span><span class="token punctuation" style="color:#9aa5b8">,</span><span class="token plain"> </span><span class="token string" style="color:#a7dcc6">"active"</span><span class="token plain"> </span><span class="token operator" style="color:#cdd4e0">=</span><span class="token plain"> $</span><span class="token number" style="color:#f3b0cb">2</span><span class="token punctuation" style="color:#9aa5b8">,</span><span class="token plain"> </span><span class="token string" style="color:#a7dcc6">"updated_at"</span><span class="token plain"> </span><span class="token operator" style="color:#cdd4e0">=</span><span class="token plain"> $</span><span class="token number" style="color:#f3b0cb">3</span><span class="token plain"></span><br></div><div class="token-line" style="color:#e6eaf2"><span class="token plain"></span><span class="token keyword" style="color:#c4b5fd">WHERE</span><span class="token plain"> </span><span class="token string" style="color:#a7dcc6">"users"</span><span class="token punctuation" style="color:#9aa5b8">.</span><span class="token string" style="color:#a7dcc6">"id"</span><span class="token plain"> </span><span class="token operator" style="color:#cdd4e0">=</span><span class="token plain"> $</span><span class="token number" style="color:#f3b0cb">4</span><br></div></code></pre></div></div>
<p>ant keeps a snapshot of every entity it loads and diffs against it, so <code>0</code>, <code>false</code>, <code>""</code> and <code>nil</code> are changes like any other value. Only changed columns are written. That also narrows the lost-update window you get with GORM's <code>Save</code>, which writes every column: two requests that edit different fields of the same row no longer overwrite each other. If both change the same field, the last write still wins, unless the model has an <code>ant:"version"</code> field, in which case the second save fails with <code>ErrConcurrency</code>.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-it-costs">What it costs<a href="https://skudra.net/blog/field-pointers-as-column-names#what-it-costs" class="hash-link" aria-label="Direct link to What it costs" title="Direct link to What it costs" translate="no">​</a></h2>
<p>The snapshot is not free. From <a href="https://github.com/skudra-go/ant/blob/main/tools/compare/results.txt" target="_blank" rel="noopener noreferrer" class=""><code>tools/compare</code></a>, time per operation in microseconds, median of 10 runs:</p>
<table><thead><tr><th></th><th style="text-align:right">Plain SQL</th><th style="text-align:right">ant</th><th style="text-align:right">ant NoTracking</th><th style="text-align:right">GORM</th><th style="text-align:right">Bun</th><th style="text-align:right">ent</th></tr></thead><tbody><tr><td>Read 1000 rows</td><td style="text-align:right">1,557</td><td style="text-align:right">2,799</td><td style="text-align:right">1,688</td><td style="text-align:right">3,285</td><td style="text-align:right">2,259</td><td style="text-align:right">2,226</td></tr><tr><td>Find one by key</td><td style="text-align:right">365</td><td style="text-align:right">406</td><td style="text-align:right">389</td><td style="text-align:right">416</td><td style="text-align:right">372</td><td style="text-align:right">383</td></tr><tr><td>Load, edit, save</td><td style="text-align:right">806</td><td style="text-align:right">830</td><td style="text-align:right">–</td><td style="text-align:right">861</td><td style="text-align:right">779</td><td style="text-align:right">1,784</td></tr></tbody></table>
<p>Measured on a Ryzen 5 3600 with Go 1.27.3 and PostgreSQL 17.11 in Docker on WSL2, with <code>synchronous_commit=off</code>; the method is on the <a class="" href="https://skudra.net/reference/benchmarks">benchmarks page</a>.</p>
<p>Reading 1000 rows with tracking on takes 1.7 times as long as with <code>NoTracking</code>, and 3 times the memory (823 KiB against 277 KiB). With tracking off, ant is 8% over plain <code>database/sql</code> on that read. Single-row work is within a few percent of everything else, because the round trip dominates. And ant allocates a lot: 118 allocations to find one row, against 41 for Bun and 36 for plain SQL. Only ent allocates more on some of the small queries. I haven't worked on that yet.</p>
<p>The ent number on the last row isn't a fair picture of ent's speed. Its <code>UpdateOne</code> re-reads the row inside a transaction, so that benchmark sends four statements where the others send two.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="where-it-falls-short">Where it falls short<a href="https://skudra.net/blog/field-pointers-as-column-names#where-it-falls-short" class="hash-link" aria-label="Direct link to Where it falls short" title="Direct link to Where it falls short" translate="no">​</a></h2>
<p>This section is the longest on purpose.</p>
<p><strong>Some mistakes compile.</strong> Field pointers carry the field and its type, but a condition is not tied to a model. These all build and fail at runtime, at the terminal call (<code>All</code>, <code>First</code>, <code>Find</code> and friends), not at <code>Where</code>:</p>
<ul>
<li class="">A pointer that isn't from a probe, such as <code>&amp;u.Age</code> on a local <code>User</code>, gives <code>ErrNotProbe</code>.</li>
<li class="">A relation where a column belongs, like <code>Column(&amp;Users.Posts)</code>, gives <code>ErrNotColumn</code>.</li>
<li class="">A slice column where a relation belongs, like <code>Include(&amp;Users.Tags)</code> on a <code>[]string</code> field, gives <code>ErrNotRelation</code>.</li>
<li class="">A field of a model the query doesn't use, like <code>db.Users.Where(ant.Eq(&amp;Posts.Title, "x"))</code>, gives <code>ErrWrongModel</code>.</li>
<li class=""><code>Find</code> with the wrong key type, like <code>Find(ctx, "42")</code> for an <code>int64</code> key, gives <code>ErrKeyType</code>. <code>Find</code> is generic over the key, and the compiler can't know which key type a model has.</li>
</ul>
<p><a class="" href="https://skudra.net/tooling/antvet">antvet</a>, a <code>go vet</code> analyzer in ant's repository, catches the non-probe, wrong-model and wrong-key cases:</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#e6eaf2;--prism-background-color:var(--ant-stage)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#e6eaf2;background-color:var(--ant-stage)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#e6eaf2"><span class="token plain">condition on Post in a query of User</span><br></div><div class="token-line" style="color:#e6eaf2"><span class="token plain">ant.Gt needs a field of a probe from ant.Fields, not of an ordinary variable</span><br></div><div class="token-line" style="color:#e6eaf2"><span class="token plain">Find key is string, and User has a key of type int64</span><br></div></code></pre></div></div>
<p>It is a separate step that you have to add to CI. ent and sqlc have none of these runtime-only cases.</p>
<p><strong>The heap assumption.</strong> As above: the design depends on the runtime not moving heap objects. That holds today and is tested, but it is not a language guarantee.</p>
<p><strong>Go 1.27 or later, no exceptions.</strong> Generic methods are the reason the API looks the way it does. There is no build tag or fallback for older toolchains.</p>
<p><strong>Pre-1.0.</strong> The API may still change. <code>docs/api.txt</code> records every exported name, and a test fails on any change to it, so changes are at least deliberate.</p>
<p><strong>Migrations are PostgreSQL only.</strong> Queries and writes work on PostgreSQL, MySQL, MariaDB, SQLite and SQL Server. Migrations and <code>EnsureCreated</code> work on PostgreSQL alone. There are no down migrations, by design: to undo a change, you write a new forward migration.</p>
<p><strong>The tracker has a cost and a rule.</strong> Besides the memory above, a tracking context serves one operation at a time. You take an <code>ant.Session(db)</code> per request or per goroutine. Use one from two goroutines and the second gets <code>ErrConcurrentUse</code>. Batch jobs that read a lot and write little should use <code>NoTracking</code>.</p>
<p><strong>It's new.</strong> GORM and ent have years of production use behind them, with the bug reports and edge cases that come with that. ant does not.</p>
<p>When to use something else. If you want to write SQL yourself and have no runtime layer between you and <code>database/sql</code>, use <a href="https://docs.sqlc.dev/" target="_blank" rel="noopener noreferrer" class="">sqlc</a>. If you want a graph schema, full build-time checking with no runtime-only cases, and you're fine with codegen, use <a href="https://entgo.io/docs/code-gen" target="_blank" rel="noopener noreferrer" class="">ent</a>. If you already run GORM and want more checking, look at its generics API and CLI first.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="try-it">Try it<a href="https://skudra.net/blog/field-pointers-as-column-names#try-it" class="hash-link" aria-label="Direct link to Try it" title="Direct link to Try it" translate="no">​</a></h2>
<p>The <a class="" href="https://skudra.net/quick-start">Quick start</a> runs a complete program on SQLite with no server:</p>
<div class="language-sh codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#e6eaf2;--prism-background-color:var(--ant-stage)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-sh codeBlock_bY9V thin-scrollbar" style="color:#e6eaf2;background-color:var(--ant-stage)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#e6eaf2"><span class="token function" style="color:#e7deff">mkdir</span><span class="token plain"> hello </span><span class="token operator" style="color:#cdd4e0">&amp;&amp;</span><span class="token plain"> </span><span class="token builtin class-name" style="color:#a5c4f7">cd</span><span class="token plain"> hello</span><br></div><div class="token-line" style="color:#e6eaf2"><span class="token plain">go mod init example.com/hello</span><br></div><div class="token-line" style="color:#e6eaf2"><span class="token plain">go get github.com/skudra-go/ant modernc.org/sqlite</span><br></div><div class="token-line" style="color:#e6eaf2"><span class="token plain"></span><span class="token comment" style="color:#8590a3;font-style:italic"># paste main.go from the Quick start</span><span class="token plain"></span><br></div><div class="token-line" style="color:#e6eaf2"><span class="token plain">go run </span><span class="token builtin class-name" style="color:#a5c4f7">.</span><br></div></code></pre></div></div>
<p><a class="" href="https://skudra.net/why-ant">Why ant</a> has the longer comparison with GORM, Bun, ent, sqlc, xorm and pop. The code is on <a href="https://github.com/skudra-go/ant" target="_blank" rel="noopener noreferrer" class="">GitHub</a>, and <a href="https://github.com/skudra-go/ant-examples" target="_blank" rel="noopener noreferrer" class="">ant-examples</a> has complete programs. The rest of the feature set is on the <a class="" href="https://skudra.net/">home page</a>.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-id-like-to-hear">What I'd like to hear<a href="https://skudra.net/blog/field-pointers-as-column-names#what-id-like-to-hear" class="hash-link" aria-label="Direct link to What I'd like to hear" title="Direct link to What I'd like to hear" translate="no">​</a></h2>
<ol>
<li class="">Does <code>&amp;Users.Age</code> read naturally in real code? The probe is a package-level variable named like the table field on your context (<code>db.Users</code>), which some people will like and some will find confusing.</li>
<li class="">Would you add antvet to CI, or does a check that lives outside the compiler defeat the purpose for you?</li>
<li class="">If you've tried to move a codebase off GORM or Bun, what stopped you?</li>
</ol>
<p><a href="https://github.com/skudra-go/ant/issues" target="_blank" rel="noopener noreferrer" class="">Open an issue</a> or start a thread in <a href="https://github.com/skudra-go/ant/discussions" target="_blank" rel="noopener noreferrer" class="">Discussions</a>.</p>
<p>Parts of ant were written with AI assistance. The feature tests in <a href="https://github.com/skudra-go/ant/tree/main/tests/features" target="_blank" rel="noopener noreferrer" class=""><code>tests/features</code></a>, the cross-library checks in <a href="https://github.com/skudra-go/ant/tree/main/tools/compare" target="_blank" rel="noopener noreferrer" class=""><code>tools/compare</code></a> and the decision log in <a href="https://github.com/skudra-go/ant/blob/main/docs/DECISIONS.md" target="_blank" rel="noopener noreferrer" class=""><code>docs/DECISIONS.md</code></a> are how I check it.</p>]]></content:encoded>
            <category>Go</category>
            <category>Generics</category>
            <category>ORM</category>
            <category>Design</category>
        </item>
    </channel>
</rss>