|
| 1 | +//! A fixed capacity [`String`](https://doc.rust-lang.org/std/string/struct.String.html) |
| 2 | +
|
1 | 3 | use core::{
|
2 | 4 | cmp::Ordering,
|
3 | 5 | fmt,
|
4 |
| - fmt::Write, |
| 6 | + fmt::{Arguments, Write}, |
5 | 7 | hash, iter, ops,
|
6 | 8 | str::{self, Utf8Error},
|
7 | 9 | };
|
@@ -570,6 +572,80 @@ impl<const N: usize> Ord for String<N> {
|
570 | 572 | }
|
571 | 573 | }
|
572 | 574 |
|
| 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 | + |
573 | 649 | macro_rules! impl_try_from_num {
|
574 | 650 | ($num:ty, $size:expr) => {
|
575 | 651 | impl<const N: usize> core::convert::TryFrom<$num> for String<N> {
|
@@ -831,4 +907,32 @@ mod tests {
|
831 | 907 | assert_eq!(s.remove(2), '\u{0301}');
|
832 | 908 | assert_eq!(s.as_str(), "hey");
|
833 | 909 | }
|
| 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 | + } |
834 | 938 | }
|
0 commit comments