Is building a type tree with generated node ids (usize) possible in current nightly?

Hi,

my first post, because I feel stuck at the moment with rust's compile time constructs. I have started a project for building DSP Applications. One aspect is, that I try to offer two modes of usage. For the dsp people basic Algorithms can be realized by implementing a trait "DspModule". For the application developer I want to support composition by building up a type tree using a trait "DspCompositeModule".

An additional requirment that I am trying to solve is that a DspCompositeModule can support any number of children between [2..15]. The "compute" function of the composite should be generic regarding the number of Child Nodes.

After finding out, that the appropriate Rust feature Variadic Generics is not existent at the moment, I came up with the following example solution (Not the real project, a simplified one focusing on the described aspect)

use std::marker::PhantomData;
use std::any::Any;

pub trait DspNode<MD: ModuleDefinition,const NdLvl: u8, const NdIdx:u8, const pTreeIdx: u16> {

    const node_idx: u16;

    fn printNodeInfo();

}

pub struct DspLeaf<MD: ModuleDefinition, const NodeLevel: u8, const NdIdx:u8, const pTreeIdx: u16> {
    md: PhantomData<MD>
}

impl <MD , const NodeLevel: u8,  const NdIdx:u8, const pTreeIdx: u16> DspNode<MD, NodeLevel, NdIdx, pTreeIdx>
for DspLeaf<MD,NodeLevel, NdIdx, pTreeIdx> where MD: ModuleDefinition,
{

    const node_idx: u16 = match NodeLevel{
        2u8 => {
            ((NdIdx as u16 +1)<< 8) + pTreeIdx
        }
        1u8 => {
            ((NdIdx as u16 +1)<< 4) + pTreeIdx
        }
        0u8 =>{
            (NdIdx as u16)  + pTreeIdx
        }
        _ =>{
            // println!("Node level {} not allowed!", NodeLevel);
            u16::MAX
        }
    };

    fn printNodeInfo() {
        {println!("Dsp Node Info: Module[idx={}, name={}, Primitive]", Self::node_idx, <MD as ModuleDefinition>::get_name());};
    }
}

/** Group2 DspNode
 *
*/
pub struct DspGroup2<MD,const NodeLevel: u8,  const NdIdx:u8, const pTreeIdx: u16,
    A:DspNode<MDA , NodeLevelA, NdIdxA, pTreeIdxA>,
    B:DspNode<MDB,  NodeLevelB, NdIdxB, pTreeIdxB>,
    MDA, const NodeLevelA: u8,  const NdIdxA:u8, const pTreeIdxA: u16,
    MDB, const NodeLevelB: u8,  const NdIdxB:u8, const pTreeIdxB: u16
>
    where MD: ModuleDefinition,
          MDA: ModuleDefinition,
          MDB: ModuleDefinition
{
    a: PhantomData<A>,
    b: PhantomData<B>,
    md: PhantomData<MD>,
    mda: PhantomData<MDA>,
    mdb: PhantomData<MDB>,

}

impl <MD,const NodeLevel: u8,  const NdIdx:u8, const pTreeIdx: u16,
    A:DspNode<MDA, NodeLevelA, NdIdxA, pTreeIdxA>,
    B:DspNode<MDB, NodeLevelB, NdIdxB, pTreeIdxB>,
    MDA, const NodeLevelA: u8,  const NdIdxA:u8, const pTreeIdxA: u16,
    MDB, const NodeLevelB: u8,  const NdIdxB:u8, const pTreeIdxB: u16
    >
    DspNode<MD, NodeLevel, NdIdx, pTreeIdx>

    for DspGroup2<MD,NodeLevel, NdIdx, pTreeIdx, A, B,
        MDA, NodeLevelA, NdIdxA, pTreeIdxA,
        MDB, NodeLevelB, NdIdxB, pTreeIdxB
    >
    where MD: ModuleDefinition,
          MDA: ModuleDefinition,
          MDB: ModuleDefinition
{
    const node_idx: u16 = match NodeLevel{
        2u8 => {
            ((NdIdx as u16 +1)<< 8) + pTreeIdx
        }
        1u8 => {
            ((NdIdx as u16 +1)<< 4) + pTreeIdx
        }
        _ =>{
            // println!("Node level {} not allowed!", NodeLevel);
            u16::MAX
        }
    };

    fn printNodeInfo() {
        {println!("Dsp Node Info: Module[idx={}, name={}, GroupNode2]", Self::node_idx, <MD as ModuleDefinition>::get_name());};
        A::printNodeInfo();
        B::printNodeInfo();
    }
}

/** Group3 DspNode
 *
*/

pub struct DspGroup3<MD,const NodeLevel: u8,  const NdIdx:u8, const pTreeIdx: u16,
    A:DspNode<MDA , NodeLevelA, NdIdxA, pTreeIdxA>,
    B:DspNode<MDB,  NodeLevelB, NdIdxB, pTreeIdxB>,
    C:DspNode<MDC,  NodeLevelC, NdIdxC, pTreeIdxC>,
    MDA, const NodeLevelA: u8,  const NdIdxA:u8, const pTreeIdxA: u16,
    MDB, const NodeLevelB: u8,  const NdIdxB:u8, const pTreeIdxB: u16,
    MDC, const NodeLevelC: u8,  const NdIdxC:u8, const pTreeIdxC: u16
    >
    where MD: ModuleDefinition,
          MDA: ModuleDefinition,
          MDB: ModuleDefinition,
          MDC: ModuleDefinition
{
    a: PhantomData<A>,
    b: PhantomData<B>,
    c: PhantomData<C>,
    md: PhantomData<MD>,
    mda: PhantomData<MDA>,
    mdb: PhantomData<MDB>,
    mdc: PhantomData<MDC>,

}

impl <MD,const NodeLevel: u8,  const NdIdx:u8, const pTreeIdx: u16,
    A:DspNode<MDA , NodeLevelA, NdIdxA, pTreeIdxA>,
    B:DspNode<MDB,  NodeLevelB, NdIdxB, pTreeIdxB>,
    C:DspNode<MDC,  NodeLevelC, NdIdxC, pTreeIdxC>,
    MDA, const NodeLevelA: u8,  const NdIdxA:u8, const pTreeIdxA: u16,
    MDB, const NodeLevelB: u8,  const NdIdxB:u8, const pTreeIdxB: u16,
    MDC, const NodeLevelC: u8,  const NdIdxC:u8, const pTreeIdxC: u16
    >
    DspNode<MD, NodeLevel, NdIdx, pTreeIdx>
    for DspGroup3<MD,NodeLevel, NdIdx, pTreeIdx, A, B, C,
        MDA, NodeLevelA, NdIdxA, pTreeIdxA,
        MDB, NodeLevelB, NdIdxB, pTreeIdxB,
        MDC, NodeLevelC, NdIdxC, pTreeIdxC
    >
    where MD: ModuleDefinition,
          MDA: ModuleDefinition,
          MDB: ModuleDefinition,
          MDC: ModuleDefinition
    {
    const node_idx: u16 = match NodeLevel{
        2u8 => {
            ((NdIdx as u16 +1)<< 8) + pTreeIdx
        }
        1u8 => {
            ((NdIdx as u16 +1)<< 4) + pTreeIdx
        }
        _ =>{
            // println!("Node level {} not allowed!", NodeLevel);
            u16::MAX
        }
    };

    fn printNodeInfo() {
        {println!("Dsp Node Info: Module[idx={}, name={}, GroupNode3]", Self::node_idx, <MD as ModuleDefinition>::get_name());};
        A::printNodeInfo();
        B::printNodeInfo();
        C::printNodeInfo();
    }
}


pub trait ModuleDefinition {

    const NAME: &'static str;

    fn get_name()->  &'static str{
        Self::NAME
    }

    fn create() ->Self;
}

with the following example of usage:

mod dspnode;
mod comp;


struct ModuleDefinitionA{

}
impl ModuleDefinition for ModuleDefinitionA{
    const NAME: &'static str = "I am Leaf A";
    fn create() -> Self {
        ModuleDefinitionA{}
    }
}


struct ModuleDefinitionB{

}

impl ModuleDefinition for ModuleDefinitionB{
    const NAME: &'static str = "I am Leaf B";
    fn create() -> Self {
        ModuleDefinitionB{}
    }
}

struct ModuleDefinitionC{

}

impl ModuleDefinition for ModuleDefinitionC{
    const NAME: &'static str = "I am Leaf C";
    fn create() -> Self {
        ModuleDefinitionC{}
    }
}


struct GroupModuleDefinition{

}

impl ModuleDefinition for GroupModuleDefinition{
    const NAME: &'static str = "I am Group 1";
    fn create() -> Self {
        GroupModuleDefinition{}
    }
}

struct GroupModuleDefinition2{

}


impl ModuleDefinition for GroupModuleDefinition2{
    const NAME: &'static str = "I am Group 2";
    fn create() -> Self {
        GroupModuleDefinition2{}
    }
}

pub const fn pidx_lvl1 (p2_idx:u8)-> u16{
    return (p2_idx as u16) << 8;
}

const fn pidx_lvl0 (p2_idx:u8, p1_idx: u8)-> u16{
    return ((p2_idx as u16) << 8) + ((p1_idx as u16) <<4)+1
}

fn tree_test () -> () {

    type MyDspTree = DspGroup3<GroupModuleDefinition,2u8,0u8,0u16,
        DspLeaf<ModuleDefinitionA,1u8,0u8,{pidx_lvl1(0u8)}>,
        DspLeaf<ModuleDefinitionB,1u8,1u8,{pidx_lvl1(0u8)}>,

        DspGroup2<GroupModuleDefinition2,1u8,2u8,{pidx_lvl1(0u8)},
            DspLeaf<ModuleDefinitionA,0u8,0u8,{pidx_lvl0(0u8, 2u8)}>,
            DspLeaf<ModuleDefinitionB,0u8,14u8,{pidx_lvl0(0u8, 2u8)}>,
            ModuleDefinitionA,0u8,0u8,{pidx_lvl0(0u8, 2u8)},
            ModuleDefinitionB,0u8,14u8,{pidx_lvl0(0u8, 2u8)},
        >,

        ModuleDefinitionA,1u8,0u8,{pidx_lvl1(0u8)},
        ModuleDefinitionB,1u8,1u8,{pidx_lvl1(0u8)},
        GroupModuleDefinition2,1u8,2u8,{pidx_lvl1(0u8)}
    >;

    MyDspTree::printNodeInfo();


}

However I am still not happy with this approach:

  1. First it does not fullfill the requirement that a group can handle a arbitrary number of children, so I need DspGroup2, DspGroup3.....
  2. Like you can see in the usage example an Application developer has to specify for each node in the type tree its level and child idx, which makes the construction of the tree very bloated and unreadable.
  3. Preconstructed group hierarchies would not be arbitrarily reusable, because you don't know on which level they will be located in the final application tree
    4.I don't like the idea of having another wrapper DspGroup / DspLeaf around the GroupModuleDefinitions / ModuleDefinitions which do the signal processing.

At first I focused on point 3 because obviously this is the biggest show stopper. For this reason I created the following mod_id generating function, that given the parent mod_id and the child_idx generates the child's node id. The tree of module ids would look like this:
0
256 512 768
16 32 272 288 ...
1..15 17..31 ....

const fn get_mod_id(par_mod_id: usize, mod_ref_id: usize) -> usize {
    //       (0,a3,a2,a1) + 256*a + 16*a2+a1
    // root  (0,0,0,0)    + ref_id  ->  l1 (0,ref_id,0,0)MOD_ID
    // l1    (0,a3,0,0)   + ref_id  ->  l2 (0,a3-1, ref_id,0)
    // l2    (0,a3,a2,0)  + ref_id ->  l3 (0,a3, a2-1,ref_id)
    // l3    (0,a3,a2,a1) -> ErroMOD_IDr

    //(0,a3,a2,a1) & a1 > 0 ->  level3 module -> max level
    let l3: usize = par_mod_id & 0b0000000000001111usize;
    if l3 > 0usize {
        return usize::MAX;
    } else {
        let l2: usize = (par_mod_id & 0b0000000011110000usize) >> 4;
        //(0,a3,a2,0) & a2 > 0 ->  level2 module -> (0,a3,a2-1,ref_id)
        if l2 > 0usize {
            let l1: usize = par_mod_id & 0b1111111100000000usize;
            return l1 + ((l2 - 1) << 4) + mod_ref_id;
        } else {
            //max 64 l1 modules
            let l1: usize = (par_mod_id & 0b1111111100000000usize) >> 8;
            //(0,a3,0,0) & a3 > 0 ->  level2 module -> (0,a3-1,ref-id,0)
            if l1 > 0usize {
                return ((l1 - 1) << 8) + (mod_ref_id << 4);
            } else {
                // (0,0,0,0) -> level1 module ->   (0,ref_id,0,0)
                return mod_ref_id << 8;
            }
        }
    }
}

Now this is my second intent to setup compile time structures, to allow building a type tree with recursive propagation of the module ids from the RootCompositeModule(0) to the LeafModules. I understand that this is not what I want, I am putting bounds on the child modules instead overwriting their default mod_ids.

pub trait DspModule<const MOD_ID: usize= {usize::MAX-1}>{

    const DSP_MOD_ID: usize = MOD_ID;
}

pub struct NOModule{}

pub trait DspCompositeModule<const MOD_ID: usize= {0usize}>: DspModule<MOD_ID>


{

    fn print_module_tree(){

    }

}


impl<DSP_MOD_P: DspCompositeModule<MOD_ID>, const MOD_ID: usize> DspModule<MOD_ID> for DSP_MOD_P{

}

impl <DSP_MOD_P: DspCompositeModule, DSP_MOD_A: DspModule<{get_mod_id({DSP_MOD_P::DSP_MOD_ID},1usize)}>> ModuleRef_A_DEF<DSP_MOD_P, DSP_MOD_A> for DSP_MOD_P
    where [(); {get_mod_id({DSP_MOD_P::DSP_MOD_ID},1usize)}]:{

}

pub trait ModuleRef_A_DEF<DSP_MOD_P: DspCompositeModule, DSP_MOD_A:DspModule, DSP_MOD_OUT = DSP_MOD_A<{get_mod_id({DSP_MOD_P::DSP_MOD_ID},1usize)}>>  {
}

struct ParentModule{

}

**//Why do I not get the error to set type parameter DSP_MOD_A, although I have a default **
**//impl of ModuleRef_A_DEF for anyDspCompositeModule?!!**
impl DspCompositeModule<0> for ParentModule{

}

My question is: Is this possible at all with the current nightly version of rust and the existing libraries. I am really happy with any solution no Matter how ugly it is, as long as the resulting composition API is less ugly as my first intent.

If there is currently no solution to this problem, is there any crude estimate when rust will support s.th. like this, 1, 10, 100 years?

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.