The Applied Go Weekly Newsletter logo

The Applied Go Weekly Newsletter

Subscribe
Archives
March 30, 2025

Contemplating on templates • The Applied Go Weekly Newsletter 2025-03-30

AppliedGoNewsletterHeader640.png

Your weekly source of Go news, tips, and projects

2025-03-30 Newsletter Badge.png

Contemplating on templates

Hi ,

April is coming, and where I live, this means unsteady weather while Spring slowly unfolds. A good time to learn something new.

If you are new to Go or know someone who is, I have an "unfair advantage" offer: My Go course "Master Go" will be available at a 40% discount from April 1st to April 15th!

So remember or share the Link until April 1st (no April fool's joke)!

Back to this week's topic: templates. I looked into a specific way of making Go templates more flexible and powerful. It's not a new way, but probably not as widely known as the usual templating mechanisms like pipelines, if, with, etc. More in the Spotlight section.

Have a great rest of March! –Christoph

Featured articles

Goodbye core types - Hello Go as we know and love it! - The Go Programming Language

Naming things can be harder than you might think. The Go team removes the concept of "core types" from the language specification. The problem: the concept of core types was introduced for generics, yet it bleeds into the documentation of non-generic parts of Go. The removal shall result in clearer descriptions of those non-generic parts, with additional descriptions for the generic parts.

It's difficult to explain this in a few words; this post I sent recently explains the change in a bit more detail.

Go's multiple return values and (Go) types

Should Go have tuple types rather than multiple return values? Chris Siebenstein rebuts this claim by pointing out the problems that tuple types would introduce.

Follow-up article: Go's choice of multiple return values was the simpler option

Tool focus: Welcome to golangci-lint v2 | The Best Code is No Code At All

Golangci-lint levels up! Version 2 shines with simpler configuration and comes with a config migration tool for painless upgrading.

Podcast corner

Cup o' Go: Go makes everything faster. Even ducks! 🦆

Jonathan and Shay on recent "typo-squatting" attacks on Go repositories and the Quacfka library for managing data ingestion more effectively.

Fallthrough: Translating The Law for Software Engineers

The language of laws can be as alien to a software developer as a programming language can be alien to a lawyer. The Fallthrough panel discusses regulations and open source, the EU's Cyber Resilience Act, and why software engineers need to care.

Spotlight: Functions in templates—embrace or avoid?

In this spotlight, I want to take a look at a particular feature of Go templates: custom functions, or the FuncMap feature.

How templates work in Go

If you're new to Go, Go's rich standard library provides two templating packages, text/template and html/template. (The latter provides the same interfaces as the former but generates output that is safe against code injection.)

The basic idea of templating is to set up text with placeholders and let the app replace these placeholders with real data.

Let's look at an example. The code below defines a Product struct type with two fields, and a variable of that type:

type Product struct {
    Name      string
    Price     float64
}

(...)

coffee := Product{
        Name:      "Espresso",
        Price:     3.5,
    }

Next, it creates a template of name "item", with placeholders for the struct fields Name and Price (note the "dot" notation; the dot represents the context of the template:

tmpl := template.Must(template.New("item").Parse(
        `: `,
    ))

Finally, the code executes the template, passing the struct variable coffee as the context. Execute() writes the rendered template to an io.Writer, in this case, os.Stdout:

tmpl.Execute(os.Stdout, coffee)
// Output: Espresso: 3.5 

(Playground)

Adding calculated output

Now let's assume you want to add a discount to the product and also format the price properly as "$n.nn":

type Product struct {
    Name      string
    Price     float64
    Discount  float64 // e.g., 0.2 for 20% off
}

You could calculate the discounted price, format the price as strings, and fill a new struct with the result, which you then pass as template context to Execute():

    coffee := Product{
        Name:     "Espresso",
        Price:    3.5,
        Discount: 0.2,
    }

    // anonymous struct definition for brevity
    formattedCoffee := struct {
        Name          string
        OriginalPrice string
        FinalPrice    string
    }{
        Name:          coffee.Name,
        OriginalPrice: fmt.Sprintf("$%.2f", coffee.Price),
        FinalPrice:    fmt.Sprintf("$%.2f", coffee.Price*(1-coffee.Discount)),
    }

    tmpl := template.Must(template.New("item").Parse(
        `:  (was )`,
    ))
    tmpl.Execute(os.Stdout, formattedCoffee)
    // Output: Espresso: $2.80 (was $3.50)

(Playground)

That's quite a mouthful of additional code for some formatting and a multiplication, isn't it? Imagine you need this in various places of the many templates you have planned to create. Is there a better way?

FuncMap to the rescue

The good news: Yes, there is a better way. You can define functions that you can call from within a template.

Here is how it works:

Instead of the extra struct and the calculations made there, you can define a FuncMap for the template that contains the functions for discount calculation and price formatting:

    funcs := template.FuncMap{
        "formatPrice": func(price float64) string {
            return fmt.Sprintf("$%.2f", price)
        },
        "applyDiscount": func(price, discount float64) float64 {
            return price * (1 - discount)
        },
        "taxLabel": func(isTaxable bool) string {
            return map[bool]string{true: "(incl. tax)", false: ""}[isTaxable]
        },
    }

The functions in this map can take arbitrary parameters. At runtime, they must match the actual number of arguments provided. The functions can return one value, or a return value and an error value.

The Template method Funcs() injects the functions into the template. Inside the template text, you can call map functions either as func param (...), like applyDiscount .Price .Discount, or through a pipe, like .Price | formatPrice:

    tmpl := template.Must(template.New("item").Funcs(funcs).Parse(
        `: applyDiscount .Price .Discount | formatPrice (was .Price | formatPrice)`,
    ))

Applying the template prints out the discounted and original price, formatted as a $ price should be:

    tmpl.Execute(os.Stdout, coffee)
    // Output: Espresso: $2.80 (was $3.50)

(Playground)

Ok, what did we win?

What did we win, especially—ESPECIALLY!—as template functions have no compile-time type safety? They get evaluated when the template is rendered and may thus fail at runtime, and, what's more problematic, they even fail silently!

Try replacing .Price in one of the function calls with "one dollar". See what I mean?

Still, template functions have undeniable advantages:

  1. You can reuse logic across the template, and across multiple templates.
  2. You can simplify template logic. For example, a function hasPermission can replace a complicated expression.
  3. Users can write custom templates without recompiling the app.
  4. Separation of concerns: business logic stays in the Go code while templates can take over the formatting.

A great example for #3 is the static site generator Hugo. Hugo's templating system (based on Go templates) provides several functions that help build flexible templates, no Go coding required.

Surely, you'll still want to minimize the disadvantages from the lack of type safety. Template functions can help here, too. A log function can implement log.Printf for your templates:

"log": func(format string, args ...any) string {
    log.Printf(format, args...)
    return ""
},

Now you can log anything you want from right within your templates:

log "%s: Get it while it's hot!" .Name

Template functions definitely increase the fun with templates!

Quote of the Week: Better not vibe-coded

I, for one, do NOT want my Airbus A320 plane software vibe coded 😅

–Pieter Levels

More articles, videos, talks

1. Starting Systems Programming: Part 2: Your program and the outside world: syscalls & files

Efron Licht's system programming series continues.

Treating integration tests as just tests (using Testcontainers) - YouTube

Escape mock limitations: Alex Pilutatu demonstrates practical integration testing in Go using testcontainers.

Proposal: official support for modelcontextprotocol/go-sdk · modelcontextprotocol · Discussion #224 · GitHub

A standard, "official" MCP SDK for Go? No. As Axel "The Merovious" Wagner points out, this proposal is not about "an official Go project for an MCP SDK", but rather "an official MCP project for a Go SDK".

A subtle but important difference.

Practical Refactoring in Go (+ Mindset) - YouTube

What and how to refactor depends to a good extent on the language used. Adib Hanna explains not only the what and how, but also refactoring techniques and pitfalls to avoid.

Projects

Libraries

GitHub - go-analyze/charts: Golang charting and graphing library

Render line, scatter, bar, horizontal bar, pie, radar, funnel and table charts to png, jpg, or svg images.

GitHub - olekukonko/ruta: Chi-inspired router for TCP and WebSocket support

All routing is for HTTP only. All routing? No, a small routing package set out to bring routing to TCP, WebSocket, and any networking protocol that you want to implement an interface for.

GitHub - tomakado/dumbql: Simple (dumb?) query language

Query your structs with this simple, Kibana-inspired query language.

GitHub - abiosoft/mold: Higher level use of Go templates for rendering web pages

If Go's standard templating system feels unsuited for modeling typical Web page structures, try abiosoft/mold.

GitHub - avamsi/climate: "CLI Mate" autogenerates CLIs from structs / functions (nested subcommands, global / local flags, help generation, typo suggestions, shell completion etc.)

It never occurred to me that "climate" actually means "CLI mate"!

climate is built on top of Cobra.

GitHub - MegaGrindStone/go-light-rag: A Go library implementation of LightRAG - an advanced Retrieval-Augmented Generation (RAG) system that uniquely combines vector databases with graph database relationships to enhance knowledge retrieval.

LightRAG improves upon "classic" RAG in utilizing a hybrid vector+graph retrieval (rather than just vector-based retrieval), thus increasing the usefulness of the retrieved information. go-light-rag is a Go implementation of LightRAG.

Tools and applications

GitHub - UoCCS/project-GROS: Making Rust better with Go

Now that's what Rust needs most: A garbage collector! Written in Go, of course, the language that successfully collects garbage since 2009 (or 2011, if you count from Go 1.0).

(Hint: If you are unsure about the nature of this repo, go to internal/net/null, click the link to the RFC in the readme, and inspect the date.)

GitHub - ashkansamadiyan/togo: a terminal-based Todo Manager

When a brilliant idea crosses your mind but you're too busy to focus on that idea right now, stow it away for later with a few keystrokes in your terminal.

Completely unrelated to Go

Trying out NixOS

The dream of any operator or devop is a fully replicable OS setup. NixOS tries to make this a reality. Karan Sharma tested NixOS in his home lab.

Big endian and little endian

Sometimes, byte order matters. For example, when encoding and decoding data on different operating systems and hardware architectures. For historical reasons, there are two "schools" of storing multibyte values: Either the least significant byte comes first, or the most significant byte.

Nicole Tietz-Sokolskaya digs into the detials of endianness to end (or, at least, explain) the confusion.

TIL: The name "endianness" comes from cracking eggs.

Happy coding! ʕ◔ϖ◔ʔ

Questions or feedback? Drop me a line. I'd love to hear from you.

Best from Munich, Christoph

Not a subscriber yet?

If you read this newsletter issue online, or if someone forwarded the newsletter to you, subscribe for regular updates to get every new issue earlier than the online version, and more reliable than an occasional forwarding. 

Find the subscription form at the end of this page.

How I can help

If you're looking for more useful content around Go, here are some ways I can help you become a better Gopher (or a Gopher at all):

On AppliedGo.net, I blog about Go projects, algorithms and data structures in Go, and other fun stuff.

Or visit the AppliedGo.com blog and learn about language specifics, Go updates, and programming-related stuff. 

My AppliedGo YouTube channel hosts quick tip and crash course videos that help you get more productive and creative with Go.

Enroll in my Go course for developers that stands out for its intense use of animated graphics for explaining abstract concepts in an intuitive way. Numerous short and concise lectures allow you to schedule your learning flow as you like.

Check it out.


Christoph Berger IT Products and Services
Dachauer Straße 29
Bergkirchen
Germany

Don't miss what's next. Subscribe to The Applied Go Weekly Newsletter:
LinkedIn
Powered by Buttondown, the easiest way to start and grow your newsletter.