Building a Simple Watch Channel in Rust
This challenge asks you to implement a basic "watch channel" in Rust. A watch channel allows one party to "watch" another party's data, receiving notifications whenever that data changes. This is a fundamental pattern in concurrent programming and is useful for building reactive systems, UI updates, and more.
Problem Description
You are to create a WatchChannel struct in Rust that allows a sender to update a value and receivers to be notified of those updates. The WatchChannel should manage a shared value and a list of receiver functions (closures). When the sender updates the value, all registered receivers should be called with the new value.
Key Requirements:
WatchChannelStruct: This struct should hold the shared value and a vector of receiver functions (closures).sendMethod: A method onWatchChannelthat takes a new value and updates the shared value. This method should also call all registered receivers with the new value.subscribeMethod: A method onWatchChannelthat takes a receiver function (a closure that accepts the shared value type) and adds it to the list of receivers.unsubscribeMethod: A method onWatchChannelthat takes a receiver function (the same closure used insubscribe) and removes it from the list of receivers.- Generic Type: The
WatchChannelshould be generic over the type of the shared value.
Expected Behavior:
- When
sendis called, the shared value is updated, and all subscribed receivers are invoked with the new value. subscribeadds a receiver to the list, ensuring it will be called on subsequentsendcalls.unsubscriberemoves a receiver, preventing it from being called on subsequentsendcalls.- The channel should handle multiple subscribers concurrently.
Edge Cases to Consider:
- What happens if
unsubscribeis called with a receiver that was never subscribed? (Should be safe, no error needed) - What happens if
sendis called after all receivers have unsubscribed? (Should update the value internally, but no receivers are called) - Consider thread safety if you want to extend this in the future. For this challenge, assume single-threaded usage.
Examples
Example 1:
Input:
```rust
let mut channel: WatchChannel<i32> = WatchChannel::new();
let mut subscriber1 = |val: i32| { println!("Subscriber 1: {}", val); };
let mut subscriber2 = |val: i32| { println!("Subscriber 2: {}", val); };
channel.subscribe(subscriber1);
channel.subscribe(subscriber2);
channel.send(10);
Output:
Subscriber 1: 10
Subscriber 2: 10
Explanation: The channel is initialized with an i32 value. Two subscribers are added. When send(10) is called, both subscribers are invoked with the value 10.
Example 2:
Input:
```rust
let mut channel: WatchChannel<String> = WatchChannel::new();
let mut subscriber1 = |val: String| { println!("Subscriber 1: {}", val); };
let mut subscriber2 = |val: String| { println!("Subscriber 2: {}", val); };
channel.subscribe(subscriber1);
channel.send("Hello".to_string());
channel.unsubscribe(subscriber2);
channel.send("World".to_string());
Output:
Subscriber 1: Hello
Subscriber 1: World
Explanation: A string channel is created. Subscriber 1 is added. The first send calls subscriber 1 with "Hello". Subscriber 2 is then unsubscribed. The second send only calls subscriber 1 with "World".
Example 3: (Edge Case)
Input:
```rust
let mut channel: WatchChannel<f64> = WatchChannel::new();
let subscriber = |val: f64| { println!("Subscriber: {}", val); };
channel.subscribe(subscriber);
channel.unsubscribe(subscriber);
channel.send(3.14);
Output:
// No output
Explanation: A subscriber is added and immediately unsubscribed. The send call updates the internal value, but no receivers are called.
Constraints
- The shared value type
Tmust implement theClonetrait. This is necessary for copying the value to the receivers. - The
subscribeandunsubscribemethods must accept closures that take a value of typeTand return nothing (Fn(&T)). - The code should be reasonably efficient. Avoid unnecessary allocations or copies.
- The solution must compile and run without panics.
Notes
- Consider using a
Vecto store the receivers. - Closures are a powerful way to represent callbacks in Rust.
- Think about how to handle the ownership of the shared value. It should be owned by the
WatchChannel. - This is a simplified version of a pub/sub system. Real-world implementations are often more complex and involve thread safety, error handling, and more sophisticated filtering mechanisms.
- Focus on correctness and clarity first. Performance optimizations can be considered later.