yaru 0.2.3

Yet Another Rust Util - A collection of utilities for timing, memory inspection and SQL result formatting.
Documentation

Yet Another Rust Util (yaru) ๐Ÿ› ๏ธ

Crates.io Documentation License

A lightweight collection of utilities for Rust applications, providing:

  • โฑ๏ธ Relative Timestamping: Track elapsed time since application start
  • ๐Ÿงต Thread Identification: Debug multi-threaded and async code with thread IDs
  • ๐Ÿ” Memory Inspection: Visualize memory layout of Rust types (addresses, lengths, capacities, reference counts, lock states)
  • ๐Ÿ—„๏ธ SQL Result Formatting: Pretty-print any Serialize query result as a UTF-8 table

yaru is designed for developers and educators who need to understand timing, concurrency, memory models, and database results without heavy frameworks.


๐Ÿ“ฆ Installation

Add this to your Cargo.toml:

[dependencies]

yaru = "0.2"


โฑ๏ธ Time Logging

Print vs Format

Each logging variant has two versions:

  • Print (t_print!, nt_print!, ti_print!, nti_print!): Output directly to stdout
  • Format (t_format!, nt_format!, ti_format!, nti_format!): Return formatted String
Variant Thread ID Leading Newline Use Case
t* No No Standard timestamped logging
nt* No Yes Visual phase separation
ti* Yes No Multi-threaded/async debugging
nti* Yes Yes Thread ID + phase separation

Example

use yaru::*;

fn main() {
    nti_print!("==> Starting main");
    
    std::thread::spawn(|| {
        ti_print!("Worker thread running...");
    }).join().unwrap();
    
    t_print!("All done.");
    
    // Using format variants
    let msg = t_format!("Formatted at runtime");
    println!("{}", msg);
}

Output:


[01][00:000] ==> Starting main
[02][00:001] Worker thread running...
[00:002] All done.
[00:003] Formatted at runtime

Functions and Macros Available

Each variant has both function and macro versions, in both print and format styles:

Print to stdout:

  • t_print(msg) / t_print!(...)
  • nt_print(msg) / nt_print!(...)
  • ti_print(msg) / ti_print!(...)
  • nti_print(msg) / nti_print!(...)

Return String:

  • t_format(msg) / t_format!(...)
  • nt_format(msg) / nt_format!(...)
  • ti_format(msg) / ti_format!(...)
  • nti_format(msg) / nti_format!(...)

Use init_time_log() to manually reset the timer origin.


๐Ÿ” Memory Inspection

Visualize memory layout of Rust types using macros or functions.

Print vs Format

Operation Output to stdout Returns String
Macro print_*_ptr! format_*_ptr!
Function print_*_ptr() format_*_ptr()

Supported Types

Type Print Macro Format Macro Print Function Format Function
Universal print_ptr! format_ptr! โ€” โ€”
String print_string_ptr! format_string_ptr! print_string_ptr() format_string_ptr()
&str print_str_ptr! format_str_ptr! print_str_ptr() format_str_ptr()
&[T] print_slice_ptr! format_slice_ptr! print_slice_ptr() format_slice_ptr()
Box<T> print_box_ptr! format_box_ptr! print_box_ptr() format_box_ptr()
Vec<T> print_vec_ptr! format_vec_ptr! print_vec_ptr() format_vec_ptr()
Rc<T> print_rc_ptr! format_rc_ptr! print_rc_ptr() format_rc_ptr()
Arc<T> print_arc_ptr! format_arc_ptr! print_arc_ptr() format_arc_ptr()
&T print_ref_ptr! format_ref_ptr! print_ref_ptr() format_ref_ptr()
Mutex<T> print_mutex_ptr! format_mutex_ptr! print_mutex_ptr() format_mutex_ptr()
RwLock<T> print_rwlock_ptr! format_rwlock_ptr! print_rwlock_ptr() format_rwlock_ptr()
HashMap print_hashmap_ptr! format_hashmap_ptr! print_hashmap_ptr() format_hashmap_ptr()

โšก Super-Powered Macros (Time + Inspect)

You can combine Time Logging and Memory Inspection in a single macro call!

Simply prefix any inspection macro with t_, nt_, ti_, or nti_.

Syntax: [prefix]_[macro]!

Prefix Effect Example
t_ Timestamped inspection t_print_vec_ptr!(v)
nt_ Newline + Timestamped inspection nt_print_vec_ptr!(v)
ti_ Thread ID + Timestamped inspection ti_print_vec_ptr!(v)
nti_ Newline + Thread ID + Timestamped inspection nti_print_vec_ptr!(v)

This works for ALL inspection macros:

  • t_print_ptr!
  • nti_print_string_ptr!
  • ti_print_rc_ptr!
  • ...and so on.

Basic Example

use yaru::*;

fn main() {
    let s = String::from("hello");
    print_ptr!(s);                    // Uses "s" as label
    print_ptr!(s, "my_string");       // Uses custom label
    
    let info = format_ptr!(s);        // Returns String
    println!("{}", info);
}

Output:

s String::{ addr: 0x7ff..., len: 5, cap: 5 }
my_string String::{ addr: 0x7ff..., len: 5, cap: 5 }
s String::{ addr: 0x7ff..., len: 5, cap: 5 }

Smart Pointer Example

use yaru::*;
use std::sync::Arc;

fn main() {
    let arc = Arc::new(42);
    let arc2 = Arc::clone(&arc);
    
    print_arc_ptr!(arc);
    print_arc_ptr!(arc2);
}

Output:

arc Arc::{ addr: 0x5fa..., strong_count: 2, weak_count: 0 }
arc2 Arc::{ addr: 0x5fa..., strong_count: 2, weak_count: 0 }

Lock State Example

use yaru::*;
use std::sync::Mutex;

fn main() {
    let m = Mutex::new(100);
    print_mutex_ptr!(m);           // state: unlocked
    
    let _guard = m.lock().unwrap();
    print_mutex_ptr!(m);           // state: locked/poisoned
}

๐Ÿ—„๏ธ SQL Result Formatting

Pretty-print any &[T] (where T: Serialize) as a UTF-8 table. Works with sqlx, SeaORM, Diesel, or any struct that derives Serialize.

Three macros are available โ€” all automatically capture the variable name via stringify!, so there is no manual name parameter:

Macro Output
print_sql_table! Pretty UTF-8 table only
print_sql_json_table! Debug dump + table
print_sql_json! Debug dump only

print_sql_table! โ€” Table Only

use serde::Serialize;

#[derive(Serialize)]
struct User { id: i32, name: String, email: String }

let users = vec![
    User { id: 1, name: "Alice".into(), email: "alice@example.com".into() },
    User { id: 2, name: "Bob".into(),   email: "bob@example.com".into() },
];
yaru::print_sql_table!(users);

Output:

=> TABLE: users (2)
โ”Œโ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ id โ”† name  โ”† email             โ”‚
โ•žโ•โ•โ•โ•โ•ชโ•โ•โ•โ•โ•โ•โ•โ•ชโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ก
โ”‚ 1  โ”† Alice โ”† alice@example.com โ”‚
โ”œโ•Œโ•Œโ•Œโ•Œโ”ผโ•Œโ•Œโ•Œโ•Œโ•Œโ•Œโ•Œโ”ผโ•Œโ•Œโ•Œโ•Œโ•Œโ•Œโ•Œโ•Œโ•Œโ•Œโ•Œโ•Œโ•Œโ•Œโ•Œโ•Œโ•Œโ•Œโ•Œโ”ค
โ”‚ 2  โ”† Bob   โ”† bob@example.com   โ”‚
โ””โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

print_sql_json_table! โ€” Debug + Table

The most complete debug view: raw Debug representation and a clean table.

use serde::Serialize;

#[derive(Debug, Serialize)]
struct Task { id: i32, title: String, done: bool }

let tasks = vec![
    Task { id: 1, title: "Write tests".into(), done: true },
    Task { id: 2, title: "Review code".into(), done: false },
];
yaru::print_sql_json_table!(tasks);

Output:

=> JSON: tasks (2)
[
    Task { id: 1, title: "Write tests", done: true },
    Task { id: 2, title: "Review code", done: false },
]

=> TABLE: tasks (2)
โ”Œโ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ id โ”† title       โ”† done โ”‚
โ•žโ•โ•โ•โ•โ•ชโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ชโ•โ•โ•โ•โ•โ•โ•ก
โ”‚ 1  โ”† Write tests โ”† T    โ”‚
โ”œโ•Œโ•Œโ•Œโ•Œโ”ผโ•Œโ•Œโ•Œโ•Œโ•Œโ•Œโ•Œโ•Œโ•Œโ•Œโ•Œโ•Œโ•Œโ”ผโ•Œโ•Œโ•Œโ•Œโ•Œโ•Œโ”ค
โ”‚ 2  โ”† Review code โ”† F    โ”‚
โ””โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”˜

print_sql_json! โ€” Debug Only

use serde::Serialize;

#[derive(Debug, Serialize)]
struct Task { id: i32, title: String, done: bool }

let tasks = vec![
    Task { id: 1, title: "Write tests".into(), done: true },
    Task { id: 2, title: "Review code".into(), done: false },
];
yaru::print_sql_json!(tasks);

Output:

=> JSON: tasks (2)
[
    Task { id: 1, title: "Write tests", done: true },
    Task { id: 2, title: "Review code", done: false },
]

NULL Handling โ€” Option Fields

Ideal for LEFT JOIN results where some columns may be NULL:

use serde::Serialize;

#[derive(Serialize)]
struct UserProfile {
    user_id: i32,
    user_name: String,
    bio: Option<String>,
    avatar_url: Option<String>,
}

let results = vec![
    UserProfile { user_id: 1, user_name: "Alice".into(),
                  bio: Some("Hello!".into()), avatar_url: None },
    UserProfile { user_id: 2, user_name: "Bob".into(),
                  bio: None, avatar_url: None },
];
yaru::print_sql_table!(results);

Output:

=> TABLE: results (2)
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ user_id โ”† user_name โ”† bio    โ”† avatar_url โ”‚
โ•žโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ชโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ชโ•โ•โ•โ•โ•โ•โ•โ•โ•ชโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ก
โ”‚ 1       โ”† Alice     โ”† Hello! โ”† NULL       โ”‚
โ”œโ•Œโ•Œโ•Œโ•Œโ•Œโ•Œโ•Œโ•Œโ•Œโ”ผโ•Œโ•Œโ•Œโ•Œโ•Œโ•Œโ•Œโ•Œโ•Œโ•Œโ•Œโ”ผโ•Œโ•Œโ•Œโ•Œโ•Œโ•Œโ•Œโ•Œโ”ผโ•Œโ•Œโ•Œโ•Œโ•Œโ•Œโ•Œโ•Œโ•Œโ•Œโ•Œโ•Œโ”ค
โ”‚ 2       โ”† Bob       โ”† NULL   โ”† NULL       โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

1:N Relations โ€” Tuples with Nested Vec

Works with SeaORM's find_with_related() results (Vec<(Parent, Vec<Child>)>):

// SeaORM example
let result: Vec<(User, Vec<Task>)> = user::Entity::find()
    .find_with_related(task::Entity)
    .all(&db).await?;

yaru::print_sql_json_table!(result);
// Parent fields are flattened + children summarised in extra columns

Smart Value Formatting

Rust type Rendered as
String / &str Plain text
bool T / F
Option::None NULL
Option::Some(v) v
Vec<T> (of objects) [name1, name2, ...] (extracts name or title field)
Other JSON representation

โš ๏ธ Important: Macro Usage Best Practices

Macros Automatically Borrow - Don't Use &!

All pointer inspection macros internally add & to the value. Pass values directly, without &.

This is one of the most important rules when using this library's macros:

use yaru::*;

let v = vec![1, 2, 3];

// โœ… CORRECT: Macro handles borrowing
print_ptr!(v);           // Shows address of v
print_vec_ptr!(v);       // Shows address of v's heap buffer
print_vec_ptr!(v, "my_vec");

// โŒ WRONG: Double reference, shows TEMPORARY reference address
print_ptr!(&v);          // Creates &&v internally โ†’ shows temporary!
print_ref_ptr!(&v);      // Shows address of TEMPORARY &v, not v!

Why This Matters

When you write print_ptr!(&v), the macro adds another &, creating &&v. This double reference points to a temporary location on the stack, not the actual data you're trying to inspect. The output will be misleading and won't help you understand your program's memory layout.

The Simple Rule: Pass the value directly to macros, without &. The macro handles borrowing for you.

Detailed Examples by Type

โœ… Vectors - Correct Usage

let v = vec![1, 2, 3];
print_vec_ptr!(v);        // โœ… Shows v's heap buffer address
print_ptr!(v);            // โœ… Shows v's heap buffer address

โŒ Vectors - Incorrect Usage

let v = vec![1, 2, 3];
print_vec_ptr!(&v);       // โŒ Shows temporary reference address
print_ptr!(&v);           // โŒ Shows temporary reference address

โœ… Box - Correct Usage

let b = Box::new(42);
print_box_ptr!(b);        // โœ… Shows heap allocation address
print_ptr!(b);            // โœ… Shows heap allocation address

โŒ Box - Incorrect Usage

let b = Box::new(42);
print_box_ptr!(&b);       // โŒ Shows temporary reference address

โœ… References - Correct Usage

let x = 42;
print_ptr!(x);            // โœ… Shows address of x on stack
// If you need to inspect a reference itself:
let r = &x;
print_ref_ptr!(r);        // โœ… Shows where x lives (r is already a reference)

โŒ References - Incorrect Usage

let x = 42;
print_ptr!(&x);           // โŒ Shows temporary reference, not x's address!

Functions Require Explicit Reference

When using functions directly (not macros), you must provide the reference:

use yaru::*;

let v = vec![1, 2, 3];

// Function signature: print_vec_ptr(&Vec<T>, prefix: &str)
print_vec_ptr(&v, "v");         // โœ… Explicit & required

// Macro signature: print_vec_ptr!(value) or print_vec_ptr!(value, prefix)
print_vec_ptr!(v);              // โœ… No & needed, macro handles it
print_vec_ptr!(v, "v");         // โœ… No & needed, macro handles it

Summary Table

You want to... Use this Note
Print with auto-label print_vec_ptr!(v) No & needed
Print with custom label (macro) print_vec_ptr!(v, "label") No & needed
Print with custom label (fn) print_vec_ptr(&v, "label") & required for fn
Get formatted String (macro) format_vec_ptr!(v) No & needed
Get formatted String (fn) format_vec_ptr(&v, "label") & required for fn

Quick Reference

Macros (most common):

  • โœ… print_ptr!(value)
  • โœ… print_vec_ptr!(value)
  • โœ… print_box_ptr!(value)
  • โœ… print_rc_ptr!(value)
  • โŒ print_ptr!(&value) โ† DON'T DO THIS

Functions (when needed):

  • โœ… print_vec_ptr(&value, "label")
  • โœ… format_vec_ptr(&value, "label")

๐ŸŽฏ Design Philosophy

  1. Lightweight Dependencies: Only serde, serde_json, and comfy-table.
  2. Educational Focus: Clear, verbose output for learning.
  3. Non-Invasive: All functions/macros only borrow data.
  4. Database-Agnostic: Works with any ORM/query library โ€” sqlx, SeaORM, Diesel, etc.
  5. Consistent API: print_* for output, format_* for strings.

๐Ÿ“š Full Documentation

See the API documentation on docs.rs for complete details.


โš–๏ธ License

Licensed under either of:

at your option.


Contributing

Contributions are welcome! Please feel free to submit a Pull Request.