hello all.
I am working on a proof of concept "step wise debugging" attribute macro dbgify for fun, just trying to get some basic functionality so far. Today I ran into a interesting problem, I would love some feed back on my solution to this and thoughts on how dumb this macro idea is??
First a bit of context. I have a trait that uses specialization to determine if the type impl's Debug. If so, the type is cast from "T" to "T: Debug" and printed accordingly. I'm using syn in some hacky ways to mimic a cousin of reflection, so the types can be dynamic, which is hard to show in the example.
This is a general example playground, this dbgify/dbg-collect link is the file where its used if any one is interested
// trait checks if Debug trait is present (same for Display)
trait Dbg {
fn is_debug(&self) -> bool;
}
impl<T> Dbg for T {
default fn is_debug(&self) -> bool {
false
}
}
impl<T: Debug> Dbg for T {
fn is_debug(&self) -> bool {
true
}
}
fn cast_dbg<T, D: Debug + 'static>(t: &T) -> &D {
unsafe { std::mem::transmute(t) }
}
fn dbg_if<T: Debug + 'static>(s: &dyn Any) {
if let Some(t) = s.downcast_ref::<T>() {
println!("Debug {:?}", t);
} else {
println!("Not Debug");
}
}
fn main() {
let v = vec![0,1];
if Dbg::is_debug(&v) {
// these types would be added dynamically by syn's quote! macro
let d = cast_dbg::<Vec<usize>, Vec<usize>>(&v);
print_if::<Vec<usize>>(d);
}
}
Thanks in advanced, any feed back is appreciated good or constructively bad.
mark cast_dbg as unsafe, since there are inputs with which one can trigger UB;
ensure that your proc_macro_attribute uses it correctly (it is fine for a proc-macro to generate code with an unsafe block in it, as long as the generated code is sound. It is important, however, that the documentation of you proc-macro do inform the users about it).
The second point is harder to spot, since proc-macros cannot be tested within the playground
On the other hand, dbg_if is fine, so you should keep exploring that road
Here is an idea for you to chew on: define the Dbg trait as follows:
This has the advantage of not requiring to guess the type parameter to feed to dbg_if; the dyn Dbg trait object type knows how to do it on its own;
This has the "drawback" of requiring dyn Dbg instead of dyn Any, although any sized type can be coerced to dyn Dbg (wherever Any was used, Dbg can now be used instead; just add a Any super trait bound on Dbg: trait Dbg : Any { ... })
Thanks for the input! it made my day to have a helpful and not demeaning answer
That type signature is so elegant I tried to clean mine up for an hour but I'm not sure if i can. I think now that there is a bit more context I can maybe get away with showing the actual code?
pub struct Debugable<T>(T);
impl<T> Debugable<T> {
unsafe fn cast<D: std::fmt::Debug + 'static>(t: &T) -> &D {
std::mem::transmute(t)
}
pub fn print<D: std::fmt::Debug + 'static>(d: T) {
// do term formating/color stuff
println!("{:?}", unsafe { Self::cast::<D>(&d) })
}
}
// this type signature is terrible, but I need to accept any T so the macro expanded code
// will "never" crash because of type errors before starting the debugging
pub fn show_fmt<T, D: std::fmt::Debug + 'static, P: std::fmt::Display + 'static>(t: &T) {
// is_display and is_debug return enum
match (DisplayCheck::is_display(t), DebugCheck::is_debug(t)) {
(Output::Display, Output::Debug) => Debugable::print::<D>(t),
(Output::Display, Output::Not) => Displayable::print::<P>(t),
(Output::Not, Output::Debug) => Debugable::print::<D>(t),
(Output::Not, Output::Not) => {
println!("'{}' does not impl Debug or Display", stringify!(t))
}
err => {
println!("{:?}", err);
panic!("show fmt failed BUG")
}
}
}
...
//then i have to call it like so again not great, (the # is because its in a quote! block)
show_fmt::<_, #var_type, #var_type>(&#capt_clone)
My only question then, is that a reasonably safe way to use unsafe, I check if Debug or Display is implemented for that type before calling the unsafe bit?