bon/
collections.rs

1/// Same as [`std::vec!`] but converts each element with [`Into`].
2///
3/// **WARNING:** it's not recommended to import this macro into scope. Reference it
4/// using the full path (`bon::vec![]`) to avoid confusion with the [`std::vec!`] macro.
5///
6/// A good example of the use case for this macro is when you want to create a
7/// [`Vec<String>`] where part of the items are hard-coded string literals of type
8/// `&str` and the other part is made of dynamic [`String`] values.
9///
10/// ```
11/// fn convert_media(input_extension: &str, output_extension: &str) -> std::io::Result<()> {
12///     let ffmpeg_args: Vec<String> = bon::vec![
13///         "-i",
14///         format!("input.{input_extension}"),
15///         "-y",
16///         format!("output.{output_extension}"),
17///     ];
18///
19///     std::process::Command::new("ffmpeg").args(ffmpeg_args).output()?;
20///
21///     Ok(())
22/// }
23/// ```
24///
25/// This macro doesn't support `vec![expr; N]` syntax, since it's simpler to
26/// just write `vec![expr.into(); N]` using [`std::vec!`] instead.
27#[macro_export]
28#[cfg(feature = "alloc")]
29#[cfg_attr(docsrs, doc(cfg(any(feature = "std", feature = "alloc"))))]
30#[allow(edition_2024_expr_fragment_specifier)]
31macro_rules! vec {
32    () => ($crate::__::alloc::vec::Vec::new());
33    ($($item:expr),+ $(,)?) => ($crate::__::alloc::vec![$(::core::convert::Into::into($item)),+ ]);
34}
35
36/// Creates a fixed-size array literal with each element converted with [`Into`].
37///
38/// You'll probably need a hint for the target type of items in the array if the
39/// compiler can't infer it from its usage.
40///
41/// This is similar in spirit to the [`bon::vec!`] macro, but it's for arrays.
42/// See [`bon::vec!`] docs for details.
43///
44/// Same example as in [`bon::vec!`], but using this macro. It works with array
45/// as well because [`Command::args`] accepts any value that implements [`IntoIterator`]:
46///
47/// ```
48/// fn convert_media(input_extension: &str, output_extension: &str) -> std::io::Result<()> {
49///     let ffmpeg_args: [String; 4] = bon::arr![
50///         "-i",
51///         format!("input.{input_extension}"),
52///         "-y",
53///         format!("output.{output_extension}"),
54///     ];
55///
56///     std::process::Command::new("ffmpeg").args(ffmpeg_args).output()?;
57///
58///     Ok(())
59/// }
60/// ```
61///
62/// This macro doesn't support `[expr; N]` syntax, since it's simpler to
63/// just write `[expr.into(); N]` instead.
64///
65/// [`Command::args`]: std::process::Command::args
66/// [`bon::vec!`]: crate::vec
67#[macro_export]
68#[allow(edition_2024_expr_fragment_specifier)]
69macro_rules! arr {
70    () => ([]);
71    ($($item:expr),+ $(,)?) => ([$(::core::convert::Into::into($item)),+]);
72}
73
74#[cfg(test)]
75mod tests {
76    #[cfg(feature = "alloc")]
77    use crate::__::alloc::{string::String, vec::Vec};
78    use core::num::NonZeroU8;
79
80    #[cfg(feature = "alloc")]
81    #[test]
82    fn arr_of_strings() {
83        let actual: [String; 3] = crate::arr!["foo", "bar", "baz"];
84        assert_eq!(actual, ["foo", "bar", "baz"]);
85
86        let actual: [String; 0] = crate::arr![];
87        assert!(actual.is_empty());
88    }
89
90    #[test]
91    fn arr_of_numbers() {
92        let actual: [u8; 2] = crate::arr![NonZeroU8::new(1).unwrap(), NonZeroU8::new(2).unwrap()];
93        assert_eq!(actual, [1, 2]);
94
95        let actual: [u8; 0] = crate::arr![];
96        assert!(actual.is_empty());
97    }
98
99    #[cfg(feature = "alloc")]
100    #[test]
101    fn vec_smoke() {
102        let actual: Vec<String> = crate::vec!["foo", "bar", "baz"];
103        assert_eq!(actual, ["foo", "bar", "baz"]);
104
105        let actual: Vec<String> = crate::vec![];
106        assert!(actual.is_empty());
107    }
108
109    #[cfg(feature = "std")]
110    #[test]
111    fn map_smoke() {
112        use std::collections::{BTreeMap, HashMap};
113
114        let hash_strings: HashMap<String, String> = crate::map! {
115            "Hello": "World",
116            "Goodbye": "Mars",
117        };
118
119        assert_eq!(hash_strings["Hello"], "World");
120        assert_eq!(hash_strings["Goodbye"], "Mars");
121
122        let tree_strings: BTreeMap<String, String> = crate::map! {
123            "Hello": "World",
124            "Goodbye": "Mars",
125        };
126
127        assert_eq!(tree_strings["Hello"], "World");
128        assert_eq!(tree_strings["Goodbye"], "Mars");
129    }
130
131    #[cfg(feature = "std")]
132    #[test]
133    fn set_smoke() {
134        use std::collections::BTreeSet;
135        use std::collections::HashSet;
136
137        let hash_strings: HashSet<String> = crate::set!["Hello", "World", "Goodbye", "Mars"];
138
139        assert!(hash_strings.contains("Hello"));
140        assert!(hash_strings.contains("World"));
141        assert!(hash_strings.contains("Goodbye"));
142        assert!(hash_strings.contains("Mars"));
143
144        let tree_strings: BTreeSet<String> = crate::set!["Hello", "World", "Goodbye", "Mars"];
145
146        assert!(tree_strings.contains("Hello"));
147        assert!(tree_strings.contains("World"));
148        assert!(tree_strings.contains("Goodbye"));
149        assert!(tree_strings.contains("Mars"));
150    }
151}