Last active
November 27, 2024 09:06
-
-
Save bonzini/2c63a905851f4f78573d022b1f196f4f to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
use std::marker::PhantomPinned; | |
use std::mem::MaybeUninit; | |
use std::os::raw::c_void; | |
// ---------------------------------------- | |
/// A callback to a logging function `f`. | |
/// This would ordinarily be provided by C code. | |
pub struct LogRegistration { | |
f: fn(&str, *mut c_void), | |
opaque: *mut c_void, | |
} | |
/// The Rust trait that is implemented by a logging backend | |
/// It is wrapped by [`LogRegistration::log_fn`] to convert | |
/// `&self` into an opaque pointer. | |
pub trait LogBackendOps<'a> { | |
fn log(&self, s: &str); | |
} | |
impl LogRegistration { | |
fn log_fn<'a, T: 'a + LogBackendOps<'a>>(s: &str, opaque: *mut c_void) { | |
let owner: &'a T = unsafe { &*opaque.cast_const().cast::<T>() }; | |
owner.log(s); | |
} | |
/// # Safety | |
/// | |
/// The owner promises that the LogRegistration is only included in a | |
/// Logger as long as the owner itself is alive. | |
pub unsafe fn new<'a, T: 'a + LogBackendOps<'a>>( | |
owner: *const T, | |
) -> Self { | |
LogRegistration { | |
f: Self::log_fn::<T>, | |
opaque: owner.cast_mut().cast(), | |
} | |
} | |
} | |
/// A hypothetical logging component that accepts multiple backends in | |
/// the form of callbacks. This would ordinarily be provided by C code. | |
#[derive(Default)] | |
pub struct Logger<'a> { | |
vec: Vec<&'a LogRegistration>, | |
} | |
impl<'a> Logger<'a> { | |
pub fn add_callback(&mut self, cb: &'a LogRegistration) { | |
self.vec.push(cb); | |
} | |
pub fn log(&self, s: &str) { | |
for cb in &self.vec { | |
(cb.f)(s, cb.opaque); | |
} | |
} | |
} | |
// ---------------------------------------- | |
/// Rust client for `Logger`. Uses MaybeUninit to construct a | |
/// callback+opaque pair that points to itself and that can be | |
/// added to a `Logger`. | |
#[repr(C)] | |
pub struct PrintlnLogBackend { | |
backend: LogRegistration, | |
pub prefix: &'static str, | |
// PhantomPinned avoids miri failures, see | |
// https://lore.kernel.org/rust-for-linux/CAH5fLgg7Sq4QDFP3iU981Txuda885mZdaUa0CRtgjrZBbyPBnw@mail.gmail.com/ | |
_pin: PhantomPinned, | |
} | |
impl LogBackendOps<'_> for PrintlnLogBackend { | |
fn log(&self, s: &str) { | |
println!("{}{}", self.prefix, s); | |
} | |
} | |
impl PrintlnLogBackend { | |
pub fn new() -> Box<Self> { | |
let mut logger: Box<MaybeUninit<Self>> = Box::new_uninit(); | |
let p_outer = logger.as_mut_ptr(); | |
logger.write(Self { | |
prefix: "test: ", | |
// SAFETY: what safety? to actually be safe this would have | |
// to be able to remove itself from the Logger, which it does | |
// not do yet. | |
backend: unsafe { LogRegistration::new::<Self>(p_outer) }, | |
_pin: PhantomPinned, | |
}); | |
unsafe { logger.assume_init() } | |
} | |
pub fn add_to<'a>(&'a self, l: &mut Logger<'a>) { | |
l.add_callback(&self.backend); | |
} | |
} | |
// ---------------------------------------- | |
fn main() { | |
let mut l = Logger::default(); | |
let backend = PrintlnLogBackend::new(); | |
backend.add_to(&mut l); | |
l.log("hello world"); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment