Rust Notification System
This challenge involves building a foundational notification system in Rust. You'll create a mechanism to send and manage simple notifications, simulating a common feature in many applications where users need to be informed of events or updates. This exercise will test your understanding of Rust's concurrency primitives and error handling.
Problem Description
Your task is to implement a Notifier struct that can:
- Register Subscribers: Allow multiple "subscribers" (represented by closures) to register for notifications.
- Send Notifications: Broadcast a message to all currently registered subscribers.
- Unregister Subscribers: Allow subscribers to be removed from the notification list.
The Notifier should be thread-safe, allowing multiple threads to register and send notifications concurrently without data races.
Key Requirements:
- A
Notifierstruct should be defined. - A method
registerto add a subscriber. A subscriber should be a function or closure that accepts aString(the notification message). - A method
notifyto send aStringmessage to all registered subscribers. - A method
unregisterto remove a specific subscriber. For simplicity, we can assume subscribers are identified by a unique ID or that theunregistermethod can accept the subscriber itself (though managing identity for closures can be tricky, we'll explore this). For this challenge, let's simplify by havingregisterreturn a unique ID thatunregistercan use. - The
Notifiermust be thread-safe.
Expected Behavior:
When notify is called with a message, each registered subscriber's closure should be invoked with that message. If a subscriber is unregistered, they should no longer receive notifications.
Edge Cases:
- Sending a notification when no subscribers are registered.
- Unregistering a subscriber that was never registered.
- Concurrent registration and unregistration while notifications are being sent.
Examples
Example 1:
Input:
let mut notifier = Notifier::new();
let sub1_id = notifier.register(|msg| println!("Subscriber 1 received: {}", msg));
let sub2_id = notifier.register(|msg| println!("Subscriber 2 received: {}", msg));
notifier.notify("Hello there!");
notifier.unregister(sub1_id);
notifier.notify("Another message.");
notifier.unregister(sub2_id);
notifier.notify("Final message.");
Output:
Subscriber 1 received: Hello there!
Subscriber 2 received: Hello there!
Subscriber 2 received: Another message.
Explanation:
- Two subscribers are registered, each printing a prefixed message.
- "Hello there!" is sent, received by both.
- Subscriber 1 is unregistered.
- "Another message." is sent, only received by Subscriber 2.
- Subscriber 2 is unregistered.
- "Final message." is sent, but there are no subscribers, so nothing is printed.
Example 2:
Input:
let mut notifier = Notifier::new();
notifier.notify("Initial broadcast."); // No subscribers
notifier.register(|msg| println!("User A: {}", msg));
notifier.notify("Second broadcast.");
notifier.unregister(0); // Assuming 0 is the ID of the first registered subscriber.
notifier.notify("Third broadcast.");
Output:
Second broadcast.
Third broadcast.
Explanation:
- The first
notifycall does nothing as there are no subscribers. - A subscriber is registered.
- "Second broadcast." is sent and received.
- The subscriber is unregistered.
- "Third broadcast." is sent, but the subscriber is gone, so nothing is printed.
Constraints
- The
Notifiermust be usable across multiple threads without causing data races. - Subscriber closures should be able to capture their environment if needed (though for this example, simple closures are sufficient).
- The registration process should yield unique, identifiable IDs for each subscriber.
- The
unregistermethod should efficiently remove a subscriber using their ID. - The
notifymethod should iterate over all currently registered subscribers without issues from concurrent modifications (registration/unregistration).
Notes
- Consider using
Arc<Mutex<...>>orArc<RwLock<...>>to make yourNotifierthread-safe. - When handling registrations and unregistrations concurrently with notifications, ensure that the collection of subscribers is accessed safely. A common pattern is to clone the list of subscribers before iterating to send notifications, allowing modifications to the main list while notifications are in progress.
- Think about how to store the subscribers. A
HashMapmapping IDs to closures might be a good choice. - For unregistering, ensure that the ID management is robust.
- The exact implementation of how closures are stored and invoked is up to you, but they must accept a
String.
Good luck building your notification system!