Skip to main content

Command Palette

Search for a command to run...

Golang 1.26 version Feature new(expr) Syntax

Go 1.26 introduces a significant enhancement to the built-in new() function. We will learn it with deep examples in this blog post

Published
8 min read
Golang 1.26 version Feature new(expr) Syntax

Prerequisites: Docker Go 1.26 Environment

Since Golang 1.26 isn’t directly available for public consumption, we will use golang:tip as a stand‑in for Go 1.26 and capture new(expr) demo output into a file, set up Docker and your project like this. Place this Dockerfile at the repo root as Dockerfile

# Use Go tip (development version) which contains upcoming Go 1.26 features
FROM golang:tip

WORKDIR /app

# Copy go.mod and go.sum first for better caching
COPY go.mod go.sum* ./

# Download dependencies (if any)
RUN go mod download 2>/dev/null || true

# Copy source code
COPY . .

# Ensure go.mod is initialized
RUN go mod tidy

# Default command
CMD ["go", "version"]

This pulls the nightly Go tip image, copies your module, installs deps, and ensures go.mod is consistent.

Create docker-compose.yml alongside the Dockerfile.

Note: Do not run this docker-compose file yet, as we need the main.go file, that we will create as we go down the blog post and learn about every line of it.

version: "3.8"

services:
  go126:
    build:
      context: .
      dockerfile: Dockerfile
    container_name: go126-newexpr
    working_dir: /app
    volumes:
      - .:/app
    command: ["sh", "-c", "go run ./main.go > /app/newexpr_output.txt"]
  • Builds from the Dockerfile using golang:tip.

  • Mounts your repo into /app so the output file is visible on the host.

  • Runs go run ./main.go (your new(expr) demo) and redirects stdout to newexpr_output.txt at the project root.​

Feature Overview of new(expr)

The new(expr) allocates memory for the expression's type, stores the evaluated result, and returns a pointer to it. Type inference ensures *T where T matches expr. Go 1.26 expects release in February 2026, with this change already in draft notes.

Pre-1.26 required temporary variables or helpers like func intPtr(v int) *int { return &v }, creating boilerplate. Now new(42) directly yields *int pointing to 42.

The main.go file

// Demo 01: new(expr) Syntax
//
// Go 1.26 enhances the built-in new() function to accept expressions,
// not just types. This enables creating pointers to values in a single expression.

package main

import (
    "encoding/json"
    "fmt"
    "time"
)

// Person represents a person with optional age field
type Person struct {
    Name string `json:"name"`
    Age  *int   `json:"age,omitempty"` // age if known; nil otherwise
}

// Config represents application configuration with optional fields
type Config struct {
    Host     string `json:"host"`
    Port     *int   `json:"port,omitempty"`
    Timeout  *int   `json:"timeout,omitempty"`
    MaxConns *int   `json:"max_connections,omitempty"`
    Debug    *bool  `json:"debug,omitempty"`
}

// yearsSince calculates approximate years since a given time
func yearsSince(t time.Time) int {
    return int(time.Since(t).Hours() / (365.25 * 24))
}

// intPtr returns a pointer to an int - OLD WAY
func intPtr(v int) *int {
    return &v
}

// boolPtr returns a pointer to a bool - OLD WAY
func boolPtr(v bool) *bool {
    return &v
}

func main() {
    fmt.Println("==============================================")
    fmt.Println("Go 1.26 Feature: new(expr) Syntax")
    fmt.Println("==============================================")
    fmt.Println()

    // ============================================
    // Example 1: The Old Way (Pre-Go 1.26)
    // ============================================
    fmt.Println("--- OLD WAY (Pre-Go 1.26) ---")
    fmt.Println()

    // Method 1: Temporary variable
    age := 30
    person1 := Person{
        Name: "Alice",
        Age:  &age, // Need temporary variable
    }
    fmt.Printf("Method 1 (temp var):    %+v\n", person1)

    // Method 2: Helper function
    person2 := Person{
        Name: "Bob",
        Age:  intPtr(25), // Need helper function
    }
    fmt.Printf("Method 2 (helper func): %+v\n", person2)

    // For config with multiple optional fields - verbose!
    port := 8080
    timeout := 30
    maxConns := 100
    debug := true
    oldConfig := Config{
        Host:     "localhost",
        Port:     &port,
        Timeout:  &timeout,
        MaxConns: &maxConns,
        Debug:    &debug,
    }
    fmt.Printf("Old Config: %+v\n", oldConfig)
    fmt.Println()

    // ============================================
    // Example 2: The New Way (Go 1.26)
    // ============================================
    fmt.Println("--- NEW WAY (Go 1.26) ---")
    fmt.Println()

    // NOTE: The new(expr) syntax is a Go 1.26 feature.
    // When running on Go 1.26+, you can use:
    //
    // person3 := Person{
    //     Name: "Charlie",
    //     Age:  new(30),  // Direct expression - clean!
    // }
    //
    // For calculated values:
    // birthDate := time.Date(1990, 1, 1, 0, 0, 0, 0, time.UTC)
    // person4 := Person{
    //     Name: "Diana",
    //     Age:  new(yearsSince(birthDate)),  // Expression!
    // }

    // Simulating Go 1.26 syntax effect with helper functions
    // In Go 1.26, this becomes: new(8080), new(30), new(100), new(true)
    newConfig := Config{
        Host:     "localhost",
        Port:     intPtr(8080),  // Go 1.26: new(8080)
        Timeout:  intPtr(30),    // Go 1.26: new(30)
        MaxConns: intPtr(100),   // Go 1.26: new(100)
        Debug:    boolPtr(true), // Go 1.26: new(true)
    }
    fmt.Printf("New Config: %+v\n", newConfig)
    fmt.Println()

    // ============================================
    // Example 3: JSON Serialization Use Case
    // ============================================
    fmt.Println("--- JSON SERIALIZATION USE CASE ---")
    fmt.Println()

    // Perfect for JSON with optional fields
    birthDate := time.Date(1990, 5, 15, 0, 0, 0, 0, time.UTC)
    calculatedAge := yearsSince(birthDate)

    person := Person{
        Name: "Eve",
        Age:  &calculatedAge, // Go 1.26: new(yearsSince(birthDate))
    }

    jsonData, err := json.MarshalIndent(person, "", "  ")
    if err != nil {
        fmt.Printf("Error: %v\n", err)
        return
    }
    fmt.Printf("Person JSON:\n%s\n", jsonData)
    fmt.Println()

    // Person without age (nil pointer omitted in JSON)
    personNoAge := Person{
        Name: "Frank",
        Age:  nil,
    }
    jsonDataNoAge, _ := json.MarshalIndent(personNoAge, "", "  ")
    fmt.Printf("Person without age:\n%s\n", jsonDataNoAge)
    fmt.Println()

    // ============================================
    // Example 4: Benefits Summary
    // ============================================
    fmt.Println("--- BENEFITS OF new(expr) ---")
    fmt.Println()
    fmt.Println("1. ✓ No temporary variables needed")
    fmt.Println("2. ✓ No helper functions required")
    fmt.Println("3. ✓ Cleaner struct initialization")
    fmt.Println("4. ✓ Works with any expression (including function calls)")
    fmt.Println("5. ✓ Especially useful for JSON/protobuf optional fields")
    fmt.Println()

    fmt.Println("==============================================")
    fmt.Println("Demo Complete!")
    fmt.Println("==============================================")
}

Before Go 1.26

// new() only accepted types
ptr := new(int)      // *int pointing to 0
ptr := new(string)   // *string pointing to ""
ptr := new(MyStruct) // *MyStruct with zero values

Go 1.26 and Later

// new() can now accept expressions!
ptr := new(42)              // *int pointing to 42
ptr := new("hello")         // *string pointing to "hello"
ptr := new(calculateAge())  // *int pointing to function result

Why This Matters

The Problem: Optional Pointer Fields

Many serialization libraries (JSON, Protocol Buffers, etc.) use pointers to represent optional values:

type Person struct {
    Name string  `json:"name"`
    Age  *int    `json:"age,omitempty"` // Optional - nil means "not set"
}

Before Go 1.26, populating such fields was awkward:

// Method 1: Temporary variable (verbose)
age := 30
person := Person{Name: "Alice", Age: &age}

// Method 2: Helper function (boilerplate)
func intPtr(v int) *int { return &v }
person := Person{Name: "Alice", Age: intPtr(30)}

The Solution: new(expr)

// Go 1.26: Clean and direct
person := Person{Name: "Alice", Age: new(30)}

// Works with any expression, including function calls!
person := Person{Name: "Alice", Age: new(calculateAge(birthDate))}

Code Walkthrough

Struct Definitions

type Person struct {
    Name string `json:"name"`
    Age  *int   `json:"age,omitempty"` // age if known; nil otherwise
}

type Config struct {
    Host     string  `json:"host"`
    Port     *int    `json:"port,omitempty"`
    Timeout  *int    `json:"timeout,omitempty"`
    MaxConns *int    `json:"max_connections,omitempty"`
    Debug    *bool   `json:"debug,omitempty"`
}

These structs use pointer fields for optional values. In JSON:

  • A nil pointer is omitted from output (due to omitempty)

  • A non-nil pointer includes the value

Old Way: Helper Functions

// intPtr returns a pointer to an int - OLD WAY
func intPtr(v int) *int {
    return &v
}

// boolPtr returns a pointer to a bool - OLD WAY
func boolPtr(v bool) *bool {
    return &v
}

These helper functions were necessary before Go 1.26. You'd need one for each type!

Old Way: Temporary Variables

age := 30
person1 := Person{
    Name: "Alice",
    Age:  &age, // Need temporary variable
}

The temporary variable age pollutes the scope and adds visual noise.

Old Way: Config with Multiple Optional Fields

port := 8080
timeout := 30
maxConns := 100
debug := true
oldConfig := Config{
    Host:     "localhost",
    Port:     &port,
    Timeout:  &timeout,
    MaxConns: &maxConns,
    Debug:    &debug,
}

This is extremely verbose! Four temporary variables for four optional fields.

New Way: Go 1.26 with new(expr)

// In Go 1.26, this becomes beautifully simple:
newConfig := Config{
    Host:     "localhost",
    Port:     new(8080),     // Direct value!
    Timeout:  new(30),       // No temp vars!
    MaxConns: new(100),      // No helpers!
    Debug:    new(true),     // Clean!
}

Calculated Values

The real power shows when using expressions:

func yearsSince(t time.Time) int {
    return int(time.Since(t).Hours() / (365.25 * 24))
}

birthDate := time.Date(1990, 5, 15, 0, 0, 0, 0, time.UTC)

// Go 1.26: Pass function result directly to new()
person := Person{
    Name: "Eve",
    Age:  new(yearsSince(birthDate)), // Expression!
}

JSON Serialization Demo

person := Person{
    Name: "Eve",
    Age:  new(35),
}

jsonData, _ := json.MarshalIndent(person, "", "  ")
// Output:
// {
//   "name": "Eve",
//   "age": 35
// }

personNoAge := Person{
    Name: "Frank",
    Age:  nil, // Omitted from JSON due to omitempty
}
// Output:
// {
//   "name": "Frank"
// }

Technical Details

How It Works

When the compiler sees new(expr):

  1. It evaluates the expression expr

  2. Allocates memory for a value of that expression's type

  3. Stores the result in the allocated memory

  4. Returns a pointer to that memory

Memory Allocation

new(42)     // Allocates int on heap, stores 42, returns *int
new("hi")   // Allocates string on heap, stores "hi", returns *string
new(fn())   // Calls fn(), allocates result type, stores result, returns pointer

Type Inference

The type is inferred from the expression:

var a = new(42)          // a is *int
var b = new(3.14)        // b is *float64
var c = new("hello")     // c is *string
var d = new(time.Now())  // d is *time.Time

Best Practices

✅ Do Use new(expr) For

  1. Optional struct fields in JSON/Protobuf

     Config{Timeout: new(30)}
    
  2. Passing computed values as pointers

     Response{CreatedAt: new(time.Now())}
    
  3. Inline pointer creation

     callAPI(new("default-value"))
    

❌ Avoid When

  1. You need the address of an existing variable

     x := 42
     ptr := &x  // Use & not new() here
    
  2. Creating zero-valued pointers (use traditional new(Type))

     ptr := new(int)  // Still valid: *int pointing to 0
    

Running This Demo

# From repository root
docker compose run --rm go126 go run ./demos/01_new_expr/...

Let me know what you think about this feature