<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>Notes - Ashwin Gopalsamy</title>
    <link>https://ashwingopalsamy.in/writing/notes/</link>
    <description>Short-form thoughts on engineering, tools, and things I&#39;m learning.</description>
    <language>en-us</language>
    
    <lastBuildDate>Sat, 28 Mar 2026 00:00:00 &#43;0000</lastBuildDate>
    
    
    <atom:link href="https://ashwingopalsamy.in/writing/notes/feed.xml" rel="self" type="application/rss+xml" />
    
    
    <item>
      <title>Go Error Wrapping Patterns</title>
      <link>https://ashwingopalsamy.in/writing/notes/go-error-wrapping-patterns/</link>
      <pubDate>Sat, 28 Mar 2026 00:00:00 &#43;0000</pubDate>
      <guid isPermaLink="true">https://ashwingopalsamy.in/writing/notes/go-error-wrapping-patterns/</guid>
      <description>&lt;p&gt;The &lt;code&gt;fmt.Errorf(&amp;quot;context: %w&amp;quot;, err)&lt;/code&gt; pattern is the standard way to add context to errors in Go. But there are nuances worth knowing.&lt;/p&gt;
&lt;p&gt;Always wrap with context that answers &amp;ldquo;what were you trying to do?&amp;rdquo; not &amp;ldquo;what went wrong?&amp;rdquo; The original error already says what went wrong.&lt;/p&gt;
&lt;p&gt;Bad: &lt;code&gt;fmt.Errorf(&amp;quot;error: %w&amp;quot;, err)&lt;/code&gt;
Good: &lt;code&gt;fmt.Errorf(&amp;quot;parsing field DE%d: %w&amp;quot;, fieldNum, err)&lt;/code&gt;&lt;/p&gt;</description>
      <content:encoded>&lt;p&gt;The &lt;code&gt;fmt.Errorf(&amp;quot;context: %w&amp;quot;, err)&lt;/code&gt; pattern is the standard way to add context to errors in Go. But there are nuances worth knowing.&lt;/p&gt;
&lt;p&gt;Always wrap with context that answers &amp;ldquo;what were you trying to do?&amp;rdquo; not &amp;ldquo;what went wrong?&amp;rdquo; The original error already says what went wrong.&lt;/p&gt;
&lt;p&gt;Bad: &lt;code&gt;fmt.Errorf(&amp;quot;error: %w&amp;quot;, err)&lt;/code&gt;
Good: &lt;code&gt;fmt.Errorf(&amp;quot;parsing field DE%d: %w&amp;quot;, fieldNum, err)&lt;/code&gt;&lt;/p&gt;
</content:encoded>
      <author>hi@ashwingopalsamy.in (Ashwin Gopalsamy)</author>
    </item>
    
    <item>
      <title>Why slog Over zerolog</title>
      <link>https://ashwingopalsamy.in/writing/notes/why-slog-over-zerolog/</link>
      <pubDate>Tue, 10 Mar 2026 00:00:00 &#43;0000</pubDate>
      <guid isPermaLink="true">https://ashwingopalsamy.in/writing/notes/why-slog-over-zerolog/</guid>
      <description>&lt;p&gt;Go 1.21 introduced &lt;code&gt;log/slog&lt;/code&gt; in the standard library. For new projects, I now default to slog over zerolog.&lt;/p&gt;
&lt;p&gt;The API is cleaner, it is part of the standard library (no dependency), and the handler interface makes it trivial to swap backends. The performance gap that once justified zerolog has narrowed significantly.&lt;/p&gt;</description>
      <content:encoded>&lt;p&gt;Go 1.21 introduced &lt;code&gt;log/slog&lt;/code&gt; in the standard library. For new projects, I now default to slog over zerolog.&lt;/p&gt;
&lt;p&gt;The API is cleaner, it is part of the standard library (no dependency), and the handler interface makes it trivial to swap backends. The performance gap that once justified zerolog has narrowed significantly.&lt;/p&gt;
</content:encoded>
      <author>hi@ashwingopalsamy.in (Ashwin Gopalsamy)</author>
    </item>
    
    <item>
      <title>Go Maps Iteration Order</title>
      <link>https://ashwingopalsamy.in/writing/notes/go-maps-iteration-order/</link>
      <pubDate>Thu, 25 Dec 2025 00:00:00 &#43;0000</pubDate>
      <guid isPermaLink="true">https://ashwingopalsamy.in/writing/notes/go-maps-iteration-order/</guid>
      <description>&lt;p&gt;I was working on a minimal word counter. Read from stdin, normalize the input, increment the value for the keys in a &lt;code&gt;map[string]int&lt;/code&gt; each time a value is observed.&lt;/p&gt;
&lt;p&gt;Irrespective of the order of the input data for a very small unit test-case I created, the output looked sorted. Alphabetically sorted. Clean. Predictable.&lt;/p&gt;
&lt;p&gt;I ran it again. Same order. Added more input. Digits. Letters. Mixed tokens.&lt;/p&gt;
&lt;p&gt;At that point, my instincts kicked in. &lt;strong&gt;Go maps are unordered.&lt;/strong&gt; I knew that. So why was the output behaving so politely?&lt;/p&gt;</description>
      <content:encoded>&lt;p&gt;I was working on a minimal word counter. Read from stdin, normalize the input, increment the value for the keys in a &lt;code&gt;map[string]int&lt;/code&gt; each time a value is observed.&lt;/p&gt;
&lt;p&gt;Irrespective of the order of the input data for a very small unit test-case I created, the output looked sorted. Alphabetically sorted. Clean. Predictable.&lt;/p&gt;
&lt;p&gt;I ran it again. Same order. Added more input. Digits. Letters. Mixed tokens.&lt;/p&gt;
&lt;p&gt;At that point, my instincts kicked in. &lt;strong&gt;Go maps are unordered.&lt;/strong&gt; I knew that. So why was the output behaving so politely?&lt;/p&gt;
&lt;h2 id=&#34;the-non-negotiable-fact&#34;&gt;The non-negotiable fact&lt;/h2&gt;
&lt;p&gt;Go maps do &lt;strong&gt;not&lt;/strong&gt; guarantee iteration order. Ever.&lt;/p&gt;
&lt;p&gt;The language spec is explicit. Iteration order is not specified and must not be relied upon.&lt;/p&gt;
&lt;p&gt;So if the output looks sorted, that is never by intention. It might be an accident. The interesting part is understanding &lt;em&gt;why this accident looks so consistent&lt;/em&gt;.&lt;/p&gt;
&lt;h2 id=&#34;what-is-actually-happening-inside-a-go-map&#34;&gt;What is actually happening inside a Go map&lt;/h2&gt;
&lt;p&gt;A Go map is a &lt;strong&gt;hash table&lt;/strong&gt;. Iteration walks buckets in a runtime-defined sequence, not key order.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Important detail:&lt;/strong&gt; iteration does &lt;strong&gt;not&lt;/strong&gt; mean &amp;ldquo;pick a random key each time&amp;rdquo; but rather &amp;ldquo;walk internal memory structures in a sequence&amp;rdquo;. This distinction explains everything I had observed.&lt;/p&gt;
&lt;div class=&#34;diagram-container&#34; data-diagram-type=&#34;mermaid&#34;&gt;
  &lt;div class=&#34;mermaid&#34;&gt;
    
graph LR
    subgraph &#34;Hash Function&#34;
        H[&#34;hash(key)&#34;]
    end
    K1[&#34;a&#34;] --&gt; H
    K2[&#34;b&#34;] --&gt; H
    K3[&#34;m&#34;] --&gt; H
    K4[&#34;1&#34;] --&gt; H
    H --&gt; B0[&#34;Bucket 0&lt;br/&gt;1&#34;]
    H --&gt; B1[&#34;Bucket 1&lt;br/&gt;2&#34;]
    H --&gt; B3[&#34;Bucket 3&lt;br/&gt;a&#34;]
    H --&gt; B4[&#34;Bucket 4&lt;br/&gt;b&#34;]
    H --&gt; B6[&#34;Bucket 6&lt;br/&gt;m&#34;]
    style H fill:#6366f1,color:#fff,stroke:none
    style B0 fill:#374151,color:#fff,stroke:none
    style B1 fill:#374151,color:#fff,stroke:none
    style B3 fill:#374151,color:#fff,stroke:none
    style B4 fill:#374151,color:#fff,stroke:none
    style B6 fill:#374151,color:#fff,stroke:none

  &lt;/div&gt;
  &lt;div class=&#34;diagram-actions&#34;&gt;
    &lt;button class=&#34;diagram-action&#34; data-action=&#34;expand&#34; aria-label=&#34;Expand diagram&#34;&gt;
      &lt;svg width=&#34;14&#34; height=&#34;14&#34; viewBox=&#34;0 0 24 24&#34; fill=&#34;none&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;2&#34; stroke-linecap=&#34;round&#34; stroke-linejoin=&#34;round&#34;&gt;
        &lt;polyline points=&#34;15 3 21 3 21 9&#34;&gt;&lt;/polyline&gt;
        &lt;polyline points=&#34;9 21 3 21 3 15&#34;&gt;&lt;/polyline&gt;
        &lt;line x1=&#34;21&#34; y1=&#34;3&#34; x2=&#34;14&#34; y2=&#34;10&#34;&gt;&lt;/line&gt;
        &lt;line x1=&#34;3&#34; y1=&#34;21&#34; x2=&#34;10&#34; y2=&#34;14&#34;&gt;&lt;/line&gt;
      &lt;/svg&gt;
    &lt;/button&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;h2 id=&#34;why-the-output-looked-sorted&#34;&gt;Why the output looked sorted&lt;/h2&gt;
&lt;p&gt;The experiment had a very specific shape:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Small number of keys&lt;/li&gt;
&lt;li&gt;Short ASCII strings&lt;/li&gt;
&lt;li&gt;No map resizing during insertion&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Under these conditions, hash values distribute nicely across buckets, and buckets are laid out in memory in a way that often correlates with lexical order.&lt;/p&gt;
&lt;p&gt;Not because Go sorts anything. Because the bucket layout ends up looking sorted &lt;em&gt;by coincidence&lt;/em&gt;.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Unspecified order can still be stable and repeatable.&lt;/p&gt;&lt;/blockquote&gt;
&lt;h2 id=&#34;a-simplified-mental-model&#34;&gt;A simplified mental model&lt;/h2&gt;
&lt;p&gt;This is &lt;strong&gt;not&lt;/strong&gt; how Go maps are implemented exactly, but it explains the behavior well enough:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;Input keys:  a   b   m   x   y   z   1   2   9

Hashing step:
a -&amp;gt; bucket 3    x -&amp;gt; bucket 9
b -&amp;gt; bucket 4    y -&amp;gt; bucket 10
m -&amp;gt; bucket 6    z -&amp;gt; bucket 11
1 -&amp;gt; bucket 0    2 -&amp;gt; bucket 1    9 -&amp;gt; bucket 2

Buckets in memory order:
[0] [1] [2] [3] [4] [5] [6] [7] [8] [9] [10] [11]
 1   2   9   a   b       m           x    y    z

Iteration walks buckets left to right:
1 2 9 a b m x y z
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Digits first. Then letters. Within each group, alphabetical-looking order. No sorting happened. Iteration just walked buckets in memory order.&lt;/p&gt;
&lt;p&gt;Change the input distribution, force collisions, trigger a map resize, or run on a different Go version, and this illusion will break instantly.&lt;/p&gt;
&lt;div class=&#34;diagram-container&#34; data-diagram-type=&#34;mermaid&#34;&gt;
  &lt;div class=&#34;mermaid&#34;&gt;
    
graph TD
    S[&#34;Small ASCII key set&#34;]
    L[&#34;Large / diverse key set&#34;]
    S --&gt; |&#34;hash values spread&lt;br/&gt;neatly across buckets&#34;| O1[&#34;Appears sorted&lt;br/&gt;(coincidence)&#34;]
    L --&gt; |&#34;collisions, resizing,&lt;br/&gt;overflow buckets&#34;| O2[&#34;Visibly unordered&lt;br/&gt;(reality)&#34;]
    style S fill:#f59e0b,color:#fff,stroke:none
    style L fill:#374151,color:#fff,stroke:none
    style O1 fill:#ef4444,color:#fff,stroke:none
    style O2 fill:#374151,color:#fff,stroke:none

  &lt;/div&gt;
  &lt;div class=&#34;diagram-actions&#34;&gt;
    &lt;button class=&#34;diagram-action&#34; data-action=&#34;expand&#34; aria-label=&#34;Expand diagram&#34;&gt;
      &lt;svg width=&#34;14&#34; height=&#34;14&#34; viewBox=&#34;0 0 24 24&#34; fill=&#34;none&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;2&#34; stroke-linecap=&#34;round&#34; stroke-linejoin=&#34;round&#34;&gt;
        &lt;polyline points=&#34;15 3 21 3 21 9&#34;&gt;&lt;/polyline&gt;
        &lt;polyline points=&#34;9 21 3 21 3 15&#34;&gt;&lt;/polyline&gt;
        &lt;line x1=&#34;21&#34; y1=&#34;3&#34; x2=&#34;14&#34; y2=&#34;10&#34;&gt;&lt;/line&gt;
        &lt;line x1=&#34;3&#34; y1=&#34;21&#34; x2=&#34;10&#34; y2=&#34;14&#34;&gt;&lt;/line&gt;
      &lt;/svg&gt;
    &lt;/button&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;h2 id=&#34;why-the-order-kept-changing-as-you-inserted-new-keys&#34;&gt;Why the order kept changing as you inserted new keys&lt;/h2&gt;
&lt;p&gt;When you print the map after each insertion, you see the output &amp;ldquo;reorder itself.&amp;rdquo; That was not reordering. What happened was:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A new key landed in an earlier bucket&lt;/li&gt;
&lt;li&gt;Iteration still walked buckets from the start&lt;/li&gt;
&lt;li&gt;The newly populated bucket now appeared earlier in output&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This is why adding &lt;code&gt;&amp;quot;0&amp;quot;&lt;/code&gt; suddenly made it appear before &lt;code&gt;&amp;quot;1&amp;quot;&lt;/code&gt; through &lt;code&gt;&amp;quot;z&amp;quot;&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id=&#34;we-cant-really-inspect-map-internals&#34;&gt;We can&amp;rsquo;t really inspect map internals&lt;/h2&gt;
&lt;p&gt;Go does not allow reliable inspection of map internals. The language specification does not define the memory layout of maps at all. Bucket structure, hash seeds, overflow handling and growth strategy live entirely inside the runtime and are treated as implementation details. They are free to change between Go versions, and they do.&lt;/p&gt;
&lt;p&gt;On top of that, map elements are allowed to move in memory when the map grows. This is why Go explicitly disallows taking the address of a map element. Any address you observe today could become invalid tomorrow, even within the same program execution.&lt;/p&gt;
&lt;p&gt;As Keith Randall explains in his deep dive into Go maps, this movement happens incrementally during normal map operations, which makes layout and addresses fundamentally unstable by design.&lt;/p&gt;
&lt;div class=&#34;video-container&#34;&gt;
  &lt;iframe
    src=&#34;https://www.youtube-nocookie.com/embed/Tl7mi9QmLns&#34;
    title=&#34;YouTube video&#34;
    frameborder=&#34;0&#34;
    allow=&#34;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture&#34;
    allowfullscreen
    loading=&#34;lazy&#34;&gt;
  &lt;/iframe&gt;
&lt;/div&gt;

&lt;p&gt;You can technically poke around using &lt;code&gt;unsafe&lt;/code&gt;, but that immediately ties your understanding to a specific Go version and runtime implementation. It is educational at best and misleading at worst.&lt;/p&gt;


&lt;div class=&#34;callout callout--insight&#34;&gt;
  &lt;div class=&#34;callout-icon&#34;&gt;★&lt;/div&gt;
  &lt;div class=&#34;callout-body&#34;&gt;
    Deterministic-looking behavior does not imply guarantees. This is a perfect, low-stakes example of that lesson.
  &lt;/div&gt;
&lt;/div&gt;

</content:encoded>
      <author>hi@ashwingopalsamy.in (Ashwin Gopalsamy)</author>
    </item>
    
    <item>
      <title>Runes, Bytes, and Graphemes in Go</title>
      <link>https://ashwingopalsamy.in/writing/notes/runes-bytes-and-graphemes-in-go/</link>
      <pubDate>Sat, 09 Aug 2025 00:00:00 &#43;0000</pubDate>
      <guid isPermaLink="true">https://ashwingopalsamy.in/writing/notes/runes-bytes-and-graphemes-in-go/</guid>
      <description>&lt;p&gt;I once ran into this problem of differentiating runes, bytes and graphemes while handling names in Tamil and emoji in a Go web app: a string that &lt;em&gt;looked&lt;/em&gt; short wasn&amp;rsquo;t, and reversing it produced gibberish. The culprit wasn&amp;rsquo;t Go being flawed, it was me making assumptions about what &amp;ldquo;a character&amp;rdquo; means.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s map the territory precisely.&lt;/p&gt;
&lt;div class=&#34;diagram-container&#34; data-diagram-type=&#34;mermaid&#34;&gt;
  &lt;div class=&#34;mermaid&#34;&gt;
    
graph TD
    G[&#34;Grapheme Cluster&lt;br/&gt;(what users see)&#34;]
    R1[&#34;Rune 1&lt;br/&gt;(code point)&#34;]
    R2[&#34;Rune 2&lt;br/&gt;(combining mark)&#34;]
    B1[&#34;Bytes&lt;br/&gt;(1-4 per rune)&#34;]
    B2[&#34;Bytes&lt;br/&gt;(1-4 per rune)&#34;]
    G --&gt; R1
    G --&gt; R2
    R1 --&gt; B1
    R2 --&gt; B2
    style G fill:#374151,color:#fff,stroke:none
    style R1 fill:#6366f1,color:#fff,stroke:none
    style R2 fill:#6366f1,color:#fff,stroke:none
    style B1 fill:#64748b,color:#fff,stroke:none
    style B2 fill:#64748b,color:#fff,stroke:none

  &lt;/div&gt;
  &lt;div class=&#34;diagram-actions&#34;&gt;
    &lt;button class=&#34;diagram-action&#34; data-action=&#34;expand&#34; aria-label=&#34;Expand diagram&#34;&gt;
      &lt;svg width=&#34;14&#34; height=&#34;14&#34; viewBox=&#34;0 0 24 24&#34; fill=&#34;none&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;2&#34; stroke-linecap=&#34;round&#34; stroke-linejoin=&#34;round&#34;&gt;
        &lt;polyline points=&#34;15 3 21 3 21 9&#34;&gt;&lt;/polyline&gt;
        &lt;polyline points=&#34;9 21 3 21 3 15&#34;&gt;&lt;/polyline&gt;
        &lt;line x1=&#34;21&#34; y1=&#34;3&#34; x2=&#34;14&#34; y2=&#34;10&#34;&gt;&lt;/line&gt;
        &lt;line x1=&#34;3&#34; y1=&#34;21&#34; x2=&#34;10&#34; y2=&#34;14&#34;&gt;&lt;/line&gt;
      &lt;/svg&gt;
    &lt;/button&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;h2 id=&#34;1-bytes-the-raw-material-go-calls-a-string&#34;&gt;1. Bytes: the raw material Go calls a string&lt;/h2&gt;
&lt;p&gt;Go represents strings as immutable UTF-8 byte sequences. What we &lt;em&gt;see&lt;/em&gt; isn&amp;rsquo;t what Go handles under the hood.&lt;/p&gt;</description>
      <content:encoded>&lt;p&gt;I once ran into this problem of differentiating runes, bytes and graphemes while handling names in Tamil and emoji in a Go web app: a string that &lt;em&gt;looked&lt;/em&gt; short wasn&amp;rsquo;t, and reversing it produced gibberish. The culprit wasn&amp;rsquo;t Go being flawed, it was me making assumptions about what &amp;ldquo;a character&amp;rdquo; means.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s map the territory precisely.&lt;/p&gt;
&lt;div class=&#34;diagram-container&#34; data-diagram-type=&#34;mermaid&#34;&gt;
  &lt;div class=&#34;mermaid&#34;&gt;
    
graph TD
    G[&#34;Grapheme Cluster&lt;br/&gt;(what users see)&#34;]
    R1[&#34;Rune 1&lt;br/&gt;(code point)&#34;]
    R2[&#34;Rune 2&lt;br/&gt;(combining mark)&#34;]
    B1[&#34;Bytes&lt;br/&gt;(1-4 per rune)&#34;]
    B2[&#34;Bytes&lt;br/&gt;(1-4 per rune)&#34;]
    G --&gt; R1
    G --&gt; R2
    R1 --&gt; B1
    R2 --&gt; B2
    style G fill:#374151,color:#fff,stroke:none
    style R1 fill:#6366f1,color:#fff,stroke:none
    style R2 fill:#6366f1,color:#fff,stroke:none
    style B1 fill:#64748b,color:#fff,stroke:none
    style B2 fill:#64748b,color:#fff,stroke:none

  &lt;/div&gt;
  &lt;div class=&#34;diagram-actions&#34;&gt;
    &lt;button class=&#34;diagram-action&#34; data-action=&#34;expand&#34; aria-label=&#34;Expand diagram&#34;&gt;
      &lt;svg width=&#34;14&#34; height=&#34;14&#34; viewBox=&#34;0 0 24 24&#34; fill=&#34;none&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;2&#34; stroke-linecap=&#34;round&#34; stroke-linejoin=&#34;round&#34;&gt;
        &lt;polyline points=&#34;15 3 21 3 21 9&#34;&gt;&lt;/polyline&gt;
        &lt;polyline points=&#34;9 21 3 21 3 15&#34;&gt;&lt;/polyline&gt;
        &lt;line x1=&#34;21&#34; y1=&#34;3&#34; x2=&#34;14&#34; y2=&#34;10&#34;&gt;&lt;/line&gt;
        &lt;line x1=&#34;3&#34; y1=&#34;21&#34; x2=&#34;10&#34; y2=&#34;14&#34;&gt;&lt;/line&gt;
      &lt;/svg&gt;
    &lt;/button&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;h2 id=&#34;1-bytes-the-raw-material-go-calls-a-string&#34;&gt;1. Bytes: the raw material Go calls a string&lt;/h2&gt;
&lt;p&gt;Go represents strings as immutable UTF-8 byte sequences. What we &lt;em&gt;see&lt;/em&gt; isn&amp;rsquo;t what Go handles under the hood.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nx&#34;&gt;s&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;:=&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;&amp;#34;வணக்கம்&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nx&#34;&gt;fmt&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;Println&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;len&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;s&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;))&lt;/span&gt; &lt;span class=&#34;c1&#34;&gt;// 21&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The length is 21 bytes, not visible symbols. Every Tamil character can span 3 bytes. Even simple-looking emojis stretch across multiple bytes.&lt;/p&gt;
&lt;h2 id=&#34;2-runes-unicode-code-points&#34;&gt;2. Runes: Unicode code points&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;string&lt;/code&gt; to &lt;code&gt;[]rune&lt;/code&gt; gives you code points, but still not what a human perceives.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nx&#34;&gt;rs&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;:=&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;[]&lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;rune&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;s&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nx&#34;&gt;fmt&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;Println&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;len&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;rs&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;))&lt;/span&gt; &lt;span class=&#34;c1&#34;&gt;// 7&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Here it&amp;rsquo;s 7 runes, but some Tamil graphemes (like &amp;ldquo;க்&amp;rdquo;) combine two runes: &lt;code&gt;க&lt;/code&gt; + &lt;code&gt;்&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id=&#34;3-grapheme-clusters-the-units-users-actually-see&#34;&gt;3. Grapheme clusters: the units users actually see&lt;/h2&gt;
&lt;p&gt;Go&amp;rsquo;s standard library stops at runes. To work with visible characters, you need a grapheme-aware library like &lt;code&gt;github.com/rivo/uniseg&lt;/code&gt;.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;for&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;gr&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;:=&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;uniseg&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;NewGraphemes&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;s&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;gr&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;Next&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;();&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nx&#34;&gt;fmt&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;Printf&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;%q\n&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;gr&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;Str&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;())&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;That outputs what a human reads: &amp;ldquo;வ&amp;rdquo;, &amp;ldquo;ண&amp;rdquo;, &amp;ldquo;க்&amp;rdquo;, &amp;ldquo;க&amp;rdquo;, &amp;ldquo;ம்&amp;rdquo;, and even a heart emoji as a single unit.&lt;/p&gt;
&lt;div class=&#34;diagram-container&#34; data-diagram-type=&#34;mermaid&#34;&gt;
  &lt;div class=&#34;mermaid&#34;&gt;
    
graph LR
    T[&#34;வணக்கம் (Tamil)&#34;]
    T --&gt; |&#34;len()&#34;| BY[&#34;21 bytes&#34;]
    T --&gt; |&#34;[]rune&#34;| RU[&#34;7 runes&#34;]
    T --&gt; |&#34;uniseg&#34;| GR[&#34;5 graphemes&#34;]
    style T fill:#374151,color:#fff,stroke:none
    style BY fill:#ef4444,color:#fff,stroke:none
    style RU fill:#f59e0b,color:#fff,stroke:none
    style GR fill:#374151,color:#fff,stroke:none

  &lt;/div&gt;
  &lt;div class=&#34;diagram-actions&#34;&gt;
    &lt;button class=&#34;diagram-action&#34; data-action=&#34;expand&#34; aria-label=&#34;Expand diagram&#34;&gt;
      &lt;svg width=&#34;14&#34; height=&#34;14&#34; viewBox=&#34;0 0 24 24&#34; fill=&#34;none&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;2&#34; stroke-linecap=&#34;round&#34; stroke-linejoin=&#34;round&#34;&gt;
        &lt;polyline points=&#34;15 3 21 3 21 9&#34;&gt;&lt;/polyline&gt;
        &lt;polyline points=&#34;9 21 3 21 3 15&#34;&gt;&lt;/polyline&gt;
        &lt;line x1=&#34;21&#34; y1=&#34;3&#34; x2=&#34;14&#34; y2=&#34;10&#34;&gt;&lt;/line&gt;
        &lt;line x1=&#34;3&#34; y1=&#34;21&#34; x2=&#34;10&#34; y2=&#34;14&#34;&gt;&lt;/line&gt;
      &lt;/svg&gt;
    &lt;/button&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;h2 id=&#34;why-this-matters&#34;&gt;Why this matters&lt;/h2&gt;
&lt;p&gt;If your app deals with names, chats, or any multilingual text, indexing by bytes will break things. Counting runes helps but can still split what you intend as one unit. Grapheme-aware operations align with what users actually expect.&lt;/p&gt;
&lt;p&gt;Real bugs I&amp;rsquo;ve seen: Tamil names chopped mid-character, emoji reactions breaking because only one code point was taken.&lt;/p&gt;
&lt;h2 id=&#34;quick-reference&#34;&gt;Quick reference&lt;/h2&gt;
&lt;table&gt;
  &lt;thead&gt;
      &lt;tr&gt;
          &lt;th&gt;Task&lt;/th&gt;
          &lt;th&gt;Approach&lt;/th&gt;
      &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
      &lt;tr&gt;
          &lt;td&gt;Count code points&lt;/td&gt;
          &lt;td&gt;&lt;code&gt;utf8.RuneCountInString(s)&lt;/code&gt;&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;Count visible units&lt;/td&gt;
          &lt;td&gt;Grapheme iteration (&lt;code&gt;uniseg&lt;/code&gt;)&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;Reverse text&lt;/td&gt;
          &lt;td&gt;Parse into graphemes, reverse slice, join&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;Slice safely&lt;/td&gt;
          &lt;td&gt;Only use &lt;code&gt;s[i:j]&lt;/code&gt; on grapheme boundaries&lt;/td&gt;
      &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Think about what you intend to manipulate: the raw bytes, the code points, or what a user actually reads on screen, and choose the right level.&lt;/p&gt;
</content:encoded>
      <author>hi@ashwingopalsamy.in (Ashwin Gopalsamy)</author>
    </item>
    
  </channel>
</rss>
