Menu
Coddy logo textTech

Generic Structs

Part of the Object Oriented Programming section of Coddy's GO journey — lesson 70 of 107.

Just like functions, structs can also have type parameters. A generic struct lets you create data structures that work with any type while maintaining type safety. This is perfect for building reusable containers like stacks, queues, or key-value pairs.

To define a generic struct, place the type parameter after the struct name:

type Box[T any] struct {
    Value T
}

func main() {
    intBox := Box[int]{Value: 42}
    strBox := Box[string]{Value: "hello"}
    
    fmt.Println(intBox.Value)  // 42
    fmt.Println(strBox.Value)  // hello
}

Unlike generic functions, you must explicitly specify the type when creating an instance of a generic struct. Go cannot infer the type from context alone.

Generic structs can have multiple type parameters, making them ideal for structures like pairs or maps:

type Pair[K, V any] struct {
    Key   K
    Value V
}

p := Pair[string, int]{Key: "age", Value: 25}
fmt.Printf("%s: %d\n", p.Key, p.Value)  // age: 25

You can also apply constraints to ensure the stored types support specific operations:

type NumberBox[T int | float64] struct {
    Value T
}

nb := NumberBox[float64]{Value: 3.14}

Generic structs form the foundation for building type-safe, reusable data structures in Go. In the next lesson, you'll learn how to define methods on these generic types.

challenge icon

Challenge

Easy

Let's build a generic inventory system that can track different types of items! You'll create reusable container structures that work with any type while maintaining full type safety.

You'll organize your code across two files:

  • inventory.go: Define your generic container structures.

    Create a generic struct Item[T any] with two fields: Name (string) and Data (of type T). This represents any item with associated data of a flexible type.

    Create a generic struct Container[T any] with a single field Items that holds a slice of Item[T]. This container can store multiple items of the same data type.

    Create a constrained generic struct PricedItem[T int | float64] with three fields: Name (string), Quantity (int), and Price (of type T). The constraint ensures prices are always numeric.

    Implement a function NewContainer[T any]() *Container[T] that creates and returns a pointer to an empty Container.

    Implement a method Add on *Container[T] that takes a name (string) and data (T), creates an Item, and appends it to the container's Items slice.

    Implement a method Count on Container[T] that returns the number of items in the container.

  • main.go: Demonstrate your generic structures with different types.

    Read an item type (string, int, or priced), then read a count followed by item details.

    For string type: Read pairs of name and string data. Create a Container[string], add all items, then print each item as [Name]: [Data] followed by the total count.

    For int type: Read pairs of name and integer data. Create a Container[int], add all items, then print each item as [Name]: [Data] followed by the total count.

    For priced type: Read triplets of name, quantity, and price (as float). Create PricedItem[float64] instances directly and print each as [Name] x[Quantity] @ [Price] with price showing one decimal place.

    Print the count line as: Total items: [count]

The following inputs will be provided:

  • Line 1: Item type (string, int, or priced)
  • Line 2: Count of items (integer)
  • Following lines: Item details based on type

For example, given:

string
3
Book
Fiction Novel
Pen
Blue Ink
Notebook
Lined Paper

Your output should be:

Book: Fiction Novel
Pen: Blue Ink
Notebook: Lined Paper
Total items: 3

And given:

int
2
Apples
50
Oranges
30

Your output should be:

Apples: 50
Oranges: 30
Total items: 2

And given:

priced
2
Widget
10
19.99
Gadget
5
49.50

Your output should be:

Widget x10 @ 19.9
Gadget x5 @ 49.5
Total items: 2

Notice how the same Container structure works seamlessly with both strings and integers, while PricedItem uses a constraint to ensure only numeric types can be used for prices. You must explicitly specify the type parameter when creating instances of these generic structs.

Cheat sheet

A generic struct allows you to create data structures that work with any type while maintaining type safety.

To define a generic struct, place the type parameter after the struct name:

type Box[T any] struct {
    Value T
}

When creating an instance of a generic struct, you must explicitly specify the type (Go cannot infer it):

intBox := Box[int]{Value: 42}
strBox := Box[string]{Value: "hello"}

Generic structs can have multiple type parameters:

type Pair[K, V any] struct {
    Key   K
    Value V
}

p := Pair[string, int]{Key: "age", Value: 25}

You can apply constraints to ensure stored types support specific operations:

type NumberBox[T int | float64] struct {
    Value T
}

nb := NumberBox[float64]{Value: 3.14}

Generic structs can contain fields that use the type parameter, including slices:

type Container[T any] struct {
    Items []T
}

You can define methods on generic structs and create generic constructor functions:

func NewContainer[T any]() *Container[T] {
    return &Container[T]{Items: []T{}}
}

func (c *Container[T]) Add(item T) {
    c.Items = append(c.Items, item)
}

Try it yourself

package main

import (
	"bufio"
	"fmt"
	"os"
	"strconv"
)

func main() {
	scanner := bufio.NewScanner(os.Stdin)
	
	// Read item type
	scanner.Scan()
	itemType := scanner.Text()
	
	// Read count
	scanner.Scan()
	count, _ := strconv.Atoi(scanner.Text())
	
	switch itemType {
	case "string":
		// TODO: Create a Container[string] using NewContainer
		// TODO: Read 'count' items (each has name on one line, data on next line)
		// TODO: Add each item to the container
		// TODO: Print each item as "[Name]: [Data]"
		// TODO: Print "Total items: [count]" using the Count method
		
	case "int":
		// TODO: Create a Container[int] using NewContainer
		// TODO: Read 'count' items (each has name on one line, integer data on next line)
		// TODO: Add each item to the container
		// TODO: Print each item as "[Name]: [Data]"
		// TODO: Print "Total items: [count]" using the Count method
		
	case "priced":
		// TODO: Create a slice to hold PricedItem[float64] instances
		// TODO: Read 'count' items (each has name, quantity, and price on separate lines)
		// TODO: Create PricedItem[float64] for each and add to slice
		// TODO: Print each item as "[Name] x[Quantity] @ [Price]" with price showing one decimal
		// TODO: Print "Total items: [count]"
	}
	
	_ = scanner // Use scanner to read input
	_ = count   // Use count for looping
}
quiz iconTest yourself

This lesson includes a short quiz. Start the lesson to answer it and track your progress.

All lessons in Object Oriented Programming