1 unstable release
0.1.2 | Oct 22, 2022 |
---|---|
0.1.1 |
|
0.1.0 |
|
#1304 in Rust patterns
17KB
Usage
Add the following to your Cargo.toml
:
[dependencies]
printc = "0.1"
Example
#[macro_use] extern crate printc;
fn main() {
let x = 500;
printc!(x);
}
Output:
x = 500
Problem statement
Why choose between messy input (println!
) and messy output (dbg!
)? printc!
allows you to debug code with clean input and clean output. Simultaneously.
Motivation, use-cases
Short motivation:
If you want to debug, you're normally forced to choose between writing long boilerplate (println!
) and having messy output that requires more effort to visually navigate (dbg!
). The ability to produce clean output from clean input allows you to analyze your code more easily and learn faster.
Long motivation:
If we want to debug code, the Standard Library offers the following options:
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 where print!
and eprint!
would be preferable). Thus, print!
and eprint!
are suboptimal choices for producing clean output with minimal boilerplate.
println!
and eprintln!
produce clean output, but they require you to write long boilerplate in the input.
dbg!
takes clean input, but the output is messy and takes more effort to visually navigate.
For instance, you may want to copy code examples from documentation and forums (like github) and paste them into your local environment (or Rust Playground) to play around with it, to better understand what the code actually does. We will demonstrate how the messy input of println!
and messy output of dbg!
looks like, and compare it with printc!
.
Let's suppose that you are unfamiliar with Pinning and you read a documentation page about it, and you encounter the code example below:
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;
}
#[allow(unused)]
fn a<'a>(self: Pin<&'a Self>) -> &'a str {
&self.get_ref().a
}
#[allow(unused)]
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) }
}
}
In an attempt to understand the code, you may try to play around with it for educational purposes. As discussed previously, println!
and dbg!
are the best options offered by the Standard Library. Let's modify the main()
function and see how it works out for each of the two approaches:
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,
}
printc!
approach
printc!
lets you do the same thing, but with clean input and clean output. Simultaneously.
Clean input:
fn main() {
let mut test1 = Test::new("test1");
printc!(test1);
let mut test1_pin = unsafe { Pin::new_unchecked(&mut test1) };
Test::init(test1_pin.as_mut());
drop(test1_pin);
printc!(test1);
let mut test2 = Test::new("test2");
printc!(test1, test2);
mem::swap(&mut test1, &mut test2);
printc!(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,
}