High level requirement: I want to expose a single function to the users of my library. Something like:
pub fn execute<T>(data: T, param: String){
// Some computation here
}
Here is a MRE, with comments, on what exactly I am trying to do:
use std::{sync::{RwLock, Arc}, collections::HashMap};
use dashmap;
/// This function takes a standard DataSet
/// Param just simulates a parameter
fn execute_standard<DS: DataSet + ?Sized>(data: &DS, param: String) -> String {
return data.get_name().clone()
}
/// execute_cacheable is exactly the same as execute_standard
/// except it's arg is CacheableDataSet
/// Arc to share across threads
/// Perhaps should be & instead of Arc
fn execute_cacheable<DS: CacheableDataSet + ?Sized>(data: &DS, param: String) -> String {
// Simulating some fancy cache lookup
let cache = data.get_cache();
let lookup = cache.get(¶m);
let res = if let Some(r) = lookup
{
println!("Found: {:?}", r);
r.clone()
} else {
// Not found
let r = execute_standard(&*data, param.clone());
cache.insert(param, r.clone());
r
};
return res
}
/// No cache
struct Standard {
pub name: String
}
trait DataSet {
/// Simulates one of the methods on the main trait
fn get_name(&self) -> &String;
}
impl DataSet for Standard {
fn get_name(&self) -> &String {&self.name}
}
/// This Struct has Cache
struct WithCache {
pub name: String,
pub cache: dashmap::DashMap<String, String>
}
trait CacheableDataSet: DataSet {
fn get_cache(&self) -> &dashmap::DashMap<String, String>;
}
impl DataSet for WithCache {
fn get_name(&self) -> &String {&self.name}
}
impl CacheableDataSet for WithCache {
fn get_cache(&self) -> &dashmap::DashMap<String, String> {&self.cache}
}
pub fn main() {
// Arc is important because I share objects across threads (using actix)
let a = Arc::new(Standard{name: "London".into()});
let b = Arc::new(WithCache{name: "NY".into(), cache: dashmap::DashMap::default()});
// I'd like the users to use a single execute() function
// execute(&a, String::from("X"))
// execute(&b, String::from("X"))
// I wouldn't mind if it was like this
// a.execute(String::from("X"))
// b.execute(String::from("X"))
// What I can do now is not good enough I think
println!("{}", execute_standard(&*b, String::from("X")));
println!("{}", execute_cacheable(&*b, String::from("X")));
}
Note that trait DataSet
contains several methods, some default and some not. As such to avoid duplication it would be best to "reuse" what's already in the DataSet
.
It doesn't seem like a good idea to me to expose two functions to the users.
Originally I was looking for a way to check trait implementation at runtime. I found this and this but it uses unstable features and I'd like to avoid that as much as possible.
One possible solution I see is to use feature gates. Implement all Cacheable methods inside DataSet with #[cfg(feature = "cache")]
flag and use if cfg!(feature = "cache")
inside execute
, which then would have same signature as execute_standard
.
Surely I am not the first person trying to achieve this :) Is there any better way?