0xPolygonMiden / miden-vm

STARK-based virtual machine
MIT License
611 stars 148 forks source link

`MastForest` and `MastNodeId`: add `Tag` generic parameter #1368

Open plafer opened 1 week ago

plafer commented 1 week ago

The initial implementation of MastForest in #1217 made it the user's responsibility to use a MastNodeId with its corresponding MastForest; incorrect use would not be caught by the compiler.

I believe this can be solved by using "type tags", as outlined below. A few derived considerations:

struct MastForest<Tag> {
    nodes: Vec<MastNode>,
    _tag: PhantomData<Tag>,
}

impl<Tag> MastForest<Tag> {
    pub fn new() -> Self {
        Self {
            _tag: PhantomData,
            nodes: Vec::new()
        }
    }

    pub fn add_node(&mut self, mast_node: MastNode) -> MastNodeId<Tag> {
        let idx = self.nodes.len();
        self.nodes.push(mast_node);

        MastNodeId {
            idx,
            _tag: PhantomData,
        }
    }

    pub fn get_node_by_id(&self, id: MastNodeId<Tag>) -> Option<&MastNode> {
        self.nodes.get(id.idx)
    }
}

struct MastNodeId<Tag> {
    idx: usize,
    _tag: PhantomData<Tag>,
}

#[derive(Clone)]
struct MastNode(i32);

struct Tag1;
struct Tag2;

fn main() {
    let mut m1: MastForest<Tag1> = MastForest::new();
    let mut m2: MastForest<Tag2> = MastForest::new();

    let node = MastNode(17);

    let node_id_m1 = m1.add_node(node.clone());
    let node_id_m2 = m2.add_node(node);

    // compiles
    let _works = m1.get_node_by_id(node_id_m1);

    // fails to compile
    let _doesnt_work = m1.get_node_by_id(node_id_m2);
}
bobbinth commented 1 week ago

Very interesting! One question: if I understood the above correctly, tag need to be defined at compile time - right? If so, how would we deal with MAST forests which need to be instantiated at runtime? For example, say we have a set of serialized MastForest's (this set could be arbitrarily large) - how would we assign tags to them on deserialization?