From 86563114b25ef6b53c10ee732bcd41a719f46f19 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Wed, 21 May 2025 15:06:09 +0000 Subject: [PATCH] feat: Introduce lazy_format for owned formatting arguments Addresses the issue of needing an owned version of `std::fmt::Arguments` that can be stored or sent across threads. This change introduces the `lazy_format` crate as the recommended solution. Key changes: - Added `lazy_format = "2.0.3"` to Cargo.toml. - Included `examples/owned_format.rs` to demonstrate creating `'static` formatting objects that capture owned data (e.g., String, numbers) and can be sent across threads. - Added integration tests in `tests/owned_format_test.rs` to verify the behavior, including data ownership and cross-thread usage. - Updated `README.md` with a new section "Handling Owned Formatting Arguments" explaining the problem and solution with `lazy_format`, complete with an example. This provides a practical way to handle complex formatting scenarios where the lifetime of `std::fmt::Arguments` would be restrictive. --- Cargo.toml | 1 + README.md | 38 ++++++++++++++++++++++++ examples/owned_format.rs | 28 ++++++++++++++++++ tests/owned_format_test.rs | 60 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 127 insertions(+) create mode 100644 examples/owned_format.rs create mode 100644 tests/owned_format_test.rs diff --git a/Cargo.toml b/Cargo.toml index 6cbb58e..8428a85 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,3 +15,4 @@ edition = "2018" crossbeam-channel = "0.4.4" log = { version = "0.4.6", features = ["std"] } time = "0.1.35" +lazy_format = "2.0.3" diff --git a/README.md b/README.md index a133d65..8868f01 100644 --- a/README.md +++ b/README.md @@ -45,3 +45,41 @@ fn main() { ``` More examples can be found under `examples` directory. + +## Handling Owned Formatting Arguments + +The standard `std::fmt::Arguments` type in Rust is tied to the lifetime of the data being formatted. This makes it unsuitable for scenarios where the formatted arguments need to be stored or sent across threads (i.e., require a `'static` lifetime). + +For these situations, you can use the `lazy_format` crate. It allows you to create formatting objects that capture their arguments (by moving or cloning them). If all captured arguments are themselves owned and `'static`, the resulting `lazy_format` object can also be `'static`. + +**Example:** + +```rust +use lazy_format::lazy_format; +use std::fmt::Display; +use std::thread; + +fn create_owned_formatter(name: String, count: i32) -> Box { + Box::new(lazy_format!("User: {}, Count: {}", name, count)) +} + +fn main() { + let name = String::from("Alice"); + let formatter = create_owned_formatter(name, 42); + + let handle = thread::spawn(move || { + // This formatter can be sent to another thread + println!("{}", formatter); // Outputs: "User: Alice, Count: 42" + }); + handle.join().unwrap(); +} +``` + +First, add `lazy_format` to your `Cargo.toml`: + +```toml +[dependencies] +lazy_format = "2.0.3" # Use the latest version +``` + +This approach provides a flexible way to handle formatting needs where lifetimes would otherwise be an issue. diff --git a/examples/owned_format.rs b/examples/owned_format.rs new file mode 100644 index 0000000..88cc2b8 --- /dev/null +++ b/examples/owned_format.rs @@ -0,0 +1,28 @@ +use lazy_format::lazy_format; +use std::fmt::Display; +use std::thread; + +fn main() { + let owned_string = String::from("world"); + let number = 42; + + // Create a lazy_format object that captures owned data. + // This object itself should be Send + 'static. + let formatter: Box = + Box::new(lazy_format!("Hello, {}! The number is {}.", owned_string, number)); + + // Demonstrate that it can be sent to another thread. + let handle = thread::spawn(move || { + // Format the object in the other thread. + let formatted_string = formatter.to_string(); + println!("From thread: {}", formatted_string); + formatted_string + }); + + let result_from_thread = handle.join().expect("Thread panicked"); + + println!("In main thread, got back: {}", result_from_thread); + + assert_eq!(result_from_thread, "Hello, world! The number is 42."); + println!("Successfully created and used an owned formatter across threads!"); +} diff --git a/tests/owned_format_test.rs b/tests/owned_format_test.rs new file mode 100644 index 0000000..ec164fe --- /dev/null +++ b/tests/owned_format_test.rs @@ -0,0 +1,60 @@ +use lazy_format::lazy_format; +use std::fmt::Display; +use std::thread; + +#[test] +fn test_owned_format_across_threads() { + let owned_string = String::from("Rustacean"); + let value = 100; + + // Create a lazy_format object capturing owned data. + // Ensure it's Send + 'static by boxing it. + let formatter: Box = + Box::new(lazy_format!("Greetings, {}! Your score is {}.", owned_string, value)); + + // Move to another thread and format. + let handle = thread::spawn(move || { + formatter.to_string() + }); + + let result_from_thread = handle.join().expect("Thread panicked during test"); + + assert_eq!(result_from_thread, "Greetings, Rustacean! Your score is 100."); +} + +#[test] +fn test_owned_format_static_lifetime() { + // This test demonstrates that if all inputs are 'static, the formatter can be 'static. + fn create_static_formatter() -> Box { + Box::new(lazy_format!("This is a static message from {}.", "static data")) + } + + let formatter = create_static_formatter(); + + // Move to another thread + let handle = thread::spawn(move || { + formatter.to_string() + }); + + let result = handle.join().unwrap(); + assert_eq!(result, "This is a static message from static data."); +} + +#[test] +fn test_format_with_owned_moved_value() { + let my_data = String::from("owned and moved"); + + // my_data is moved into the formatter + let formatter: Box = + Box::new(lazy_format!("Data: {}", my_data)); + + // If my_data was not moved, this would be a compile error: + // drop(my_data); // uncommenting this should cause a compile error if lazy_format doesn't take ownership + + let handle = thread::spawn(move || { + formatter.to_string() + }); + + let result = handle.join().unwrap(); + assert_eq!(result, "Data: owned and moved"); +}