Search code examples
ruststructcallbackstate

How to design a Rust struct with a callback to modify its state/fields


I'm using a library that provides me with structs and methods necessary to create an object and assign a custom callback to its event. But with each callback I need to keep track of a global or object wide state: a hashmap. What's the rust way of designing this. I thought I could create a new struct that contains the libraries object and a hashmap but not sure how to get the callback to modify that structs state


fn fileio_callback(record: &EventRecord, schema_locator: &SchemaLocator) {
    //...here i handle the event
    //but i also need to access the hashmap
    //...
}

struct EtwKernelFileTracker {
    trace: KernelTrace,
    fileobjects: HashMap<u64, String>,
}

impl EtwKernelFileTracker {
    fn new<T>(callback: T) -> EtwKernelFileTracker 
    where T: FnMut(&EventRecord, &SchemaLocator) + Send + Sync + 'static {    
        let file_provider = Provider::kernel(&KernelProvider::new(GUID::from("90cbdc39-4a3e-11d1-84f4-0000f80464e3"),EVENT_TRACE_FLAG_FILE_IO|EVENT_TRACE_FLAG_FILE_IO_INIT|EVENT_TRACE_FLAG_DISK_FILE_IO))
        .add_callback(callback)
        .build();

        let kernel_trace = KernelTrace::new()
        .named(String::from("file_io"))
        .enable(file_provider)
        .start_and_process()
        .unwrap();
        
        return EtwKernelFileTracker{trace: kernel_trace, fileobjects: HashMap::new()}
    }
} 


fn main() {
    let file_etw = EtwKernelFileTracker::new(fileio_callback);
    std::thread::sleep(Duration::new(10, 0));
}


I thought about putting the callback inside the struct but the way i think of doing it feels yucky especially with the function inputs and traits and still not even sure how to pass the hashmap to the callback at struct creation. I'm new to the language even if I'm way off I would like to know the different and better ways to design such functionality.


Solution

  • The callback does not have to be a freestanding function, it can be a closure that captures things. You can make it capture the HashMap. If you need to share it with other functions (other callbacks, for example), you can wrap it in Arc<Mutex>.

    use std::collections::HashMap;
    use std::sync::{Arc, Mutex};
    
    fn fileio_callback(
        record: &EventRecord,
        schema_locator: &SchemaLocator,
        map: &Mutex<HashMap<u64, String>>,
    ) {
        //...here i handle the event
        //but i also need to access the hashmap
        //...
    }
    
    struct EtwKernelFileTracker {
        trace: KernelTrace,
        fileobjects: Arc<Mutex<HashMap<u64, String>>>,
    }
    
    impl EtwKernelFileTracker {
        fn new<T>(callback: T) -> EtwKernelFileTracker
        where
            T: FnMut(&EventRecord, &SchemaLocator, &Mutex<HashMap<u64, String>>)
                + Send
                + Sync
                + 'static,
        {
            let map = Arc::new(Mutex::new(HashMap::new()));
    
            let file_provider = Provider::kernel(&KernelProvider::new(
                GUID::from("90cbdc39-4a3e-11d1-84f4-0000f80464e3"),
                EVENT_TRACE_FLAG_FILE_IO
                    | EVENT_TRACE_FLAG_FILE_IO_INIT
                    | EVENT_TRACE_FLAG_DISK_FILE_IO,
            ))
            .add_callback({
                let map = Arc::clone(&map);
                move |record, schema_locator| callback(record, schema_locator, &map)
            })
            .build();
    
            let kernel_trace = KernelTrace::new()
                .named(String::from("file_io"))
                .enable(file_provider)
                .start_and_process()
                .unwrap();
    
            EtwKernelFileTracker {
                trace: kernel_trace,
                fileobjects: map,
            }
        }
    }
    
    fn main() {
        let file_etw = EtwKernelFileTracker::new(fileio_callback);
        std::thread::sleep(Duration::new(10, 0));
    }