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: 25You 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
EasyLet'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) andData(of type T). This represents any item with associated data of a flexible type.Create a generic struct
Container[T any]with a single fieldItemsthat holds a slice ofItem[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), andPrice(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
Addon*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
CountonContainer[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, orpriced), then read a count followed by item details.For
stringtype: Read pairs of name and string data. Create aContainer[string], add all items, then print each item as[Name]: [Data]followed by the total count.For
inttype: Read pairs of name and integer data. Create aContainer[int], add all items, then print each item as[Name]: [Data]followed by the total count.For
pricedtype: Read triplets of name, quantity, and price (as float). CreatePricedItem[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, orpriced) - 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 PaperYour output should be:
Book: Fiction Novel
Pen: Blue Ink
Notebook: Lined Paper
Total items: 3And given:
int
2
Apples
50
Oranges
30Your output should be:
Apples: 50
Oranges: 30
Total items: 2And given:
priced
2
Widget
10
19.99
Gadget
5
49.50Your output should be:
Widget x10 @ 19.9
Gadget x5 @ 49.5
Total items: 2Notice 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
}
This lesson includes a short quiz. Start the lesson to answer it and track your progress.
All lessons in Object Oriented Programming
1Fundamentals of Go OOP
External FilesGo Workspace & ModulesPackages & ImportsExported vs Unexported NamesIntroduction to OOP in GoStructs as ClassesDefining Methods on StructsPointer vs Value ReceiversStruct InitializationConstructor FunctionsRecap - Simple Calculator4Interfaces
Introduction to InterfacesImplicit ImplementationInterface as ContractEmpty Interface (any)Type AssertionType SwitchInterface CompositionStringer & Error InterfacesRecap - Shape Calculator7Encapsulation
Exported vs Unexported FieldsPackage-Level EncapsulationGetter & Setter MethodsInformation Hiding in GoRecap - Student Records10Generics (Go 1.18+)
Introduction to GenericsType ParametersType ConstraintsGeneric StructsGeneric Methods WorkaroundRecap - Generic Collection2Types & Structs Deep Dive
Basic & Composite TypesCustom Type DefinitionsStruct TagsAnonymous StructsNested StructsZero Values & DefaultsRecap - Contact Book5Composition Over Inheritance
Why Go Has No InheritanceStruct Embedding BasicsMethod PromotionEmbedding Multiple StructsEmbedding vs AggregationShadowing Embedded MethodsRecap - Employee Hierarchy8Error Handling & OOP
The error InterfaceCustom Error TypesError Wrapping (fmt.Errorf)Sentinel Errorserrors.Is() and errors.As()Panic, Defer, and RecoverRecap - File Parser3Pointers & Memory
Pointer Basics in GoPointers to StructsPass by Value vs ReferenceThe new() FunctionGarbage Collection in GoRecap - Linked List Builder6Polymorphism in Go
Polymorphism via InterfacesDuck Typing in GoInterface Satisfaction RulesPolymorphic CollectionsDependency InjectionRecap - Payment Processor9Concurrency & OOP
Goroutines BasicsChannels & CommunicationBuffered vs Unbuffered ChanSelect Statementsync.Mutex & sync.RWMutexsync.WaitGroupThread-Safe Struct DesignRecap - Worker Pool