Skip to content

Instantly share code, notes, and snippets.

@bonzini
Last active November 27, 2024 09:06
Show Gist options
  • Save bonzini/2c63a905851f4f78573d022b1f196f4f to your computer and use it in GitHub Desktop.
Save bonzini/2c63a905851f4f78573d022b1f196f4f to your computer and use it in GitHub Desktop.
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