Expand description
Provides MobX style observables. Example:
use observatory as o;
o::init();
let first_name = o::observable("William");
let last_name = o::observable("Riker");
let nickname = o::observable::<Option<&'static str>>(None);
// A derivation is run the first time it is created, and the guts of the Derivation type will
// detect that the function borrows nickname, first_name, and last_name during that time.
let display_name = o::derivation_with_ptrs!(
first_name, last_name, nickname;
if let Some(name) = *nickname.borrow() {
format!("{}", name)
} else {
format!("{} {}", *first_name.borrow(), *last_name.borrow())
}
);
// Prints "William Riker"
let logger = o::derivation_with_ptrs!(
display_name;
println!("{}", *display_name.borrow())
);
// Prints "Will of Yam Riker"
first_name.set("Will of Yam");
// Prints "Number One"
// After executing this function the library will detect that `display_name` didn't need to
// borrow first_name or last_name to update its value.
nickname.set(Some("Number One"));
// Causes no updates, display_name has automatically unsubscribed from updates to last_name.
last_name.set("Something else");
§Observables
The function observable
returns a value of type ObservablePtr<T>
.
These represent singular pieces of data which can be modified through ObservablePtr::set
or
ObservablePtr::borrow_mut
. The value itself works like the standard library’s Rc
shared
pointer, in that it can be cloned and shared around, as long as it is not sent between threads:
use observatory as o;
o::init();
let data = o::observable(123);
let data2 = Clone::clone(&data);
data.set(42);
assert_eq!(*data2.borrow_untracked(), 42);
Notice the use of borrow_untracked
in the previous example. The normal ObservablePtr::borrow
is intended to be called from within the body of a derivation and as such will panic outside of
that environment.
§Derivations
Derivations themselves are a kind of observable, as their result can be
observed. However, they do not have set
or borrow_mut
functions. Instead, a single function
is specified which computes the value the derivation should have. This function is then
automatically re-run whenever any of the observables it had borrowed have changed. Here is an
example of a derivation:
use observatory as o;
o::init();
let number /* ObservablePtr<i32> */ = o::observable(3);
let number2 = Clone::clone(&number);
let squared /* DerivationPtr<i32, [closure type]> */ = o::derivation(move || {
let value = *number2.borrow();
value * value
});
assert_eq!(*squared.borrow_untracked(), 9);
number.set(4);
assert_eq!(*squared.borrow_untracked(), 16);
Cloning all the pointers you need to access from within the closure can be rather tedious, which
is where the derivation_with_ptrs
macro comes in:
use observatory as o;
o::init();
let number = o::observable(3);
let squared = o::derivation_with_ptrs!(
number, extra_example: number; {
let value = *number.borrow();
assert_eq!(*number.borrow(), *extra_example.borrow());
value * value
});
assert_eq!(*squared.borrow_untracked(), 9);
number.set(4);
assert_eq!(*squared.borrow_untracked(), 16);
The DerivationPtr
struct has two template parameters, one for the return type and another for
the type of the function. Because of this, it is not directly possible to store these pointers
in a struct or in a vector. To solve this, the function type can be made to be
Box<dyn FnMut() -> T>
, but this would introduce a lot of boilerplate. This is handled for you
if you use derivation_dyn
or o::derivation_with_pointers_dyn
and the type
alias DerivationDynPtr<T>
:
use observatory as o;
o::init();
let number = o::observable(3);
let squared /* DerivationDynPtr<i32> */ = o::derivation_with_ptrs_dyn!(
number, extra_example: number; {
let value = *number.borrow();
assert_eq!(*number.borrow(), *extra_example.borrow());
value * value
});
assert_eq!(*squared.borrow_untracked(), 9);
number.set(4);
assert_eq!(*squared.borrow_untracked(), 16);