Observable value, code review

I decided to try to make my own observable values. The main difference from the crates that I looked at is that I wrap values in Rc and store weak references for subscribed values. This allows us not to think about ownership but should be expensive. And I had a hard time expressing what I want with existing crates.
Anyway, any feedback is welcome.

Example:

        let ctx = ObservableContext::new();
        let observable = Observable::<i32>::new(0, Rc::clone(&ctx));

        let computed = {
            let observable = Rc::clone(&observable); // how to avoid this clone?
            Computed::new(move || *observable.get() + 1, ctx.clone())
        };

        assert_eq!(*computed.get(), 1);

        observable.set(100);
        assert_eq!(*computed.get(), 101);

        *observable.get_mut() = 200;
        assert_eq!(*computed.get(), 201);

        observable.set(300);
        assert_eq!(*computed.get(), 301);

As you can see, I have to explicitly clone Observable before using it in a closure passed to Computed, but I have no idea how to avoid it.

Here's my attempt: Rust Playground

Some implementation notes:

  • Observable<T> is just a new-type over RefCell<T> [1]. They have practically the same functionality and interface.

  • Computed can directly reference an Observable, removing the need to clone an Rc. This greatly simplifies everything and removes the subscriber list and all of the reference counting; Computed becomes completely lazy.

    • If you need to store Computed and Observable together in a struct, then Rc should be used
      instead of &Observable. Doing this will reintroduce the need to clone the Rc, without reintroducing the subscriber list and other indirections (see test_move): Rust Playground
  • Likewise, ObservableContext and the Observer trait are no longer necessary.

Doing this removes the subscribe functionality. But it's unclear how you intend callers to use it directly. The test_observable() code reaches into private fields to gain access to the method. So it appears that it was for internal usage only.

If you really want the subscribe functionality to be exposed in the public API, it requires a little more effort: Rust Playground


  1. My initial implementation literally just used pub type Observable<T> = RefCell<T>; and removed its entire impl block. This was a huge win for boilerplate reduction. But it changes the interface and looks too different from other alternative implementations I decided to share.
    Here's the original tiny version: Rust Playground ↩︎

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.