Start learning free

LESSON 562 OF 576 · OBJECT-ORIENTED PROGRAMMING - CHALLENGES

Ticket Numbers in Rust: A Shared Counter with static mut

Practice O(1) Read the full explanation

A ticket machine gives every new ticket the next number: the first ticket gets 1, the next one 2, and so on. The counter belongs to all the tickets together, not to any one of them. Rust structs have no static fields: as in the Static Members lesson, the shared counter is a module-level static mut variable, read and changed only inside unsafe blocks. A function in the impl block that has no self is called on the type itself, like Ticket::new: that is Rust's static method. Each ticket keeps its own number, copied from the counter when the ticket is created.

Write:

  • The Ticket struct, with two fields: name (a String) and number (an i32).
  • The module-level counter static mut COUNT: i32, starting at 0.
  • Ticket::new(name): it adds 1 to COUNT and returns a new Ticket with that name and the new value of COUNT as its number.
  • Ticket::issued(): returns how many tickets were created since the counter was last reset.
  • Ticket::reset_counter(): sets the counter back to 0, so the next ticket gets number 1 again.

For each test case, the test code calls Ticket::reset_counter(), then creates one ticket per name with Ticket::new, in order. Once all the tickets of the case exist, it prints each ticket as its name, a space, # and its number, one ticket per line, and then the value of Ticket::issued() on a line of its own.

 

Example 1:

Input:
    Ticket::new("Ann")
    Ticket::new("Bob")
    Ticket::new("Cy")
Output:
Ann #1
Bob #2
Cy #3
3
Explanation: reset_counter() set the counter to 0. Each new ticket adds 1 to it and keeps the new value,
so the tickets get 1, 2 and 3, and issued() returns 3.

Example 2:

Input:
    Ticket::new("Mia")
    Ticket::new("Mia")
Output:
Mia #1
Mia #2
2
Explanation: The number comes from the shared counter, not from the name, so the two tickets for Mia
get different numbers. The numbers start at 1 again because reset_counter() ran before this case.

Example 3:

Input:
    (no tickets)
Output:
0
Explanation: reset_counter() set the counter to 0 and no ticket was created, so issued() returns 0.

 

Constraints:

  • A test case creates from 0 to 1000 tickets.
  • A name is one word of 1 to 20 English letters. Two tickets can have the same name.
  • The tickets are printed only after all of them were created, so every ticket must store its own number: reading COUNT when printing would give every ticket the last number.
STEP 1 OF 7
    Ctrl + Enter to check

    RUST · OBJECT-ORIENTED PROGRAMMING - CHALLENGES

    Ticket Numbers in Rust, explained

    Problem overview

    A ticket machine gives every new ticket the next number: the first ticket gets 1, the next gets 2, and so on. The counter belongs to all the tickets together, not to any one of them, while each ticket keeps its own name and its own number.

    You write three things:

    • the Ticket struct, with a name (a String) and a number (an i32);
    • a module-level counter, static mut COUNT: i32, starting at 0;
    • three functions in impl Ticket: Ticket::new(name), Ticket::issued() and Ticket::reset_counter().

    After a reset, creating tickets for Ann, Bob and Cy gives them the numbers 1, 2 and 3, and issued() returns 3. Two tickets for Mia get different numbers (1 and 2), because the number comes from the counter, not from the name.

    Approach

    A field of a struct belongs to each value separately: two tickets have two number fields. A shared counter needs exactly one copy for the whole program. Rust structs have no static fields, so the counter is a static item placed outside the struct. Because it must change, it is declared static mut.

    The rule for a ticket is then short:

    1. add 1 to the counter;
    2. copy the counter's new value into the new ticket's number.

    The copy is the important part. The counter keeps moving as more tickets are made, but each ticket's number is a plain i32 that stays what it was when the ticket was created.

    Step by step

    1. Declare the struct with name: String and number: i32. Every ticket has its own copy of both.
    2. Declare the counter below the struct: static mut COUNT: i32 = 0;. There is one COUNT for the whole program, so every ticket sees the same one.
    3. In new, add 1 to COUNT inside an unsafe block. Rust requires unsafe for reading or changing a static mut, because two threads could change it at the same time. A single-threaded program like this one is fine, but the compiler can't know that.
    4. Build the ticket as the last expression. The name is a &str parameter, so name.to_string() makes the owned String that the struct stores.
    5. Set number to unsafe { COUNT }. That block's value is the counter, copied into the field.
    6. issued returns unsafe { COUNT }. It has no self, so it is called on the type as Ticket::issued(), like Ticket::new. This is Rust's version of a static method.
    7. reset_counter assigns 0 to COUNT, again inside unsafe, so the next ticket gets number 1.

    The solution in Rust

    struct Ticket {
        name: String,
        number: i32,
    }
    static mut COUNT: i32 = 0;
    impl Ticket {
        fn new(name: &str) -> Ticket {
            unsafe {
                COUNT += 1;
            }
            Ticket {
                name: name.to_string(),
                number: unsafe { COUNT },
            }
        }
        fn issued() -> i32 {
            unsafe { COUNT }
        }
        fn reset_counter() {
            unsafe {
                COUNT = 0;
            }
        }
    }
    

    Using it:

    Ticket::reset_counter();
    let tickets = vec![Ticket::new("Ann"), Ticket::new("Bob"), Ticket::new("Cy")];
    for t in &tickets {
        println!("{} #{}", t.name, t.number);
    }
    println!("{}", Ticket::issued());
    Ticket::reset_counter();
    println!("{}", Ticket::issued());
    println!("{}", tickets[0].number);
    

    It prints:

    Ann #1
    Bob #2
    Cy #3
    3
    0
    1
    

    After the reset, issued() is 0, but Ann's ticket still says 1: it holds its own copy of the number.

    Rust details worth knowing:

    • static mut needs unsafe everywhere. Without it, COUNT += 1; is rejected with "use of mutable static is unsafe and requires unsafe function or block".
    • Two unsafe blocks in new. The first changes the counter. The second reads it while the struct is built. An unsafe block is an expression, so unsafe { COUNT } can sit in the middle of the struct literal.
    • Associated functions. issued and reset_counter have no self, so they are called with Ticket:: and need no ticket at all.
    • The statics are a tool for this lesson. A shared mutable global is rarely the best design in a real program. It is fine here because the program runs one thing at a time. Programs with several threads use types made for sharing, such as atomics.

    Complexity

    Each function does a constant amount of work, so new, issued and reset_counter all run in O(1) time. The counter is one integer for the whole program, and each ticket adds its name and number.

    Edge cases and common mistakes

    • Reading COUNT later instead of storing it. The tickets are printed after all of them are created. A ticket that reads COUNT when asked for its number reports the last value for everyone: with Ann, Bob and Cy it prints Ann #3, Bob #3 and Cy #3. Copying the value into number at creation avoids that.
    • Adding 1 after reading. The first ticket must get 1, so the counter is increased first and then copied. Copying first would make the numbers start at 0.
    • Forgetting the reset. The tests call reset_counter() before each case, so every case numbers its tickets from 1. Without the reset, the second case would go on from where the first one stopped.
    • Counting by name. Two tickets with the same name are still two tickets: Mia's tickets get 1 and 2.
    • No tickets. After a reset and before any new, issued() returns 0.

    Testing

    • After a reset, Ann, Bob and Cy give Ann #1, Bob #2, Cy #3 and an issued() of 3.
    • After a reset, two tickets named Mia give Mia #1, Mia #2 and an issued() of 2.
    • After a reset and no tickets, issued() gives 0.
    Keep going with Rust500+ step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free