pub fn computed<T, U, F>(
dependencies: Vec<Arc<ObservableProperty<T>>>,
compute_fn: F,
) -> Result<Arc<ObservableProperty<U>>, PropertyError>Expand description
Creates a computed property that automatically recomputes when any dependency changes
A computed property is a special type of ObservableProperty whose value is derived
from one or more other observable properties (dependencies). When any dependency changes,
the computed property automatically recalculates its value and notifies its own observers.
§Type Parameters
T- The type of the dependency propertiesU- The type of the computed property’s valueF- The compute function type
§Arguments
dependencies- A vector ofArc<ObservableProperty<T>>that this computed property depends oncompute_fn- A function that takes a slice of current dependency values and returns the computed value
§Returns
An Arc<ObservableProperty<U>> that will automatically update when any dependency changes.
Returns an error if subscribing to dependencies fails.
§How It Works
- Creates a new
ObservableProperty<U>with the initial computed value - Subscribes to each dependency property
- When any dependency changes, recomputes the value using all current dependency values
- Updates the computed property, which triggers its own observers
§Examples
§Basic Math Computation
use observable_property::{ObservableProperty, computed};
use std::sync::Arc;
// Create source properties
let width = Arc::new(ObservableProperty::new(10));
let height = Arc::new(ObservableProperty::new(5));
// Create computed property for area
let area = computed(
vec![width.clone(), height.clone()],
|values| values[0] * values[1]
)?;
// Initial computed value
assert_eq!(area.get()?, 50);
// Change width - area updates automatically
width.set(20)?;
std::thread::sleep(std::time::Duration::from_millis(10));
assert_eq!(area.get()?, 100);
// Change height - area updates automatically
height.set(8)?;
std::thread::sleep(std::time::Duration::from_millis(10));
assert_eq!(area.get()?, 160);§String Concatenation
use observable_property::{ObservableProperty, computed};
use std::sync::Arc;
let first_name = Arc::new(ObservableProperty::new("John".to_string()));
let last_name = Arc::new(ObservableProperty::new("Doe".to_string()));
let full_name = computed(
vec![first_name.clone(), last_name.clone()],
|values| format!("{} {}", values[0], values[1])
)?;
assert_eq!(full_name.get()?, "John Doe");
first_name.set("Jane".to_string())?;
std::thread::sleep(std::time::Duration::from_millis(10));
assert_eq!(full_name.get()?, "Jane Doe");§Total Price with Tax
use observable_property::{ObservableProperty, computed};
use std::sync::Arc;
let subtotal = Arc::new(ObservableProperty::new(100.0_f64));
let tax_rate = Arc::new(ObservableProperty::new(0.08_f64)); // 8% tax
let total = computed(
vec![subtotal.clone(), tax_rate.clone()],
|values| values[0] * (1.0 + values[1])
)?;
assert_eq!(total.get()?, 108.0);
subtotal.set(200.0)?;
std::thread::sleep(std::time::Duration::from_millis(10));
assert_eq!(total.get()?, 216.0);
tax_rate.set(0.10)?; // Change to 10%
std::thread::sleep(std::time::Duration::from_millis(10));
// Use approximate comparison due to floating point precision
let result = total.get()?;
assert!((result - 220.0).abs() < 0.0001);§Observing Computed Properties
Computed properties are themselves ObservableProperty instances, so you can subscribe to them:
use observable_property::{ObservableProperty, computed};
use std::sync::Arc;
let a = Arc::new(ObservableProperty::new(5));
let b = Arc::new(ObservableProperty::new(10));
let sum = computed(
vec![a.clone(), b.clone()],
|values| values[0] + values[1]
)?;
// Subscribe to changes in the computed property
sum.subscribe(Arc::new(|old, new| {
println!("Sum changed from {} to {}", old, new);
}))?;
a.set(7)?; // Will print: "Sum changed from 15 to 17"
std::thread::sleep(std::time::Duration::from_millis(10));§Chaining Computed Properties
You can create computed properties that depend on other computed properties:
use observable_property::{ObservableProperty, computed};
use std::sync::Arc;
let celsius = Arc::new(ObservableProperty::new(0.0));
// First computed property: Celsius to Fahrenheit
let fahrenheit = computed(
vec![celsius.clone()],
|values| values[0] * 9.0 / 5.0 + 32.0
)?;
// Second computed property: Fahrenheit to Kelvin
let kelvin = computed(
vec![fahrenheit.clone()],
|values| (values[0] - 32.0) * 5.0 / 9.0 + 273.15
)?;
assert_eq!(celsius.get()?, 0.0);
assert_eq!(fahrenheit.get()?, 32.0);
assert_eq!(kelvin.get()?, 273.15);
celsius.set(100.0)?;
std::thread::sleep(std::time::Duration::from_millis(10));
assert_eq!(fahrenheit.get()?, 212.0);
assert_eq!(kelvin.get()?, 373.15);§Thread Safety
Computed properties are fully thread-safe. Updates happen asynchronously in response to dependency changes, and proper synchronization ensures the computed value is always based on the current dependency values at the time of computation.
§Performance Considerations
- The compute function is called every time any dependency changes
- For expensive computations, consider using
subscribe_debouncedorsubscribe_throttledon the dependencies before computing - The computed property uses async notifications, so there may be a small delay between a dependency change and the computed value update