1 unstable release

0.1.0 Sep 7, 2023

#2487 in Rust patterns

MIT license

16KB
153 lines

Rust library for diplaying Options.

Usage

use show_option::prelude::*;

println!("received bytes: {}", None::<usize>.show_or("none")); // "received bytes: none"
println!("amount: {}", Some(20).show_prefixed_or("$", "-")); // "amount: $20"
println!("amount: {}", format_option!(Some(20), "${}", "-")); // "amount: $20"

lib.rs:

Extension methods and convenient macro for formatting Options without unnecessary code duplication and allocations.

Usage

// Import all
use show_option::prelude::*;

// OR, if you prefer, import explicitly only what you need:
use show_option::ShowOption as _; // Extension methods for `Option`.
use show_option::format_option; // Macro

// Display 'Option's
println!("received bytes: {}", None::<usize>.show_or("none")); // "received bytes: none"
println!("amount: {}", Some(20).show_prefixed_or("$", "-")); // "amount: $20"
println!("amount: {}", format_option!(Some(20), "${}", "-")); // "amount: $20"

See full list of methods in ShowOption documentation. Also see format_option macro.

If there are many places you want to apply same formatting, consider creating function:

fn format_amount(amount: &Option<usize>) -> impl Display + '_ {
    amount.show_prefixed_or("$", "-")
}
println!("amount: {}", format_amount(&Some(20))); // prints "amount: $20"

Sometimes even better solution is to create new wrapper type with own Display implementation.

Motivation

Option type doesn't implement Display intentionally, because it's formatting is context-dependant. Should None be formatted as empty string, "none", or "missing"?

One way to handle it is conditional formatting:

let bytes = Some(20);
match bytes {
    Some(value) => println!("bytes received: {}", value),
    None => println!("bytes received: none"),
}

it is very flexible, but it leads to format string duplication.

Another way is to convert optional value to it's representation before formatting. It works in simple cases, when default value has the same type as inner value:

let bytes = Some(20);
println!("bytes received: {}", bytes.unwrap_or(0));

But when types are different, it leads to verbose code and unnecessary string allocations:

let bytes = Some(20);
println!("bytes received: {}", bytes.map_or("none".to_string(), |value| value.to_string()));
//                                                 ↑ allocation               ↑ allocation

It is possible to avoid allocations by casting both branches to Display trait reference, but it's very verbose:

use std::fmt::Display;

let bytes = Some(20);
println!("bytes received: {}", bytes.as_ref().map_or(&"none" as &dyn Display, |value| value as &dyn Display));

This crate basically wraps last solution into nice methods.

Dependencies

~5KB