Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ACP: New Macro - debugging with clean input and clean output. Simultaneously. #125

Closed
amab8901 opened this issue Oct 21, 2022 · 4 comments
Closed
Labels
api-change-proposal A proposal to add or alter unstable APIs in the standard libraries T-libs-api

Comments

@amab8901
Copy link

Proposal

Problem statement

Why should the user have to choose between messy input and messy output? My proposed macro will enable the user to debug their code with clean input and clean output.

Motivation, use-cases

###Short motivation:
If you want to debug, you're currently forced to choose between writing long boilerplate and having messy output that requires more effort to visually navigate. It's important to be able to produce clean and clear output without having to write long boilerplate, especially for beginners and intermediate-level users (I can't speak for advanced users though, since I haven't reached that level myself).

###Long motivation:
If we want to debug code, we have the following options available:

  • dbg!
  • println!
  • print!
  • eprint!
  • eprintln!

As I understand it, print! and eprint! are rarely used because the user can instead choose println! and eprintln! to get the same thing but with clearer structure to more easily distinguish different outputs from each other (although there may be some use cases of print! and eprint!). Thus, print! and eprint! are suboptimal choices for producing clean & clear output with minimal boilerplate.

println! and eprintln! produce clean and clear output, but they require the user to write long boilerplate.

dbg! can be used very concisely and easily, with minimal boilerplate. But the output is messy and takes more effort to visually navigate.

Beginners and intermediate-level users (such as myself) may want to copy code examples from documentation and forums (like github) and paste them into their local environment (or Rust Playground) to play around with it, to better understand what the code actually does. Using println! and eprintln! is cumbersome and messy to write, while cfg! is cumbersome and messy to read its output.

I took the below code example from this page:

fn main() {
   let mut test1 = Test::new("test1");
   let mut test1_pin = unsafe { Pin::new_unchecked(&mut test1) };
   Test::init(test1_pin.as_mut());

   drop(test1_pin);
   println!(r#"test1.b points to "test1": {:?}..."#, test1.b);

   let mut test2 = Test::new("test2");
   mem::swap(&mut test1, &mut test2);
   println!("... and now it points nowhere: {:?}", test1.b);
}
use std::pin::Pin;
use std::marker::PhantomPinned;
use std::mem;

#[derive(Debug)]
struct Test {
    a: String,
    b: *const String,
    _marker: PhantomPinned,
}


impl Test {
    fn new(txt: &str) -> Self {
        Test {
            a: String::from(txt),
            b: std::ptr::null(),
            // This makes our type `!Unpin`
            _marker: PhantomPinned,
        }
    }

    fn init<'a>(self: Pin<&'a mut Self>) {
        let self_ptr: *const String = &self.a;
        let this = unsafe { self.get_unchecked_mut() };
        this.b = self_ptr;
    }

    fn a<'a>(self: Pin<&'a Self>) -> &'a str {
        &self.get_ref().a
    }

    fn b<'a>(self: Pin<&'a Self>) -> &'a String {
        assert!(!self.b.is_null(), "Test::b called without Test::init being called first");
        unsafe { &*(self.b) }
    }
}

I tried to play around with this code for educational purposes, using two different approaches: println! and dbg!. In both cases, I modified only the main function as follows:

println! approach

Messy input:

fn main() {
   let mut test1 = Test::new("test1");
   println!("test1 = {:#?}", test1);
   println!();
   
   let mut test1_pin = unsafe { Pin::new_unchecked(&mut test1) };
   Test::init(test1_pin.as_mut());
   
   drop(test1_pin);
   println!("test1 = {:#?}", test1);
   println!();

   let mut test2 = Test::new("test2");
   println!("test1 = {:#?}", test1);
   println!();
   println!("test2 = {:#?}", test2);
   println!();
   
   mem::swap(&mut test1, &mut test2);
   println!("test1 = {:#?}", test1);
   println!();
   println!("test2 = {:#?}", test2);
   println!();
}

Clean output:

test1 = Test {
    a: "test1",
    b: 0x0000000000000000,
    _marker: PhantomPinned,
}

test1 = Test {
    a: "test1",
    b: 0x00007ffd1bdafb68,
    _marker: PhantomPinned,
}

test1 = Test {
    a: "test1",
    b: 0x00007ffd1bdafb68,
    _marker: PhantomPinned,
}

test2 = Test {
    a: "test2",
    b: 0x0000000000000000,
    _marker: PhantomPinned,
}

test1 = Test {
    a: "test2",
    b: 0x0000000000000000,
    _marker: PhantomPinned,
}

test2 = Test {
    a: "test1",
    b: 0x00007ffd1bdafb68,
    _marker: PhantomPinned,
}

dbg! approach

Clean input:

fn main() {
   let mut test1 = Test::new("test1");
   dbg!(&test1);
   
   let mut test1_pin = unsafe { Pin::new_unchecked(&mut test1) };
   Test::init(test1_pin.as_mut());
   
   drop(test1_pin);
   dbg!(&test1);

   let mut test2 = Test::new("test2");
   dbg!(&test1, &test2);
   
   mem::swap(&mut test1, &mut test2);
   dbg!(&test1, &test2);
}

Messy output:

[src/main.rs:7] &test1 = Test {
    a: "test1",
    b: 0x0000000000000000,
    _marker: PhantomPinned,
}
[src/main.rs:13] &test1 = Test {
    a: "test1",
    b: 0x00007ffca10e7650,
    _marker: PhantomPinned,
}
[src/main.rs:16] &test1 = Test {
    a: "test1",
    b: 0x00007ffca10e7650,
    _marker: PhantomPinned,
}
[src/main.rs:16] &test2 = Test {
    a: "test2",
    b: 0x0000000000000000,
    _marker: PhantomPinned,
}
[src/main.rs:19] &test1 = Test {
    a: "test2",
    b: 0x0000000000000000,
    _marker: PhantomPinned,
}
[src/main.rs:19] &test2 = Test {
    a: "test1",
    b: 0x00007ffca10e7650,
    _marker: PhantomPinned,
}

Solution sketches

Create new macro by adding the following code (or some variation thereof) into library/std/src/macros.rs:

#[macro_export]
#[cfg_attr(not(test), rustc_diagnostic_item = "dbg_macro")]
#[unstable(feature = "dbg_macro")]
macro_rules! pdbg {
    () => {
        $crate::eprintln!()
    };
    ($val:expr $(,)?) => {
        // Use of `match` here is intentional because it affects the lifetimes
        // of temporaries - https://stackoverflow.com/a/48732525/1063961
        match $val {
            tmp => {
                $crate::eprintln!("{} = {:#?}\n",
                    $crate::stringify!($val), &tmp);
                tmp
            }
        }
    };
    ($($val:expr),+ $(,)?) => {
        ($($crate::pdbg!($val)),+,)
    };
}

If you use my solution, the input and output should both be clean, as follows:

Clean input:

fn main() {
   let mut test1 = Test::new("test1");
   pdbg!(&test1);
   
   let mut test1_pin = unsafe { Pin::new_unchecked(&mut test1) };
   Test::init(test1_pin.as_mut());
   
   drop(test1_pin);
   pdbg!(&test1);

   let mut test2 = Test::new("test2");
   pdbg!(&test1, &test2);
   
   mem::swap(&mut test1, &mut test2);
   pdbg!(&test1, &test2);
}

Clean output:

test1 = Test {
    a: "test1",
    b: 0x0000000000000000,
    _marker: PhantomPinned,
}

test1 = Test {
    a: "test1",
    b: 0x00007ffd1bdafb68,
    _marker: PhantomPinned,
}

test1 = Test {
    a: "test1",
    b: 0x00007ffd1bdafb68,
    _marker: PhantomPinned,
}

test2 = Test {
    a: "test2",
    b: 0x0000000000000000,
    _marker: PhantomPinned,
}

test1 = Test {
    a: "test2",
    b: 0x0000000000000000,
    _marker: PhantomPinned,
}

test2 = Test {
    a: "test1",
    b: 0x00007ffd1bdafb68,
    _marker: PhantomPinned,
}

Links and related work

This issue is what triggered me into writing this API Change Proposal. I'm probably not the only one who would benefit from this proposed macro.

What happens now?

This issue is part of the libs-api team API change proposal process. Once this issue is filed the libs-api team will review open proposals in its weekly meeting. You should receive feedback within a week or two.

@amab8901 amab8901 added api-change-proposal A proposal to add or alter unstable APIs in the standard libraries T-libs-api labels Oct 21, 2022
@amab8901 amab8901 changed the title (My API Change Proposal) New Macro: debugging with clean input and clean output. Simultaneously. Oct 21, 2022
@amab8901 amab8901 changed the title New Macro: debugging with clean input and clean output. Simultaneously. ACP: New Macro - debugging with clean input and clean output. Simultaneously. Oct 21, 2022
@scottmcm
Copy link
Member

The original dbg! RFC (rust-lang/rfcs#2173) included many knobs for tuning things like this.

However, that one was not accepted, and instead a simpler one was (rust-lang/rfcs#2361).

That suggests to me that having more options for this aren't desired, and the expectation is that either you use dbg! as it's good enough, or you make your own thing for your project if you'd rather different behaviour.

@amab8901
Copy link
Author

amab8901 commented Oct 22, 2022

The original dbg! RFC (rust-lang/rfcs#2173) included many knobs for tuning things like this.

However, that one was not accepted, and instead a simpler one was (rust-lang/rfcs#2361).

That suggests to me that having more options for this aren't desired, and the expectation is that either you use dbg! as it's good enough, or you make your own thing for your project if you'd rather different behaviour.

What about if we use println! as a starting point? Wouldn't it be nice to use println! by writing println!(x) instead of println!("x = {}", x)?

@the8472
Copy link
Member

the8472 commented Oct 22, 2022

Note that implicit arguments were added a while ago to println!

@scottmcm
Copy link
Member

Wouldn't it be nice to use println! by writing println!(x) instead of println!("x = {}", x)?

No, actually. Because the most common way that would be used is as println!(line); by people who actually want println!("{}", line);, not println!("line = {}", line);. Thus having it fail to compile with a nice error message is better.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
api-change-proposal A proposal to add or alter unstable APIs in the standard libraries T-libs-api
Projects
None yet
Development

No branches or pull requests

3 participants