I thought up the following code for swapping out functions at runtime (like a function that by default doesn't output logs while in production, but can be swapped with one that does to debug in-situ) but have a few questions about whether it's valid or not
use std::{sync::atomic::{AtomicUsize, Ordering}, marker::PhantomData};
pub struct SwappableFn<I, O> {
current: AtomicUsize,
__marker: PhantomData<fn(I) -> O>,
}
impl<I, O> SwappableFn<I, O> {
pub fn new(default: fn(I) -> O) -> Self {
Self { current: AtomicUsize::new(default as usize), __marker: PhantomData }
}
pub fn get(&self) -> fn(I) -> O {
let ptr = self.current.load(Ordering::SeqCst) as *mut ();
let fn_ptr = (&ptr) as *const *mut () as *const fn(I) -> O;
unsafe { *fn_ptr }
}
pub fn set(&self, new: fn(I) -> O) {
self.current.store(new as usize, Ordering::SeqCst)
}
}
Main questions:
Is the function pointer returned from get valid?
Are there any issues for swapping the function while it is being run on another thread?
Should I use AtomicPtr<()> instead of AtomicUsize?
Welcome to point out anything else you notice, though
(yeah i know the API is kinda awkward, such as requiring tuples to have multiple arguments. it's more of a "can I do this?" rather than "should I do this?")
You're not modifying the machine code in memory. What's stored within the AtomicUsize is an address to the machine code which is the start of the function.
So if I am not mistaken, you'd be doing something like this:
// Assume the log functions have a "type"
// fn(&'static str) -> ()
let log_fn = SwappableFn::new(prod_log);
// To actually call the following line, you'd need to make log_fn
// Send and Sync.
thread::spawn(|| loop {
let logger = log_fn.get();
logger("Hola!");
});
thread::sleep(Duration::from_secs(5)); // Just for kicks
log_fn.set(debug_log);
Since you're using atomics to load/store the function pointer, I think it'd work.
What you wrote is a reimplemtation of the transmute_copy, which is even worse as it doesn't check the size. Also I disagree implementing your own transmute would be better than using the one in the stdlib.
I doubt function pointers have provenance. I guess it doesn't make sense to get some function pointer from another function pointer using pointer arithmetics, which is the purpose of the provenance.