CompoundStateSpace

dyn-Compatibility

What is dyn-Compatibility?

Why a Method Returning Self is a Problem

pub trait Clone {
    fn clone(&self) -> Self; // This returns the concrete type itself.
}

Solution

  1. Create a New Trait (DynClone): A new trait DynClone is created.
  2. Define an Object-Safe Method: Instead of returning Self, its method clone_box returns a Box<dyn State>. A Box is a smart pointer, and the size of the pointer itself is known at compile time, regardless of what it points to. This makes the method object-safe.
pub trait DynClone {
    fn clone_box(&self) -> Box<dyn State>; // Returns a Box, which has a known size.
}

Downcasting

The Problem

The Solution

Step 1: state1 as &dyn Any — The Prerequisite

Step 2: .downcast_ref::<T::StateType>() — The "Unmasking"

Step 3: .unwrap() — Assuming You're Correct

Putting It All Together

Code

// In oxmpl/src/base/state.rs
use std::any::Any;

pub trait DynClone {
    fn clone_box(&self) -> Box<dyn State>;
}

impl<T> DynClone for T
where
    T: State + Clone + 'static,
{
    fn clone_box(&self) -> Box<dyn State> {
        Box::new(self.clone())
    }
}

/// A marker trait for all state types in the planning library.
///
/// A `State` represents a single point, configuration, or snapshot of the system
/// being planned for.
///
/// Supertrait bounds:
/// - `DynClone`: States must be copyable as Dyn for runtime polymorphism.
///
/// > [!NOTE] (for self)
/// > A trait is not dyn-compatible if any of its methods return Self — unless it has a `where Self: Sized` bound.
pub trait State: DynClone + Any {}

impl Clone for Box<dyn State> {
    fn clone(&self) -> Self {
        self.clone_box()
    }
}

#[derive(Clone)]
pub struct CompoundState {
    pub components: Vec<Box<dyn State>>,
}

impl State for CompoundState {}

// compound_state_space.rs
use rand::Rng;
use std::any::Any;

use crate::base::{
    error::{StateSamplingError},
    space::StateSpace,
    state::{CompoundState, State},
};

/// A helper trait to allow calling methods on a `Box<dyn StateSpace>` with `&dyn State`.
/// This avoids needing to know the concrete `StateType` at compile time.
pub trait AnyStateSpace {
    /// A version of `distance` that works with trait objects.
    fn distance_dyn(&self, state1: &dyn State, state2: &dyn State) -> f64;
    /// A version of `interpolate` that works with trait objects.
    fn interpolate_dyn(&self, from: &dyn State, to: &dyn State, t: f64, state: &mut dyn State);
    // Add other _dyn methods for enforce_bounds, satisfies_bounds, etc.
}

impl<T: StateSpace> AnyStateSpace for T
where
    // The state type must be 'static to be downcast safely.
    T::StateType: 'static,
{
    fn distance_dyn(&self, state1: &dyn State, state2: &dyn State) -> f64 {
        let s1 = (state1 as &dyn Any).downcast_ref::<T::StateType>().unwrap();
        let s2 = (state2 as &dyn Any).downcast_ref::<T::StateType>().unwrap();
        self.distance(s1, s2)
    }

    fn interpolate_dyn(&self, from: &dyn State, to: &dyn State, t: f64, state: &mut dyn State) {
        let from_s = (from as &dyn Any).downcast_ref::<T::StateType>().unwrap();
        let to_s = (to as &dyn Any).downcast_ref::<T::StateType>().unwrap();
        let state_s = (state as &mut dyn Any).downcast_mut::<T::StateType>().unwrap();
        self.interpolate(from_s, to_s, t, state_s);
    }
}

/// A state space that is composed of multiple other state spaces.
///
/// This is used to represent complex configuration spaces which combine translational and
/// rotational components, etc.
pub struct CompoundStateSpace {
    /// The component state spaces. `AnyStateSpace` is used for type erasure.
    pub subspaces: Vec<Box<dyn AnyStateSpace + Send + Sync>>,
    /// The weight of each component's contribution to the total distance.
    pub weights: Vec<f64>,
}

impl CompoundStateSpace {
    /// Creates a new `CompoundStateSpace`.
    ///
    /// The `weights` vector must have the same length as the `subspaces` vector.
    pub fn new(subspaces: Vec<Box<dyn AnyStateSpace + Send + Sync>>, weights: Vec<f64>) -> Self {
        assert_eq!(subspaces.len(), weights.len(), "Number of subspaces must match number of weights.");
        Self { subspaces, weights }
    }
}

impl StateSpace for CompoundStateSpace {
    type StateType = CompoundState;

    fn distance(&self, state1: &Self::StateType, state2: &Self::StateType) -> f64 {
        let mut total_dist_sq = 0.0;
        for i in 0..self.subspaces.len() {
            let component_dist = self.subspaces[i].distance_dyn(
                &*state1.components[i], // Deref Box<dyn State> to &dyn State
                &*state2.components[i],
            );
            total_dist_sq += (component_dist * self.weights[i]).powi(2);
        }
        total_dist_sq.sqrt()
    }

    fn interpolate(
        &self,
        from: &Self::StateType,
        to: &Self::StateType,
        t: f64,
        out_state: &mut Self::StateType,
    ) {
        for i in 0..self.subspaces.len() {
            self.subspaces[i].interpolate_dyn(
                &*from.components[i],
                &*to.components[i],
                t,
                &mut *out_state.components[i], // Mutable deref
            );
        }
    }

    // Implement the other StateSpace methods by delegating to subspaces...
    fn enforce_bounds(&self, _state: &mut Self::StateType) { todo!() }
    fn satisfies_bounds(&self, _state: &Self::StateType) -> bool { todo!() }
    fn sample_uniform(&self, _rng: &mut impl Rng) -> Result<Self::StateType, StateSamplingError> { todo!() }
    fn get_longest_valid_segment_length(&self) -> f64 { todo!() }
}
Connected Pages
CompoundStateSpace
  • dyn-Compatibility
    1. What is dyn-Compatibility?
    2. Why a Method Returning Self is a Problem
    3. Solution
  • Downcasting
  • The Problem
  • The Solution
  • Step 1: state1 as &dyn Any — The Prerequisite
  • Step 2: .downcast_ref::() — The "Unmasking"
  • Step 3: .unwrap() — Assuming You're Correct
  • Putting It All Together
  • Code