2 releases

0.1.1 Dec 27, 2024
0.1.0 Dec 26, 2024

#1307 in Rust patterns

Download history 70/week @ 2024-12-20 171/week @ 2024-12-27

241 downloads per month

GPL-3.0 license

22KB
481 lines

ty(pe)f(ormat)ling

easier implementations of the formatting traits, supporting structs, enums

# use {::tyfling::{display, lower_hex}, ::core::fmt::{Display, LowerHex}};
#[display("foo: {f0}")]
#[lower_hex("foo: {f0:x}")]
struct Foo<#[display] #[lower_hex] T>(T);

println!("{}", Foo(4)); // foo: 4
println!("{:x}", Foo(0x57e11a)); // foo: 57e11a

provides

9 attribute proc macros

  • binary, implementing the Binary trait
  • debug, implementing the Debug trait
  • display, implementing the Display trait
  • lower_exp, implementing the LowerExp trait
  • lower_hex, implementing the LowerHex trait
  • octal, implementing the Octal trait
  • pointer, implementing the Pointer trait
  • upper_exp, implementing the UpperExp trait
  • upper_hex, implementing the UpperHex trait

usage

struct

attach the attribute to the struct, and pass in formatting info

enum

attach the attribute to the enum and each variant, the enum's attribute should be empty, while the variants should have their individual formatting info

syntax

display is used as an example, but the functionality is identical for all of the attributes

  • #[display("formatting string")] formats without any variation, just a plain format string
  • #[display("fmt string that references {f0} tuple fields")] for tuple structs and tuple enum variants, their fields can be accessed as the f{n} local variables (eg: f0, f1, etc)
  • #[display("fmt string that references {n} struct fields")] for named field structs and named field enum variants, their fields are available verbatim
  • #[display("fmt string doing {} calculations {how_often}", how_much = if 42 == ~42 { "a few" } else { "at least 1" }, how_often = "uhh, once?")] format strings can have arguments like normal format strings
  • #[display(.1)] delegate to a tuple field of the [struct,enum variant] (equivalent to "{1}" (or whatever format specifier is used for a given trait) but more readable)
  • #[display(.some_field)] delegate to a named field of the [struct,enum variant] (equivalent to "{some_field}")

if any of the format strings for a type formats a generic parameter that isnt already constrained to the given trait, apply the attribute to the generic type to constrain just the impl:

# use {::tyfling::display, ::core::fmt::Display};
// ok, because the parameter is constrained to Display
#[display("foo: {f0}")]
struct Foo<T: Display>(T);

// ok, because the parameter is formatted
#[display("bar: ignoring the field")]
struct Bar<T>(T);
# use {::tyfling::display, ::core::fmt::Display};
// NOT ok, because there's no bound/attribute, and the parameter is used
#[display("bar: {f0}")]
struct Baz<T>(T);
# use {::tyfling::display, ::core::fmt::Display};
// ok, because attribute constrains the impl
// does not constrain the struct, only the impl
#[display("qux: {f0}")]
struct Qux<#[display] T>(T);

example

# use ::tyfling::display;

// set a format string and implement Display
#[display("diagnostic (from {source_file_name}): {kind}")]
struct Diagnostic</* since the format string uses a generic type, ensure that the impl is constrained to T: Display */ #[display] O> {
	kind: DiagnosticKind<O>,
	source_file_name: &'static str,
}

#[display]
enum DiagnosticKind<#[display] O> {
	#[display("forgot macro bang (at bytes {} to {}): {code}", span.start, span.end)]
	ForgotMacro {
		code: &'static str,
		span: ::core::ops::Range<usize>,
	},
	// no additional context to add, delegate to the indexed field
	#[display(.0)]
	Message(String),
	// unknown context, just delegate to the error fields
	#[display(.error)]
	OtherError {
		error: O,
		fatal: bool,
	},
}

#[display("displayable!")]
struct Displayable;

# fn main() {
assert_eq!(format!("{}", Diagnostic::<Displayable> {
	kind: DiagnosticKind::ForgotMacro {
		code: "fn main() { println(\"hello world!\"); }",
		span: 12..20,
	},
	source_file_name: "src/lib.rs",
}), "diagnostic (from src/lib.rs): forgot macro bang (at bytes 12 to 20): fn main() { println(\"hello world!\"); }");
assert_eq!(format!("{}", Diagnostic::<Displayable> {
	kind: DiagnosticKind::OtherError {
		error: Displayable,
		fatal: false,
	},
	source_file_name: "src/lib.rs",
}), "diagnostic (from src/lib.rs): displayable!");
# }
# use ::tyfling::display;
# 
# // set a format string and implement Display
# #[display("diagnostic (from {source_file_name}): {kind}")]
# struct Diagnostic</* since the format string uses a generic type, ensure that the impl is constrained to T: Display */ #[display] O> {
# 	kind: DiagnosticKind<O>,
# 	source_file_name: &'static str,
# }
# 
# #[display]
# enum DiagnosticKind<#[display] O> {
# 	#[display("forgot macro bang (at bytes {} to {}): {code}", span.start, span.end)]
# 	ForgotMacro {
# 		code: &'static str,
# 		span: ::core::ops::Range<usize>,
# 	},
# 	// no additional context to add, delegate to the indexed field
# 	#[display(.0)]
# 	Message(String),
# 	// unknown context, just delegate to the error fields
# 	#[display(.error)]
# 	OtherError {
# 		error: O,
# 		fatal: bool,
# 	},
# }
struct NotDisplayable;

# fn main() {
//! compile error, cannot Display, because NotDisplayable is not displayable
assert_eq!(format!("{}", Diagnostic::<NotDisplayable> {
	kind: DiagnosticKind::OtherError {
		error: NotDisplayable,
		fatal: true,
	},
	source_file_name: "src/lib.rs",
}), "");
# }

Dependencies

~215–660KB
~16K SLoC