const and iota — Tasks¶
Overview¶
This file contains 12 progressively challenging hands-on tasks that build real-world skills with Go's const and iota. Each task includes: - A clear objective - Starter code - Requirements checklist - Hints - Expected output or behavior
Complete each task in order. Tasks 1–4 are beginner, 5–8 are intermediate, 9–12 are advanced.
Task 1 — Basic Constants: Replace Magic Numbers¶
Objective¶
Refactor a function that uses magic numbers by replacing them with named constants.
Background¶
The function below works but uses raw numbers that are hard to understand. Replace every magic number with a named constant.
Starter Code¶
package main
import "fmt"
func classify(score int) string {
if score >= 90 {
return "A"
} else if score >= 80 {
return "B"
} else if score >= 70 {
return "C"
} else if score >= 60 {
return "D"
}
return "F"
}
func isValidAge(age int) bool {
return age >= 0 && age <= 150
}
func httpDescription(code int) string {
switch code {
case 200:
return "OK"
case 404:
return "Not Found"
case 500:
return "Internal Server Error"
}
return "Unknown"
}
func main() {
fmt.Println(classify(85))
fmt.Println(isValidAge(25))
fmt.Println(httpDescription(404))
}
Requirements¶
- Define a
constblock for grade thresholds - Define constants for age limits
- Define constants for HTTP status codes
- All original tests still pass
Hints¶
- Group related constants together in one
const ( ... )block - Use descriptive names:
GradeAMin,GradeBMin, etc. - HTTP codes can go in their own block
Expected Output¶
Solution
package main
import "fmt"
const (
GradeAMin = 90
GradeBMin = 80
GradeCMin = 70
GradeDMin = 60
)
const (
MinAge = 0
MaxAge = 150
)
const (
StatusOK = 200
StatusNotFound = 404
StatusInternal = 500
)
func classify(score int) string {
switch {
case score >= GradeAMin:
return "A"
case score >= GradeBMin:
return "B"
case score >= GradeCMin:
return "C"
case score >= GradeDMin:
return "D"
}
return "F"
}
func isValidAge(age int) bool {
return age >= MinAge && age <= MaxAge
}
func httpDescription(code int) string {
switch code {
case StatusOK:
return "OK"
case StatusNotFound:
return "Not Found"
case StatusInternal:
return "Internal Server Error"
}
return "Unknown"
}
func main() {
fmt.Println(classify(85))
fmt.Println(isValidAge(25))
fmt.Println(httpDescription(404))
}
Task 2 — Days of the Week Enum¶
Objective¶
Create a type-safe Weekday enum using iota and add utility methods.
Starter Code¶
package main
import "fmt"
// TODO: Define a Weekday type (int)
// TODO: Define constants Sunday through Saturday using iota
// TODO: Add a String() method
// TODO: Add an IsWeekend() method
func main() {
day := Wednesday // should be 3
fmt.Println(day) // should print "Wednesday"
fmt.Println(day.IsWeekend()) // should print false
fmt.Println(Saturday.IsWeekend()) // should print true
}
Requirements¶
- Define
type Weekday int - Define
SundaythroughSaturdayusingiota(Sunday = 0) - Implement
func (w Weekday) String() string - Implement
func (w Weekday) IsWeekend() bool— returns true for Saturday and Sunday -
fmt.Println(Wednesday)must printWednesday(not3)
Hints¶
- Use
[...]string{"Sunday", "Monday", ...}[w]for fast lookup IsWeekend()only needs to checkw == Saturday || w == Sunday
Expected Output¶
Solution
package main
import "fmt"
type Weekday int
const (
Sunday Weekday = iota
Monday
Tuesday
Wednesday
Thursday
Friday
Saturday
)
func (w Weekday) String() string {
names := [...]string{
"Sunday", "Monday", "Tuesday", "Wednesday",
"Thursday", "Friday", "Saturday",
}
if w < Sunday || w > Saturday {
return fmt.Sprintf("Weekday(%d)", int(w))
}
return names[w]
}
func (w Weekday) IsWeekend() bool {
return w == Saturday || w == Sunday
}
func main() {
day := Wednesday
fmt.Println(day)
fmt.Println(day.IsWeekend())
fmt.Println(Saturday.IsWeekend())
}
Task 3 — File Permission Bit Flags¶
Objective¶
Implement a Unix-inspired file permission system using bit flags and iota.
Starter Code¶
package main
import "fmt"
// TODO: Define FileMode type (uint)
// TODO: Define ModeRead, ModeWrite, ModeExecute using 1 << iota
// TODO: Implement func (m FileMode) String() string
// Should return a 3-char string like "rw-" or "r-x" or "rwx"
func main() {
perm := ModeRead | ModeWrite
fmt.Println(perm) // should print "rw-"
fmt.Println(ModeRead | ModeExecute) // should print "r-x"
fmt.Println(ModeRead | ModeWrite | ModeExecute) // should print "rwx"
fmt.Println(FileMode(0)) // should print "---"
}
Requirements¶
-
ModeRead = 1,ModeWrite = 2,ModeExecute = 4 -
String()returns a 3-character permission string - Combining flags with
|works correctly -
FileMode(0)returns"---"
Hints¶
- Use conditional:
if m&ModeRead != 0 { s += "r" } else { s += "-" } - Build the string character by character
Expected Output¶
Solution
package main
import "fmt"
type FileMode uint
const (
ModeRead FileMode = 1 << iota // 1
ModeWrite // 2
ModeExecute // 4
)
func (m FileMode) String() string {
r, w, x := "-", "-", "-"
if m&ModeRead != 0 {
r = "r"
}
if m&ModeWrite != 0 {
w = "w"
}
if m&ModeExecute != 0 {
x = "x"
}
return r + w + x
}
func main() {
perm := ModeRead | ModeWrite
fmt.Println(perm)
fmt.Println(ModeRead | ModeExecute)
fmt.Println(ModeRead | ModeWrite | ModeExecute)
fmt.Println(FileMode(0))
}
Task 4 — Byte Size Constants¶
Objective¶
Implement byte size constants using iota expressions and a ByteSize type with automatic formatting.
Starter Code¶
package main
import "fmt"
type ByteSize float64
// TODO: Define KB, MB, GB, TB using iota (1 << (10 * iota))
// Hint: skip iota=0 with _
// TODO: Implement String() method for ByteSize
// Format: "1.00KB", "2.50MB", "3.14GB", "1.00TB"
func main() {
fmt.Println(ByteSize(1024)) // 1.00KB
fmt.Println(ByteSize(1536)) // 1.50KB
fmt.Println(ByteSize(3 * 1024 * 1024)) // 3.00MB
fmt.Println(GB) // 1.00GB
}
Requirements¶
-
KB = 1024.0,MB = 1048576.0,GB = 1073741824.0,TB = 1099511627776.0 -
String()picks the largest unit the value is >= to - Output is formatted with 2 decimal places
Hints¶
- Use
iotastarting position 1 (_at position 0) switchwith cases from largest to smallest
Solution
package main
import "fmt"
type ByteSize float64
const (
_ = iota
KB ByteSize = 1 << (10 * iota) // 1024
MB // 1048576
GB // 1073741824
TB // 1099511627776
)
func (b ByteSize) String() string {
switch {
case b >= TB:
return fmt.Sprintf("%.2fTB", b/TB)
case b >= GB:
return fmt.Sprintf("%.2fGB", b/GB)
case b >= MB:
return fmt.Sprintf("%.2fMB", b/MB)
case b >= KB:
return fmt.Sprintf("%.2fKB", b/KB)
}
return fmt.Sprintf("%.2fB", b)
}
func main() {
fmt.Println(ByteSize(1024))
fmt.Println(ByteSize(1536))
fmt.Println(ByteSize(3 * 1024 * 1024))
fmt.Println(GB)
}
Task 5 — Priority Queue with Typed Constants¶
Objective¶
Build a priority queue that uses a typed Priority constant to order tasks.
Starter Code¶
package main
import (
"fmt"
"sort"
)
// TODO: Define Priority type and constants
// PriorityLow, PriorityMedium, PriorityHigh, PriorityCritical
// Skip the zero value (use iota + 1 or _ = iota)
type Task struct {
Name string
Priority // TODO: add Priority field
}
// TODO: Implement String() for Priority
func main() {
tasks := []Task{
{"Write tests", PriorityMedium},
{"Fix prod bug", PriorityCritical},
{"Update docs", PriorityLow},
{"Deploy feature", PriorityHigh},
}
sort.Slice(tasks, func(i, j int) bool {
return tasks[i].Priority > tasks[j].Priority // highest priority first
})
for _, t := range tasks {
fmt.Printf("[%s] %s\n", t.Priority, t.Name)
}
}
Requirements¶
-
Priorityis a namedinttype - Zero value means "unset" (not a valid priority)
-
PriorityLow < PriorityMedium < PriorityHigh < PriorityCritical -
String()returns readable names - Tasks are sorted highest-priority first
Expected Output¶
Solution
package main
import (
"fmt"
"sort"
)
type Priority int
const (
_ Priority = iota
PriorityLow // 1
PriorityMedium // 2
PriorityHigh // 3
PriorityCritical // 4
)
func (p Priority) String() string {
switch p {
case PriorityLow:
return "Low"
case PriorityMedium:
return "Medium"
case PriorityHigh:
return "High"
case PriorityCritical:
return "Critical"
}
return fmt.Sprintf("Priority(%d)", int(p))
}
type Task struct {
Name string
Priority Priority
}
func main() {
tasks := []Task{
{"Write tests", PriorityMedium},
{"Fix prod bug", PriorityCritical},
{"Update docs", PriorityLow},
{"Deploy feature", PriorityHigh},
}
sort.Slice(tasks, func(i, j int) bool {
return tasks[i].Priority > tasks[j].Priority
})
for _, t := range tasks {
fmt.Printf("[%s] %s\n", t.Priority, t.Name)
}
}
Task 6 — HTTP Router with Typed Method Constants¶
Objective¶
Implement a simple HTTP route registry using typed HTTPMethod constants.
Starter Code¶
package main
import "fmt"
// TODO: Define HTTPMethod type (string)
// TODO: Define GET, POST, PUT, DELETE, PATCH constants
type Route struct {
// TODO: add Method and Path fields
}
type Router struct {
routes []Route
}
func (r *Router) Add(method HTTPMethod, path string) {
// TODO: add a route
}
func (r *Router) List() {
// TODO: print all routes as "METHOD /path"
}
func main() {
router := &Router{}
router.Add(GET, "/users")
router.Add(POST, "/users")
router.Add(GET, "/users/:id")
router.Add(PUT, "/users/:id")
router.Add(DELETE, "/users/:id")
router.List()
}
Requirements¶
-
HTTPMethodis a namedstringtype - Constants are
GET,POST,PUT,DELETE,PATCH -
Addaccepts onlyHTTPMethod, not raw strings -
List()prints routes in the order they were added
Expected Output¶
Solution
package main
import "fmt"
type HTTPMethod string
const (
GET HTTPMethod = "GET"
POST HTTPMethod = "POST"
PUT HTTPMethod = "PUT"
DELETE HTTPMethod = "DELETE"
PATCH HTTPMethod = "PATCH"
)
type Route struct {
Method HTTPMethod
Path string
}
type Router struct {
routes []Route
}
func (r *Router) Add(method HTTPMethod, path string) {
r.routes = append(r.routes, Route{Method: method, Path: path})
}
func (r *Router) List() {
for _, route := range r.routes {
fmt.Printf("%s %s\n", route.Method, route.Path)
}
}
func main() {
router := &Router{}
router.Add(GET, "/users")
router.Add(POST, "/users")
router.Add(GET, "/users/:id")
router.Add(PUT, "/users/:id")
router.Add(DELETE, "/users/:id")
router.List()
}
Task 7 — State Machine with Typed States¶
Objective¶
Implement a connection state machine using typed ConnState constants with valid transition rules.
Starter Code¶
package main
import (
"errors"
"fmt"
)
// TODO: Define ConnState type (int)
// States: Disconnected, Connecting, Connected, Closing
// Use iota, skip zero value
// TODO: Add String() for ConnState
type Connection struct {
state ConnState
}
// TODO: Implement:
// func (c *Connection) Connect() error
// func (c *Connection) Close() error
// func (c *Connection) State() ConnState
// Valid transitions:
// Disconnected → Connecting
// Connecting → Connected
// Connected → Closing
// Closing → Disconnected
func main() {
conn := &Connection{}
fmt.Println(conn.State()) // Disconnected
if err := conn.Connect(); err != nil {
fmt.Println("Error:", err)
}
fmt.Println(conn.State()) // Connecting
// Try invalid transition
if err := conn.Connect(); err != nil {
fmt.Println("Error:", err) // should print error
}
}
Requirements¶
- Zero value means
Disconnected(first valid state, or skip zero and useiota+1) -
Connect()only works fromDisconnectedstate -
Close()only works fromConnectedstate - Invalid transitions return an error
-
State()returns current state
Solution
package main
import (
"errors"
"fmt"
)
type ConnState int
const (
Disconnected ConnState = iota
Connecting
Connected
Closing
)
func (s ConnState) String() string {
switch s {
case Disconnected:
return "Disconnected"
case Connecting:
return "Connecting"
case Connected:
return "Connected"
case Closing:
return "Closing"
}
return fmt.Sprintf("ConnState(%d)", int(s))
}
type Connection struct {
state ConnState
}
func (c *Connection) Connect() error {
if c.state != Disconnected {
return fmt.Errorf("cannot connect from state %s", c.state)
}
c.state = Connecting
return nil
}
func (c *Connection) Establish() error {
if c.state != Connecting {
return fmt.Errorf("not in connecting state")
}
c.state = Connected
return nil
}
func (c *Connection) Close() error {
if c.state != Connected {
return errors.New("cannot close: not connected")
}
c.state = Closing
return nil
}
func (c *Connection) State() ConnState {
return c.state
}
func main() {
conn := &Connection{}
fmt.Println(conn.State())
if err := conn.Connect(); err != nil {
fmt.Println("Error:", err)
}
fmt.Println(conn.State())
if err := conn.Connect(); err != nil {
fmt.Println("Error:", err)
}
}
Task 8 — Subscription Tier System¶
Objective¶
Build a subscription tier system where each tier has limits enforced by constants.
Starter Code¶
package main
import "fmt"
// TODO: Define Tier type and constants
// TierFree, TierBasic, TierPro, TierEnterprise (using iota)
// TODO: Add String() for Tier
type Limits struct {
Projects int
TeamMembers int
StorageGB int
APICallsPerDay int
}
// TODO: Implement func (t Tier) Limits() Limits
// Free: 3 projects, 1 member, 1GB, 100 API calls
// Basic: 10 projects, 5 members, 10GB, 1000 API calls
// Pro: 50 projects, 20 members, 100GB, 10000 API calls
// Enterprise: -1 (unlimited) for all
// TODO: Implement func (t Tier) CanAddProject(current int) bool
func main() {
for _, tier := range []Tier{TierFree, TierBasic, TierPro, TierEnterprise} {
l := tier.Limits()
fmt.Printf("%s: %d projects, %dGB storage\n", tier, l.Projects, l.StorageGB)
}
fmt.Println(TierFree.CanAddProject(3)) // false — at limit
fmt.Println(TierPro.CanAddProject(49)) // true
fmt.Println(TierEnterprise.CanAddProject(9999)) // true — unlimited
}
Requirements¶
- Four tiers with meaningful names
-
Limits()returns the correct limits per tier -
-1means unlimited -
CanAddProjecthandles unlimited (-1) correctly
Expected Output¶
Free: 3 projects, 1GB storage
Basic: 10 projects, 10GB storage
Pro: 50 projects, 100GB storage
Enterprise: -1 projects, -1GB storage
false
true
true
Solution
package main
import "fmt"
type Tier int
const (
TierFree Tier = iota
TierBasic
TierPro
TierEnterprise
tierCount
)
func (t Tier) String() string {
return [tierCount]string{"Free", "Basic", "Pro", "Enterprise"}[t]
}
type Limits struct {
Projects int
TeamMembers int
StorageGB int
APICallsPerDay int
}
var tierLimits = [tierCount]Limits{
{Projects: 3, TeamMembers: 1, StorageGB: 1, APICallsPerDay: 100},
{Projects: 10, TeamMembers: 5, StorageGB: 10, APICallsPerDay: 1000},
{Projects: 50, TeamMembers: 20, StorageGB: 100, APICallsPerDay: 10000},
{Projects: -1, TeamMembers: -1, StorageGB: -1, APICallsPerDay: -1},
}
func (t Tier) Limits() Limits {
if t < 0 || t >= tierCount {
return Limits{}
}
return tierLimits[t]
}
func (t Tier) CanAddProject(current int) bool {
limit := t.Limits().Projects
return limit == -1 || current < limit
}
func main() {
for _, tier := range []Tier{TierFree, TierBasic, TierPro, TierEnterprise} {
l := tier.Limits()
fmt.Printf("%s: %d projects, %dGB storage\n", tier, l.Projects, l.StorageGB)
}
fmt.Println(TierFree.CanAddProject(3))
fmt.Println(TierPro.CanAddProject(49))
fmt.Println(TierEnterprise.CanAddProject(9999))
}
Task 9 — Log Level Filter System¶
Objective¶
Build a structured logger that filters messages by log level using typed constants.
Starter Code¶
package main
import "fmt"
// TODO: Define LogLevel type (int) and constants
// TRACE, DEBUG, INFO, WARN, ERROR, FATAL (using iota)
// TODO: Add String() method
type Logger struct {
// TODO: minLevel field
}
// TODO: func NewLogger(minLevel LogLevel) *Logger
// TODO: func (l *Logger) Log(level LogLevel, msg string)
// Only log if level >= minLevel
func main() {
l := NewLogger(WARN)
l.Log(DEBUG, "debugging something") // should NOT print
l.Log(INFO, "server started") // should NOT print
l.Log(WARN, "high memory usage") // SHOULD print
l.Log(ERROR, "db connection failed") // SHOULD print
l.Log(FATAL, "out of disk space") // SHOULD print
}
Requirements¶
-
TRACE < DEBUG < INFO < WARN < ERROR < FATAL - Logger only prints messages at or above
minLevel -
String()returns uppercase level names
Expected Output¶
Solution
package main
import "fmt"
type LogLevel int
const (
TRACE LogLevel = iota
DEBUG
INFO
WARN
ERROR
FATAL
logLevelCount
)
var levelNames = [logLevelCount]string{
"TRACE", "DEBUG", "INFO", "WARN", "ERROR", "FATAL",
}
func (l LogLevel) String() string {
if l < 0 || l >= logLevelCount {
return fmt.Sprintf("LogLevel(%d)", int(l))
}
return levelNames[l]
}
type Logger struct {
minLevel LogLevel
}
func NewLogger(minLevel LogLevel) *Logger {
return &Logger{minLevel: minLevel}
}
func (l *Logger) Log(level LogLevel, msg string) {
if level >= l.minLevel {
fmt.Printf("[%s] %s\n", level, msg)
}
}
func main() {
l := NewLogger(WARN)
l.Log(DEBUG, "debugging something")
l.Log(INFO, "server started")
l.Log(WARN, "high memory usage")
l.Log(ERROR, "db connection failed")
l.Log(FATAL, "out of disk space")
}
Task 10 — Build Options with Bit Flags¶
Objective¶
Implement a build system that accepts configuration options as bit flags.
Starter Code¶
package main
import (
"fmt"
"strings"
)
// TODO: Define BuildFlag type (uint) and flags:
// FlagOptimize, FlagDebug, FlagStrip, FlagVerbose, FlagTest
// Use 1 << iota
// TODO: func (f BuildFlag) String() string
// Show all active flags separated by "|", e.g., "optimize|verbose"
// If no flags: "none"
// TODO: func ValidateFlags(f BuildFlag) error
// FlagOptimize and FlagDebug cannot be combined
type BuildConfig struct {
Flags BuildFlag
Target string
}
func build(cfg BuildConfig) {
if err := ValidateFlags(cfg.Flags); err != nil {
fmt.Println("Invalid config:", err)
return
}
fmt.Printf("Building %s [%s]\n", cfg.Target, cfg.Flags)
}
func main() {
build(BuildConfig{FlagOptimize | FlagStrip | FlagVerbose, "myapp"})
build(BuildConfig{FlagDebug | FlagVerbose, "myapp-debug"})
build(BuildConfig{FlagOptimize | FlagDebug, "conflict"}) // should error
build(BuildConfig{0, "plain"})
}
Requirements¶
- Five flags defined with
1 << iota -
String()lists active flags by name, pipe-separated -
ValidateFlagsreturns error if Optimize+Debug are both set - Zero flags prints
"none"
Expected Output¶
Building myapp [optimize|strip|verbose]
Building myapp-debug [debug|verbose]
Invalid config: cannot combine optimize and debug flags
Building plain [none]
Solution
package main
import (
"fmt"
"strings"
)
type BuildFlag uint
const (
FlagOptimize BuildFlag = 1 << iota // 1
FlagDebug // 2
FlagStrip // 4
FlagVerbose // 8
FlagTest // 16
)
var flagDefs = []struct {
flag BuildFlag
name string
}{
{FlagOptimize, "optimize"},
{FlagDebug, "debug"},
{FlagStrip, "strip"},
{FlagVerbose, "verbose"},
{FlagTest, "test"},
}
func (f BuildFlag) String() string {
var parts []string
for _, def := range flagDefs {
if f&def.flag != 0 {
parts = append(parts, def.name)
}
}
if len(parts) == 0 {
return "none"
}
return strings.Join(parts, "|")
}
func ValidateFlags(f BuildFlag) error {
if f&FlagOptimize != 0 && f&FlagDebug != 0 {
return fmt.Errorf("cannot combine optimize and debug flags")
}
return nil
}
type BuildConfig struct {
Flags BuildFlag
Target string
}
func build(cfg BuildConfig) {
if err := ValidateFlags(cfg.Flags); err != nil {
fmt.Println("Invalid config:", err)
return
}
fmt.Printf("Building %s [%s]\n", cfg.Target, cfg.Flags)
}
func main() {
build(BuildConfig{FlagOptimize | FlagStrip | FlagVerbose, "myapp"})
build(BuildConfig{FlagDebug | FlagVerbose, "myapp-debug"})
build(BuildConfig{FlagOptimize | FlagDebug, "conflict"})
build(BuildConfig{0, "plain"})
}
Task 11 — Validated Enum from External Input¶
Objective¶
Build a function that safely converts untrusted integer input (from a JSON API or database) into a typed enum value.
Starter Code¶
package main
import (
"encoding/json"
"fmt"
)
// TODO: Define UserRole type (int) and constants
// RoleViewer=1, RoleEditor=2, RoleAdmin=3
// Do NOT use iota (roles must be stable for storage)
// TODO: Add String() for UserRole
// TODO: Add func UserRoleFromInt(n int) (UserRole, error)
// TODO: Add func UserRoleFromString(s string) (UserRole, error)
type User struct {
Name string
Role UserRole
}
func main() {
// Simulate JSON input
data := `{"name": "Alice", "role": 2}`
var raw struct {
Name string `json:"name"`
Role int `json:"role"`
}
json.Unmarshal([]byte(data), &raw)
role, err := UserRoleFromInt(raw.Role)
if err != nil {
fmt.Println("Error:", err)
return
}
user := User{Name: raw.Name, Role: role}
fmt.Printf("User: %s, Role: %s\n", user.Name, user.Role)
// Test invalid
_, err = UserRoleFromInt(99)
fmt.Println("Invalid role:", err)
_, err = UserRoleFromString("superadmin")
fmt.Println("Invalid string:", err)
}
Requirements¶
- Roles use explicit values (not iota) for stability
-
UserRoleFromIntvalidates and returns descriptive error -
UserRoleFromStringcase-insensitive matching -
String()returns lowercase role names
Expected Output¶
Solution
package main
import (
"encoding/json"
"fmt"
"strings"
)
type UserRole int
const (
RoleViewer UserRole = 1
RoleEditor UserRole = 2
RoleAdmin UserRole = 3
)
var roleNames = map[UserRole]string{
RoleViewer: "viewer",
RoleEditor: "editor",
RoleAdmin: "admin",
}
func (r UserRole) String() string {
if name, ok := roleNames[r]; ok {
return name
}
return fmt.Sprintf("Role(%d)", int(r))
}
func UserRoleFromInt(n int) (UserRole, error) {
r := UserRole(n)
if _, ok := roleNames[r]; ok {
return r, nil
}
return 0, fmt.Errorf("invalid role: %d", n)
}
func UserRoleFromString(s string) (UserRole, error) {
lower := strings.ToLower(s)
for role, name := range roleNames {
if name == lower {
return role, nil
}
}
return 0, fmt.Errorf("unknown role: %q", s)
}
type User struct {
Name string
Role UserRole
}
func main() {
data := `{"name": "Alice", "role": 2}`
var raw struct {
Name string `json:"name"`
Role int `json:"role"`
}
json.Unmarshal([]byte(data), &raw)
role, err := UserRoleFromInt(raw.Role)
if err != nil {
fmt.Println("Error:", err)
return
}
user := User{Name: raw.Name, Role: role}
fmt.Printf("User: %s, Role: %s\n", user.Name, user.Role)
_, err = UserRoleFromInt(99)
fmt.Println("Invalid role:", err)
_, err = UserRoleFromString("superadmin")
fmt.Println("Invalid string:", err)
}
Task 12 — Compile-Time Safety with Sentinel and Array¶
Objective¶
Design a Month enum with a sentinel constant that enforces compile-time synchronization between the enum and a lookup table.
Starter Code¶
package main
import "fmt"
// TODO: Define Month type (int) starting at 1 (January=1)
// Include all 12 months + monthCount sentinel
// Use iota + 1 for months (so January=1, not 0)
// TODO: Create a [monthCount+1]int array for days in each month
// Index 0 unused; index 1=January=31, ..., 12=December=31
// If monthCount changes and the array size doesn't match → compile error
// TODO: Add String() for Month using the array
// TODO: Implement func (m Month) Days(leapYear bool) int
// February has 28 days normally, 29 in leap year
func main() {
for m := January; m <= December; m++ {
fmt.Printf("%s: %d days (non-leap), %d days (leap)\n",
m, m.Days(false), m.Days(true))
}
}
Requirements¶
- January = 1, December = 12
-
monthCountis an unexported sentinel = 13 (one past December) - Array uses
monthCountas its size — compile error if out of sync -
Days(false)returns correct non-leap days -
Days(true)returns 29 for February, same as non-leap for others
Solution
package main
import "fmt"
type Month int
const (
January Month = iota + 1
February
March
April
May
June
July
August
September
October
November
December
monthCount // 13
)
var monthNames = [monthCount]string{
"", // index 0 unused
"January", "February", "March", "April",
"May", "June", "July", "August",
"September", "October", "November", "December",
}
// Compile-time check: array must have exactly monthCount entries
var daysInMonth = [monthCount]int{
0, // index 0 unused
31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31,
}
func (m Month) String() string {
if m < January || m >= monthCount {
return fmt.Sprintf("Month(%d)", int(m))
}
return monthNames[m]
}
func (m Month) Days(leapYear bool) int {
if m == February && leapYear {
return 29
}
if m < January || m >= monthCount {
return 0
}
return daysInMonth[m]
}
func main() {
for m := January; m <= December; m++ {
fmt.Printf("%s: %d days (non-leap), %d days (leap)\n",
m, m.Days(false), m.Days(true))
}
}
Summary Table¶
| Task | Topic | Level |
|---|---|---|
| 1 | Replace magic numbers with named constants | Beginner |
| 2 | Days-of-week enum with iota | Beginner |
| 3 | File permission bit flags | Beginner |
| 4 | Byte size constants with iota expression | Beginner |
| 5 | Priority queue with typed enum | Intermediate |
| 6 | HTTP router with typed string constants | Intermediate |
| 7 | Connection state machine | Intermediate |
| 8 | Subscription tier system | Intermediate |
| 9 | Log level filter logger | Advanced |
| 10 | Build options bit flags with validation | Advanced |
| 11 | Validated enum from external JSON | Advanced |
| 12 | Month enum with compile-time safety | Advanced |