Problem overview
A ticket machine gives every new ticket the next number: 1 for the first, 2 for the next, and so on. Each ticket keeps its own number, but the counter that hands the numbers out belongs to all tickets together, not to any one of them.
In languages with classes, a counter like that is a static field, and the functions that read or reset it are static methods. Go has neither classes nor static. The same design uses a package-level variable, declared outside any function, so there is exactly one copy and every function in the package sees it. The "static methods" become plain functions.
The lesson asks for five things: the Ticket struct with a name and a number, the counter ticketCount, NewTicket(name), issued() and resetCounter().
Approach
There are two kinds of data here, and they live in different places:
- Per ticket: the name and the number. Every
Ticketvalue has its own copy. - Shared: how many tickets were made. One variable for the whole package.
Making a ticket is two actions in a fixed order: first add 1 to the counter, then build the ticket with the counter's new value as its number. The first ticket after a reset finds the counter at 1, so numbers start at 1.
The key point is that the ticket copies the value. The struct literal reads ticketCount at that moment and stores the result in the ticket's number. When the counter moves on, the old tickets keep their numbers. This matters because the tests create all the tickets first and print them afterwards: a ticket that read the counter when printed would show the last number, not its own.
Every function is constant time.
Step by step
- Declare the fields of
Ticket:nameas astringandnumberas anint. - Declare the counter below the struct and outside any function:
var ticketCount int. Package-level variables start at the zero value, so it starts at 0. - In
NewTicket, add 1 toticketCountwith++. Every ticket counts once, whatever its name. - Return the new ticket, with
nameas its name andticketCountas its number. - Write
issued, which returnsticketCount. It is a plain function: it needs no ticket, as a static method needs no object. - Write
resetCounter, which assigns 0 toticketCount. The next ticket will then get 1 again.
The solution in Go
type Ticket struct {
name string
number int
}
var ticketCount int
func NewTicket(name string) Ticket {
ticketCount++
return Ticket{name: name, number: ticketCount}
}
func issued() int {
return ticketCount
}
func resetCounter() {
ticketCount = 0
}
Go details:
- Package scope. A variable declared outside every function belongs to the package. Functions can read and change it without receiving it as a parameter.
++is a statement. In Go,ticketCount++is a statement on its own line, not an expression. You can't writenumber: ticketCount++, which is why the increment comes on its own line before the return.- Value copy.
intis a plain value.number: ticketCountcopies it, and later changes toticketCountdo not reach the ticket. - Shared state is shared by everything. Because the counter is one variable, a reset affects every future ticket. Use this kind of variable sparingly: it is global state, and in a program that creates tickets from several goroutines at once it would need protection, which this lesson doesn't cover.
Using it
resetCounter()
tickets := []Ticket{NewTicket("Ann"), NewTicket("Bob"), NewTicket("Cy")}
for _, t := range tickets {
fmt.Printf("%s #%d\n", t.name, t.number)
}
fmt.Println(issued())
resetCounter()
m1 := NewTicket("Mia")
m2 := NewTicket("Mia")
fmt.Println(m1.number, m2.number, issued())
resetCounter()
fmt.Println(issued())
This prints:
Ann #1
Bob #2
Cy #3
3
1 2 2
0
The tickets for Mia get different numbers because the number comes from the counter, not from the name. After the final reset, with no tickets made, issued() is 0.
Edge cases and common mistakes
- Reading the counter before adding 1. Building the ticket first and incrementing later gives the first ticket number 0, and every number is one too low.
- Storing the counter in each ticket. A
Ticketfield namedcountwould give every ticket its own counter, and each ticket would number itself 1. - Reading
ticketCountwhen printing. Every ticket would show the last number. The number has to be copied into the ticket when it is created. - Forgetting
resetCounter. Without it, a second group of tickets would carry on from where the first stopped instead of starting at 1. - Shadowing the counter. Writing
ticketCount := 0insideresetCounterdeclares a new local variable and leaves the shared one alone. Use=, not:=.
