/rust/registry/src/index.crates.io-1949cf8c6b5b557f/writeable-0.6.2/src/lib.rs
Line | Count | Source |
1 | | // This file is part of ICU4X. For terms of use, please see the file |
2 | | // called LICENSE at the top level of the ICU4X source tree |
3 | | // (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ). |
4 | | |
5 | | // https://github.com/unicode-org/icu4x/blob/main/documents/process/boilerplate.md#library-annotations |
6 | | #![cfg_attr(not(any(test, doc)), no_std)] |
7 | | #![cfg_attr( |
8 | | not(test), |
9 | | deny( |
10 | | clippy::indexing_slicing, |
11 | | clippy::unwrap_used, |
12 | | clippy::expect_used, |
13 | | clippy::panic, |
14 | | clippy::exhaustive_structs, |
15 | | clippy::exhaustive_enums, |
16 | | clippy::trivially_copy_pass_by_ref, |
17 | | missing_debug_implementations, |
18 | | ) |
19 | | )] |
20 | | |
21 | | //! This crate defines [`Writeable`], a trait representing an object that can be written to a |
22 | | //! sink implementing `std::fmt::Write`. It is an alternative to `std::fmt::Display` with the |
23 | | //! addition of a function indicating the number of bytes to be written. |
24 | | //! |
25 | | //! `Writeable` improves upon `std::fmt::Display` in two ways: |
26 | | //! |
27 | | //! 1. More efficient, since the sink can pre-allocate bytes. |
28 | | //! 2. Smaller code, since the format machinery can be short-circuited. |
29 | | //! |
30 | | //! This crate also exports [`TryWriteable`], a writeable that supports a custom error. |
31 | | //! |
32 | | //! # Benchmarks |
33 | | //! |
34 | | //! The benchmarks to generate the following data can be found in the `benches` directory. |
35 | | //! |
36 | | //! | Case | `Writeable` | `Display` | |
37 | | //! |---|---|---| |
38 | | //! | Create string from single-string message (139 chars) | 15.642 ns | 19.251 ns | |
39 | | //! | Create string from complex message | 35.830 ns | 89.478 ns | |
40 | | //! | Write complex message to buffer | 57.336 ns | 64.408 ns | |
41 | | //! |
42 | | //! # Examples |
43 | | //! |
44 | | //! ``` |
45 | | //! use std::fmt; |
46 | | //! use writeable::assert_writeable_eq; |
47 | | //! use writeable::LengthHint; |
48 | | //! use writeable::Writeable; |
49 | | //! |
50 | | //! struct WelcomeMessage<'s> { |
51 | | //! pub name: &'s str, |
52 | | //! } |
53 | | //! |
54 | | //! impl<'s> Writeable for WelcomeMessage<'s> { |
55 | | //! fn write_to<W: fmt::Write + ?Sized>(&self, sink: &mut W) -> fmt::Result { |
56 | | //! sink.write_str("Hello, ")?; |
57 | | //! sink.write_str(self.name)?; |
58 | | //! sink.write_char('!')?; |
59 | | //! Ok(()) |
60 | | //! } |
61 | | //! |
62 | | //! fn writeable_length_hint(&self) -> LengthHint { |
63 | | //! // "Hello, " + '!' + length of name |
64 | | //! LengthHint::exact(8 + self.name.len()) |
65 | | //! } |
66 | | //! } |
67 | | //! |
68 | | //! let message = WelcomeMessage { name: "Alice" }; |
69 | | //! assert_writeable_eq!(&message, "Hello, Alice!"); |
70 | | //! |
71 | | //! // Types implementing `Writeable` are recommended to also implement `fmt::Display`. |
72 | | //! // This can be simply done by redirecting to the `Writeable` implementation: |
73 | | //! writeable::impl_display_with_writeable!(WelcomeMessage<'_>); |
74 | | //! assert_eq!(message.to_string(), "Hello, Alice!"); |
75 | | //! ``` |
76 | | //! |
77 | | //! [`ICU4X`]: ../icu/index.html |
78 | | |
79 | | #[cfg(feature = "alloc")] |
80 | | extern crate alloc; |
81 | | |
82 | | mod cmp; |
83 | | #[cfg(feature = "either")] |
84 | | mod either; |
85 | | mod impls; |
86 | | mod ops; |
87 | | mod parts_write_adapter; |
88 | | #[cfg(feature = "alloc")] |
89 | | mod testing; |
90 | | #[cfg(feature = "alloc")] |
91 | | mod to_string_or_borrow; |
92 | | mod try_writeable; |
93 | | |
94 | | #[cfg(feature = "alloc")] |
95 | | use alloc::borrow::Cow; |
96 | | |
97 | | #[cfg(feature = "alloc")] |
98 | | use alloc::string::String; |
99 | | use core::fmt; |
100 | | |
101 | | pub use cmp::{cmp_str, cmp_utf8}; |
102 | | #[cfg(feature = "alloc")] |
103 | | pub use to_string_or_borrow::to_string_or_borrow; |
104 | | pub use try_writeable::TryWriteable; |
105 | | |
106 | | /// Helper types for trait impls. |
107 | | pub mod adapters { |
108 | | use super::*; |
109 | | |
110 | | pub use parts_write_adapter::CoreWriteAsPartsWrite; |
111 | | pub use parts_write_adapter::WithPart; |
112 | | pub use try_writeable::TryWriteableInfallibleAsWriteable; |
113 | | pub use try_writeable::WriteableAsTryWriteableInfallible; |
114 | | |
115 | | #[derive(Debug)] |
116 | | #[allow(clippy::exhaustive_structs)] // newtype |
117 | | pub struct LossyWrap<T>(pub T); |
118 | | |
119 | | impl<T: TryWriteable> Writeable for LossyWrap<T> { |
120 | | fn write_to<W: fmt::Write + ?Sized>(&self, sink: &mut W) -> fmt::Result { |
121 | | let _ = self.0.try_write_to(sink)?; |
122 | | Ok(()) |
123 | | } |
124 | | |
125 | | fn writeable_length_hint(&self) -> LengthHint { |
126 | | self.0.writeable_length_hint() |
127 | | } |
128 | | } |
129 | | |
130 | | impl<T: TryWriteable> fmt::Display for LossyWrap<T> { |
131 | | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
132 | | let _ = self.0.try_write_to(f)?; |
133 | | Ok(()) |
134 | | } |
135 | | } |
136 | | } |
137 | | |
138 | | #[doc(hidden)] // for testing and macros |
139 | | pub mod _internal { |
140 | | #[cfg(feature = "alloc")] |
141 | | pub use super::testing::try_writeable_to_parts_for_test; |
142 | | #[cfg(feature = "alloc")] |
143 | | pub use super::testing::writeable_to_parts_for_test; |
144 | | #[cfg(feature = "alloc")] |
145 | | pub use alloc::string::String; |
146 | | } |
147 | | |
148 | | /// A hint to help consumers of `Writeable` pre-allocate bytes before they call |
149 | | /// [`write_to`](Writeable::write_to). |
150 | | /// |
151 | | /// This behaves like `Iterator::size_hint`: it is a tuple where the first element is the |
152 | | /// lower bound, and the second element is the upper bound. If the upper bound is `None` |
153 | | /// either there is no known upper bound, or the upper bound is larger than `usize`. |
154 | | /// |
155 | | /// `LengthHint` implements std`::ops::{Add, Mul}` and similar traits for easy composition. |
156 | | /// During computation, the lower bound will saturate at `usize::MAX`, while the upper |
157 | | /// bound will become `None` if `usize::MAX` is exceeded. |
158 | | #[derive(Debug, PartialEq, Eq, Copy, Clone)] |
159 | | #[non_exhaustive] |
160 | | pub struct LengthHint(pub usize, pub Option<usize>); |
161 | | |
162 | | impl LengthHint { |
163 | | pub fn undefined() -> Self { |
164 | | Self(0, None) |
165 | | } |
166 | | |
167 | | /// `write_to` will use exactly n bytes. |
168 | | pub fn exact(n: usize) -> Self { |
169 | | Self(n, Some(n)) |
170 | | } |
171 | | |
172 | | /// `write_to` will use at least n bytes. |
173 | | pub fn at_least(n: usize) -> Self { |
174 | | Self(n, None) |
175 | | } |
176 | | |
177 | | /// `write_to` will use at most n bytes. |
178 | | pub fn at_most(n: usize) -> Self { |
179 | | Self(0, Some(n)) |
180 | | } |
181 | | |
182 | | /// `write_to` will use between `n` and `m` bytes. |
183 | | pub fn between(n: usize, m: usize) -> Self { |
184 | | Self(Ord::min(n, m), Some(Ord::max(n, m))) |
185 | | } |
186 | | |
187 | | /// Returns a recommendation for the number of bytes to pre-allocate. |
188 | | /// If an upper bound exists, this is used, otherwise the lower bound |
189 | | /// (which might be 0). |
190 | | /// |
191 | | /// # Examples |
192 | | /// |
193 | | /// ``` |
194 | | /// use writeable::Writeable; |
195 | | /// |
196 | | /// fn pre_allocate_string(w: &impl Writeable) -> String { |
197 | | /// String::with_capacity(w.writeable_length_hint().capacity()) |
198 | | /// } |
199 | | /// ``` |
200 | | pub fn capacity(&self) -> usize { |
201 | | self.1.unwrap_or(self.0) |
202 | | } |
203 | | |
204 | | /// Returns whether the `LengthHint` indicates that the string is exactly 0 bytes long. |
205 | | pub fn is_zero(&self) -> bool { |
206 | | self.1 == Some(0) |
207 | | } |
208 | | } |
209 | | |
210 | | /// [`Part`]s are used as annotations for formatted strings. |
211 | | /// |
212 | | /// For example, a string like `Alice, Bob` could assign a `NAME` part to the |
213 | | /// substrings `Alice` and `Bob`, and a `PUNCTUATION` part to `, `. This allows |
214 | | /// for example to apply styling only to names. |
215 | | /// |
216 | | /// `Part` contains two fields, whose usage is left up to the producer of the [`Writeable`]. |
217 | | /// Conventionally, the `category` field will identify the formatting logic that produces |
218 | | /// the string/parts, whereas the `value` field will have semantic meaning. `NAME` and |
219 | | /// `PUNCTUATION` could thus be defined as |
220 | | /// ``` |
221 | | /// # use writeable::Part; |
222 | | /// const NAME: Part = Part { |
223 | | /// category: "userlist", |
224 | | /// value: "name", |
225 | | /// }; |
226 | | /// const PUNCTUATION: Part = Part { |
227 | | /// category: "userlist", |
228 | | /// value: "punctuation", |
229 | | /// }; |
230 | | /// ``` |
231 | | /// |
232 | | /// That said, consumers should not usually have to inspect `Part` internals. Instead, |
233 | | /// formatters should expose the `Part`s they produces as constants. |
234 | | #[derive(Clone, Copy, Debug, PartialEq)] |
235 | | #[allow(clippy::exhaustive_structs)] // stable |
236 | | pub struct Part { |
237 | | pub category: &'static str, |
238 | | pub value: &'static str, |
239 | | } |
240 | | |
241 | | impl Part { |
242 | | /// A part that should annotate error segments in [`TryWriteable`] output. |
243 | | /// |
244 | | /// For an example, see [`TryWriteable`]. |
245 | | pub const ERROR: Part = Part { |
246 | | category: "writeable", |
247 | | value: "error", |
248 | | }; |
249 | | } |
250 | | |
251 | | /// A sink that supports annotating parts of the string with [`Part`]s. |
252 | | pub trait PartsWrite: fmt::Write { |
253 | | type SubPartsWrite: PartsWrite + ?Sized; |
254 | | |
255 | | /// Annotates all strings written by the closure with the given [`Part`]. |
256 | | fn with_part( |
257 | | &mut self, |
258 | | part: Part, |
259 | | f: impl FnMut(&mut Self::SubPartsWrite) -> fmt::Result, |
260 | | ) -> fmt::Result; |
261 | | } |
262 | | |
263 | | /// `Writeable` is an alternative to `std::fmt::Display` with the addition of a length function. |
264 | | pub trait Writeable { |
265 | | /// Writes a string to the given sink. Errors from the sink are bubbled up. |
266 | | /// The default implementation delegates to `write_to_parts`, and discards any |
267 | | /// `Part` annotations. |
268 | | fn write_to<W: fmt::Write + ?Sized>(&self, sink: &mut W) -> fmt::Result { |
269 | | self.write_to_parts(&mut parts_write_adapter::CoreWriteAsPartsWrite(sink)) |
270 | | } |
271 | | |
272 | | /// Write bytes and `Part` annotations to the given sink. Errors from the |
273 | | /// sink are bubbled up. The default implementation delegates to `write_to`, |
274 | | /// and doesn't produce any `Part` annotations. |
275 | | fn write_to_parts<S: PartsWrite + ?Sized>(&self, sink: &mut S) -> fmt::Result { |
276 | | self.write_to(sink) |
277 | | } |
278 | | |
279 | | /// Returns a hint for the number of UTF-8 bytes that will be written to the sink. |
280 | | /// |
281 | | /// Override this method if it can be computed quickly. |
282 | | fn writeable_length_hint(&self) -> LengthHint { |
283 | | LengthHint::undefined() |
284 | | } |
285 | | |
286 | | /// Returns a `&str` that matches the output of `write_to`, if possible. |
287 | | /// |
288 | | /// This method is used to avoid materializing a [`String`] in `write_to_string`. |
289 | | fn writeable_borrow(&self) -> Option<&str> { |
290 | | None |
291 | | } |
292 | | |
293 | | /// Creates a new string with the data from this `Writeable`. |
294 | | /// |
295 | | /// Unlike [`to_string`](ToString::to_string), this does not pull in `core::fmt` |
296 | | /// code, and borrows the string if possible. |
297 | | /// |
298 | | /// To remove the `Cow` wrapper, call `.into_owned()` or `.as_str()` as appropriate. |
299 | | /// |
300 | | /// # Examples |
301 | | /// |
302 | | /// Inspect a [`Writeable`] before writing it to the sink: |
303 | | /// |
304 | | /// ``` |
305 | | /// use core::fmt::{Result, Write}; |
306 | | /// use writeable::Writeable; |
307 | | /// |
308 | | /// fn write_if_ascii<W, S>(w: &W, sink: &mut S) -> Result |
309 | | /// where |
310 | | /// W: Writeable + ?Sized, |
311 | | /// S: Write + ?Sized, |
312 | | /// { |
313 | | /// let s = w.write_to_string(); |
314 | | /// if s.is_ascii() { |
315 | | /// sink.write_str(&s) |
316 | | /// } else { |
317 | | /// Ok(()) |
318 | | /// } |
319 | | /// } |
320 | | /// ``` |
321 | | /// |
322 | | /// Convert the `Writeable` into a fully owned `String`: |
323 | | /// |
324 | | /// ``` |
325 | | /// use writeable::Writeable; |
326 | | /// |
327 | | /// fn make_string(w: &impl Writeable) -> String { |
328 | | /// w.write_to_string().into_owned() |
329 | | /// } |
330 | | /// ``` |
331 | | /// |
332 | | /// # Note to implementors |
333 | | /// |
334 | | /// This method has a default implementation in terms of `writeable_borrow`, |
335 | | /// `writeable_length_hint`, and `write_to`. The only case |
336 | | /// where this should be implemented is if the computation of `writeable_borrow` |
337 | | /// requires a full invocation of `write_to`. In this case, implement this |
338 | | /// using [`to_string_or_borrow`]. |
339 | | /// |
340 | | /// # `alloc` Cargo feature |
341 | | /// |
342 | | /// Calling or implementing this method requires the `alloc` Cargo feature. |
343 | | /// However, as all the methods required by the default implementation do |
344 | | /// not require the `alloc` Cargo feature, a caller that uses the feature |
345 | | /// can still call this on types from crates that don't use the `alloc` |
346 | | /// Cargo feature. |
347 | | #[cfg(feature = "alloc")] |
348 | | fn write_to_string(&self) -> Cow<'_, str> { |
349 | | if let Some(borrow) = self.writeable_borrow() { |
350 | | return Cow::Borrowed(borrow); |
351 | | } |
352 | | let hint = self.writeable_length_hint(); |
353 | | if hint.is_zero() { |
354 | | return Cow::Borrowed(""); |
355 | | } |
356 | | let mut output = String::with_capacity(hint.capacity()); |
357 | | let _ = self.write_to(&mut output); |
358 | | Cow::Owned(output) |
359 | | } |
360 | | } |
361 | | |
362 | | /// Implements [`Display`](core::fmt::Display) for types that implement [`Writeable`]. |
363 | | /// |
364 | | /// It's recommended to do this for every [`Writeable`] type, as it will add |
365 | | /// support for `core::fmt` features like [`fmt!`](std::fmt), |
366 | | /// [`print!`](std::print), [`write!`](std::write), etc. |
367 | | /// |
368 | | /// This macro also adds a concrete `to_string` function. This function will shadow the |
369 | | /// standard library `ToString`, using the more efficient writeable-based code path. |
370 | | /// To add only `Display`, use the `@display` macro variant. |
371 | | #[macro_export] |
372 | | macro_rules! impl_display_with_writeable { |
373 | | (@display, $type:ty) => { |
374 | | /// This trait is implemented for compatibility with [`fmt!`](alloc::fmt). |
375 | | /// To create a string, [`Writeable::write_to_string`] is usually more efficient. |
376 | | impl core::fmt::Display for $type { |
377 | | #[inline] |
378 | 0 | fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { |
379 | 0 | $crate::Writeable::write_to(&self, f) |
380 | 0 | } |
381 | | } |
382 | | }; |
383 | | ($type:ty $(, #[$alloc_feature:meta])? ) => { |
384 | | $crate::impl_display_with_writeable!(@display, $type); |
385 | | $(#[$alloc_feature])? |
386 | | impl $type { |
387 | | /// Converts the given value to a `String`. |
388 | | /// |
389 | | /// Under the hood, this uses an efficient [`Writeable`] implementation. |
390 | | /// However, in order to avoid allocating a string, it is more efficient |
391 | | /// to use [`Writeable`] directly. |
392 | | pub fn to_string(&self) -> $crate::_internal::String { |
393 | | $crate::Writeable::write_to_string(self).into_owned() |
394 | | } |
395 | | } |
396 | | }; |
397 | | } |
398 | | |
399 | | /// Testing macros for types implementing [`Writeable`]. |
400 | | /// |
401 | | /// Arguments, in order: |
402 | | /// |
403 | | /// 1. The [`Writeable`] under test |
404 | | /// 2. The expected string value |
405 | | /// 3. [`*_parts_eq`] only: a list of parts (`[(start, end, Part)]`) |
406 | | /// |
407 | | /// Any remaining arguments get passed to `format!` |
408 | | /// |
409 | | /// The macros tests the following: |
410 | | /// |
411 | | /// - Equality of string content |
412 | | /// - Equality of parts ([`*_parts_eq`] only) |
413 | | /// - Validity of size hint |
414 | | /// |
415 | | /// # Examples |
416 | | /// |
417 | | /// ``` |
418 | | /// # use writeable::Writeable; |
419 | | /// # use writeable::LengthHint; |
420 | | /// # use writeable::Part; |
421 | | /// # use writeable::assert_writeable_eq; |
422 | | /// # use writeable::assert_writeable_parts_eq; |
423 | | /// # use std::fmt::{self, Write}; |
424 | | /// |
425 | | /// const WORD: Part = Part { |
426 | | /// category: "foo", |
427 | | /// value: "word", |
428 | | /// }; |
429 | | /// |
430 | | /// struct Demo; |
431 | | /// impl Writeable for Demo { |
432 | | /// fn write_to_parts<S: writeable::PartsWrite + ?Sized>( |
433 | | /// &self, |
434 | | /// sink: &mut S, |
435 | | /// ) -> fmt::Result { |
436 | | /// sink.with_part(WORD, |w| w.write_str("foo")) |
437 | | /// } |
438 | | /// fn writeable_length_hint(&self) -> LengthHint { |
439 | | /// LengthHint::exact(3) |
440 | | /// } |
441 | | /// } |
442 | | /// |
443 | | /// writeable::impl_display_with_writeable!(Demo); |
444 | | /// |
445 | | /// assert_writeable_eq!(&Demo, "foo"); |
446 | | /// assert_writeable_eq!(&Demo, "foo", "Message: {}", "Hello World"); |
447 | | /// |
448 | | /// assert_writeable_parts_eq!(&Demo, "foo", [(0, 3, WORD)]); |
449 | | /// assert_writeable_parts_eq!( |
450 | | /// &Demo, |
451 | | /// "foo", |
452 | | /// [(0, 3, WORD)], |
453 | | /// "Message: {}", |
454 | | /// "Hello World" |
455 | | /// ); |
456 | | /// ``` |
457 | | /// |
458 | | /// [`*_parts_eq`]: assert_writeable_parts_eq |
459 | | #[macro_export] |
460 | | #[cfg(feature = "alloc")] |
461 | | macro_rules! assert_writeable_eq { |
462 | | ($actual_writeable:expr, $expected_str:expr $(,)?) => { |
463 | | $crate::assert_writeable_eq!($actual_writeable, $expected_str, "") |
464 | | }; |
465 | | ($actual_writeable:expr, $expected_str:expr, $($arg:tt)+) => {{ |
466 | | $crate::assert_writeable_eq!(@internal, $actual_writeable, $expected_str, $($arg)*); |
467 | | }}; |
468 | | (@internal, $actual_writeable:expr, $expected_str:expr, $($arg:tt)+) => {{ |
469 | | let actual_writeable = &$actual_writeable; |
470 | | let (actual_str, actual_parts) = $crate::_internal::writeable_to_parts_for_test(actual_writeable); |
471 | | let actual_len = actual_str.len(); |
472 | | assert_eq!(actual_str, $expected_str, $($arg)*); |
473 | | let cow = $crate::Writeable::write_to_string(actual_writeable); |
474 | | assert_eq!(actual_str, cow, $($arg)+); |
475 | | if let Some(borrowed) = ($crate::Writeable::writeable_borrow(&actual_writeable)) { |
476 | | assert_eq!(borrowed, $expected_str, $($arg)*); |
477 | | assert!(matches!(cow, std::borrow::Cow::Borrowed(_)), $($arg)*); |
478 | | } |
479 | | let length_hint = $crate::Writeable::writeable_length_hint(actual_writeable); |
480 | | let lower = length_hint.0; |
481 | | assert!( |
482 | | lower <= actual_len, |
483 | | "hint lower bound {lower} larger than actual length {actual_len}: {}", |
484 | | format!($($arg)*), |
485 | | ); |
486 | | if let Some(upper) = length_hint.1 { |
487 | | assert!( |
488 | | actual_len <= upper, |
489 | | "hint upper bound {upper} smaller than actual length {actual_len}: {}", |
490 | | format!($($arg)*), |
491 | | ); |
492 | | } |
493 | | assert_eq!(actual_writeable.to_string(), $expected_str, $($arg)*); |
494 | | actual_parts // return for assert_writeable_parts_eq |
495 | | }}; |
496 | | } |
497 | | |
498 | | /// See [`assert_writeable_eq`]. |
499 | | #[macro_export] |
500 | | #[cfg(feature = "alloc")] |
501 | | macro_rules! assert_writeable_parts_eq { |
502 | | ($actual_writeable:expr, $expected_str:expr, $expected_parts:expr $(,)?) => { |
503 | | $crate::assert_writeable_parts_eq!($actual_writeable, $expected_str, $expected_parts, "") |
504 | | }; |
505 | | ($actual_writeable:expr, $expected_str:expr, $expected_parts:expr, $($arg:tt)+) => {{ |
506 | | let actual_parts = $crate::assert_writeable_eq!(@internal, $actual_writeable, $expected_str, $($arg)*); |
507 | | assert_eq!(actual_parts, $expected_parts, $($arg)+); |
508 | | }}; |
509 | | } |