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