Skip to content

Commit 7cd2625

Browse files
authored
Merge pull request #369 from ede1998/format-macro
add format macro implementation
2 parents ee3a308 + 0602c66 commit 7cd2625

File tree

3 files changed

+116
-1
lines changed

3 files changed

+116
-1
lines changed

CHANGELOG.md

+4
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
77

88
## [Unreleased]
99

10+
### Added
11+
12+
- Added `format` macro.
13+
1014
### Changed
1115

1216
- Changed `stable_deref_trait` to a platform-dependent dependency.

src/lib.rs

+7
Original file line numberDiff line numberDiff line change
@@ -134,3 +134,10 @@ pub mod spsc;
134134
mod ufmt;
135135

136136
mod sealed;
137+
138+
/// Implementation details for macros.
139+
/// Do not use. Used for macros only. Not covered by semver guarantees.
140+
#[doc(hidden)]
141+
pub mod _export {
142+
pub use crate::string::format;
143+
}

src/string.rs

+105-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1+
//! A fixed capacity [`String`](https://doc.rust-lang.org/std/string/struct.String.html)
2+
13
use core::{
24
cmp::Ordering,
35
fmt,
4-
fmt::Write,
6+
fmt::{Arguments, Write},
57
hash, iter, ops,
68
str::{self, Utf8Error},
79
};
@@ -570,6 +572,80 @@ impl<const N: usize> Ord for String<N> {
570572
}
571573
}
572574

575+
/// Equivalent to [`format`](https://doc.rust-lang.org/std/fmt/fn.format.html).
576+
///
577+
/// Please note that using [`format!`] might be preferable.
578+
///
579+
/// # Errors
580+
///
581+
/// There are two possible error cases. Both return the unit type [`core::fmt::Error`].
582+
///
583+
/// - In case the formatting exceeds the string's capacity. This error does not exist in
584+
/// the standard library as the string would just grow.
585+
/// - If a formatting trait implementation returns an error. The standard library panics
586+
/// in this case.
587+
///
588+
/// [`format!`]: crate::format!
589+
pub fn format<const N: usize>(args: Arguments<'_>) -> Result<String<N>, fmt::Error> {
590+
fn format_inner<const N: usize>(args: Arguments<'_>) -> Result<String<N>, fmt::Error> {
591+
let mut output = String::new();
592+
output.write_fmt(args)?;
593+
Ok(output)
594+
}
595+
596+
args.as_str().map_or_else(
597+
|| format_inner(args),
598+
|s| s.try_into().map_err(|_| fmt::Error),
599+
)
600+
}
601+
602+
/// Macro that creates a fixed capacity [`String`]. Equivalent to [`format!`](https://doc.rust-lang.org/std/macro.format.html).
603+
///
604+
/// The macro's arguments work in the same way as the regular macro.
605+
///
606+
/// It is possible to explicitly specify the capacity of the returned string as the first argument.
607+
/// In this case it is necessary to disambiguate by separating the capacity with a semicolon.
608+
///
609+
/// # Errors
610+
///
611+
/// There are two possible error cases. Both return the unit type [`core::fmt::Error`].
612+
///
613+
/// - In case the formatting exceeds the string's capacity. This error does not exist in
614+
/// the standard library as the string would just grow.
615+
/// - If a formatting trait implementation returns an error. The standard library panics
616+
/// in this case.
617+
///
618+
/// # Examples
619+
///
620+
/// ```
621+
/// # fn main() -> Result<(), core::fmt::Error> {
622+
/// use heapless::{format, String};
623+
///
624+
/// // Notice semicolon instead of comma!
625+
/// format!(4; "test")?;
626+
/// format!(15; "hello {}", "world!")?;
627+
/// format!(20; "x = {}, y = {y}", 10, y = 30)?;
628+
/// let (x, y) = (1, 2);
629+
/// format!(12; "{x} + {y} = 3")?;
630+
///
631+
/// let implicit: String<10> = format!("speed = {}", 7)?;
632+
/// # Ok(())
633+
/// # }
634+
/// ```
635+
#[macro_export]
636+
macro_rules! format {
637+
// Without semicolon as separator to disambiguate between arms, Rust just
638+
// chooses the first so that the format string would land in $max.
639+
($max:expr; $($arg:tt)*) => {{
640+
let res = $crate::_export::format::<$max>(core::format_args!($($arg)*));
641+
res
642+
}};
643+
($($arg:tt)*) => {{
644+
let res = $crate::_export::format(core::format_args!($($arg)*));
645+
res
646+
}};
647+
}
648+
573649
macro_rules! impl_try_from_num {
574650
($num:ty, $size:expr) => {
575651
impl<const N: usize> core::convert::TryFrom<$num> for String<N> {
@@ -831,4 +907,32 @@ mod tests {
831907
assert_eq!(s.remove(2), '\u{0301}');
832908
assert_eq!(s.as_str(), "hey");
833909
}
910+
911+
#[test]
912+
fn format() {
913+
let number = 5;
914+
let float = 3.12;
915+
let formatted = format!(15; "{:0>3} plus {float}", number).unwrap();
916+
assert_eq!(formatted, "005 plus 3.12")
917+
}
918+
#[test]
919+
fn format_inferred_capacity() {
920+
let number = 5;
921+
let float = 3.12;
922+
let formatted: String<15> = format!("{:0>3} plus {float}", number).unwrap();
923+
assert_eq!(formatted, "005 plus 3.12")
924+
}
925+
926+
#[test]
927+
fn format_overflow() {
928+
let i = 1234567;
929+
let formatted = format!(4; "13{}", i);
930+
assert_eq!(formatted, Err(core::fmt::Error))
931+
}
932+
933+
#[test]
934+
fn format_plain_string_overflow() {
935+
let formatted = format!(2; "123");
936+
assert_eq!(formatted, Err(core::fmt::Error))
937+
}
834938
}

0 commit comments

Comments
 (0)