Skip to main content

computed

Function computed 

Source
pub fn computed<T, U, F>(
    dependencies: Vec<Arc<ObservableProperty<T>>>,
    compute_fn: F,
) -> Result<Arc<ObservableProperty<U>>, PropertyError>
where T: Clone + Send + Sync + 'static, U: Clone + Send + Sync + 'static, F: Fn(&[T]) -> U + Send + Sync + 'static,
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 properties
  • U - The type of the computed property’s value
  • F - The compute function type

§Arguments

  • dependencies - A vector of Arc<ObservableProperty<T>> that this computed property depends on
  • compute_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

  1. Creates a new ObservableProperty<U> with the initial computed value
  2. Subscribes to each dependency property
  3. When any dependency changes, recomputes the value using all current dependency values
  4. 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_debounced or subscribe_throttled on 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