#![forbid(rust_2018_idioms)]
#![deny(nonstandard_style)]
#![warn(unreachable_pub, missing_debug_implementations, missing_docs)]
use std::{
borrow::{Borrow, BorrowMut},
cmp::Ordering,
convert::Infallible,
fmt::{Debug, Display, Error, Formatter, Write},
hash::{Hash, Hasher},
iter::FromIterator,
mem::{forget, MaybeUninit},
ops::{
Add, Bound, Deref, DerefMut, Index, IndexMut, Range, RangeBounds, RangeFrom, RangeFull,
RangeInclusive, RangeTo, RangeToInclusive,
},
ptr::drop_in_place,
str::FromStr,
};
mod config;
pub use config::{Compact, LazyCompact, SmartStringMode};
mod marker_byte;
use marker_byte::{Discriminant, Marker};
mod inline;
use inline::InlineString;
mod boxed;
use boxed::BoxedString;
mod casts;
use casts::{StringCast, StringCastInto, StringCastMut};
mod iter;
pub use iter::Drain;
#[cfg(feature = "serde")]
mod serde;
#[cfg(feature = "arbitrary")]
mod arbitrary;
#[cfg(feature = "proptest")]
pub mod proptest;
pub mod alias {
use super::*;
pub type String = SmartString<LazyCompact>;
pub type CompactString = SmartString<Compact>;
}
#[cfg_attr(target_pointer_width = "64", repr(C, align(8)))]
#[cfg_attr(target_pointer_width = "32", repr(C, align(4)))]
pub struct SmartString<Mode: SmartStringMode> {
data: MaybeUninit<InlineString<Mode>>,
}
impl<Mode: SmartStringMode> Drop for SmartString<Mode> {
fn drop(&mut self) {
if let StringCastMut::Boxed(string) = self.cast_mut() {
unsafe { drop_in_place(string) };
}
}
}
impl<Mode: SmartStringMode> Clone for SmartString<Mode> {
fn clone(&self) -> Self {
match self.cast() {
StringCast::Boxed(string) => Self::from_boxed(string.string().clone().into()),
StringCast::Inline(string) => Self::from_inline(*string),
}
}
}
impl<Mode: SmartStringMode> Deref for SmartString<Mode> {
type Target = str;
#[inline(always)]
fn deref(&self) -> &Self::Target {
match self.cast() {
StringCast::Boxed(string) => string.string().as_str(),
StringCast::Inline(string) => string.as_str(),
}
}
}
impl<Mode: SmartStringMode> DerefMut for SmartString<Mode> {
#[inline(always)]
fn deref_mut(&mut self) -> &mut Self::Target {
match self.cast_mut() {
StringCastMut::Boxed(string) => string.string_mut().as_mut_str(),
StringCastMut::Inline(string) => string.as_mut_str(),
}
}
}
impl<Mode: SmartStringMode> SmartString<Mode> {
#[inline(always)]
pub fn new() -> Self {
Self::from_inline(InlineString::new())
}
fn from_boxed(boxed: Mode::BoxedString) -> Self {
let mut out = Self {
data: MaybeUninit::uninit(),
};
let data_ptr: *mut Mode::BoxedString = out.data.as_mut_ptr().cast();
unsafe { data_ptr.write(boxed) };
out
}
fn from_inline(inline: InlineString<Mode>) -> Self {
let mut out = Self {
data: MaybeUninit::uninit(),
};
unsafe { out.data.as_mut_ptr().write(inline) };
out
}
fn discriminant(&self) -> Discriminant {
let ptr: *const Marker = self.data.as_ptr().cast();
unsafe { *ptr }.discriminant()
}
fn cast(&self) -> StringCast<'_, Mode> {
match self.discriminant() {
Discriminant::Inline => StringCast::Inline(unsafe { &*self.data.as_ptr() }),
Discriminant::Boxed => StringCast::Boxed(unsafe { &*self.data.as_ptr().cast() }),
}
}
fn cast_mut(&mut self) -> StringCastMut<'_, Mode> {
match self.discriminant() {
Discriminant::Inline => StringCastMut::Inline(unsafe { &mut *self.data.as_mut_ptr() }),
Discriminant::Boxed => {
StringCastMut::Boxed(unsafe { &mut *self.data.as_mut_ptr().cast() })
}
}
}
fn cast_into(mut self) -> StringCastInto<Mode> {
match self.discriminant() {
Discriminant::Inline => StringCastInto::Inline(unsafe { self.data.assume_init() }),
Discriminant::Boxed => StringCastInto::Boxed(unsafe {
let boxed_ptr: *mut Mode::BoxedString = self.data.as_mut_ptr().cast();
let string = boxed_ptr.read();
forget(self);
string
}),
}
}
fn promote_from(&mut self, string: String) {
debug_assert!(self.discriminant() == Discriminant::Inline);
let string: Mode::BoxedString = string.into();
let data: *mut Mode::BoxedString = self.data.as_mut_ptr().cast();
unsafe { data.write(string) };
}
fn try_demote(&mut self) -> bool {
if Mode::DEALLOC {
self.really_try_demote()
} else {
false
}
}
fn really_try_demote(&mut self) -> bool {
if let StringCastMut::Boxed(string) = self.cast_mut() {
if string.len() > Mode::MAX_INLINE {
false
} else {
let inlined = string.string().as_bytes().into();
unsafe {
drop_in_place(string);
let data = &mut self.data.as_mut_ptr();
data.write(inlined);
}
true
}
} else {
true
}
}
pub fn len(&self) -> usize {
match self.cast() {
StringCast::Boxed(string) => string.len(),
StringCast::Inline(string) => string.len(),
}
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn is_inline(&self) -> bool {
self.discriminant() == Discriminant::Inline
}
pub fn as_str(&self) -> &str {
self.deref()
}
pub fn as_mut_str(&mut self) -> &mut str {
self.deref_mut()
}
pub fn push(&mut self, ch: char) {
match self.cast_mut() {
StringCastMut::Boxed(string) => string.string_mut().push(ch),
StringCastMut::Inline(string) => {
let len = string.len();
let new_len = len + ch.len_utf8();
if new_len > Mode::MAX_INLINE {
let mut new_str = String::with_capacity(new_len);
new_str.push_str(string.as_str());
new_str.push(ch);
self.promote_from(new_str);
} else {
let written = ch.encode_utf8(&mut string.as_mut_slice()[len..]).len();
string.set_size(len + written);
}
}
}
}
pub fn push_str(&mut self, string: &str) {
let len = self.len();
match self.cast_mut() {
StringCastMut::Boxed(this) => this.string_mut().push_str(string),
StringCastMut::Inline(this) => {
let new_len = len + string.len();
if new_len > Mode::MAX_INLINE {
let mut new_str = String::with_capacity(new_len);
new_str.push_str(this.as_str());
new_str.push_str(string);
self.promote_from(new_str);
} else {
this.as_mut_slice()[len..len + string.len()].copy_from_slice(string.as_bytes());
this.set_size(len + string.len());
}
}
}
}
pub fn capacity(&self) -> usize {
if let StringCast::Boxed(string) = self.cast() {
string.string().capacity()
} else {
Mode::MAX_INLINE
}
}
pub fn shrink_to_fit(&mut self) {
if let StringCastMut::Boxed(string) = self.cast_mut() {
if string.len() > Mode::MAX_INLINE {
string.string_mut().shrink_to_fit();
}
}
self.really_try_demote();
}
pub fn truncate(&mut self, new_len: usize) {
match self.cast_mut() {
StringCastMut::Boxed(string) => string.string_mut().truncate(new_len),
StringCastMut::Inline(string) => {
if new_len < string.len() {
assert!(string.as_str().is_char_boundary(new_len));
string.set_size(new_len);
}
return;
}
}
self.try_demote();
}
pub fn pop(&mut self) -> Option<char> {
let result = match self.cast_mut() {
StringCastMut::Boxed(string) => string.string_mut().pop()?,
StringCastMut::Inline(string) => {
let ch = string.as_str().chars().rev().next()?;
string.set_size(string.len() - ch.len_utf8());
return Some(ch);
}
};
self.try_demote();
Some(result)
}
pub fn remove(&mut self, index: usize) -> char {
let result = match self.cast_mut() {
StringCastMut::Boxed(string) => string.string_mut().remove(index),
StringCastMut::Inline(string) => {
let ch = match string.as_str()[index..].chars().next() {
Some(ch) => ch,
None => panic!("cannot remove a char from the end of a string"),
};
let next = index + ch.len_utf8();
let len = string.len();
let tail_len = len - next;
if tail_len > 0 {
unsafe {
(&mut string.as_mut_slice()[index] as *mut u8)
.copy_from(&string.as_slice()[next], tail_len);
}
}
string.set_size(len - (next - index));
return ch;
}
};
self.try_demote();
result
}
pub fn insert(&mut self, index: usize, ch: char) {
match self.cast_mut() {
StringCastMut::Boxed(string) => {
string.string_mut().insert(index, ch);
}
StringCastMut::Inline(string) if string.len() + ch.len_utf8() <= Mode::MAX_INLINE => {
let mut buffer = [0; 4];
let buffer = ch.encode_utf8(&mut buffer).as_bytes();
string.insert_bytes(index, buffer);
}
_ => {
let mut string = self.to_string();
string.insert(index, ch);
self.promote_from(string);
}
}
}
pub fn insert_str(&mut self, index: usize, string: &str) {
match self.cast_mut() {
StringCastMut::Boxed(this) => {
this.string_mut().insert_str(index, string);
}
StringCastMut::Inline(this) if this.len() + string.len() <= Mode::MAX_INLINE => {
this.insert_bytes(index, string.as_bytes());
}
_ => {
let mut this = self.to_string();
this.insert_str(index, string);
self.promote_from(this);
}
}
}
pub fn split_off(&mut self, index: usize) -> Self {
let result = match self.cast_mut() {
StringCastMut::Boxed(string) => string.string_mut().split_off(index),
StringCastMut::Inline(string) => {
let s = string.as_str();
assert!(s.is_char_boundary(index));
let result = s[index..].into();
string.set_size(index);
return result;
}
};
self.try_demote();
result.into()
}
pub fn clear(&mut self) {
*self = Self::new();
}
pub fn retain<F>(&mut self, mut f: F)
where
F: FnMut(char) -> bool,
{
match self.cast_mut() {
StringCastMut::Boxed(string) => {
string.string_mut().retain(f);
}
StringCastMut::Inline(string) => {
let len = string.len();
let mut del_bytes = 0;
let mut index = 0;
while index < len {
let ch = unsafe {
string
.as_mut_str()
.get_unchecked(index..len)
.chars()
.next()
.unwrap()
};
let ch_len = ch.len_utf8();
if !f(ch) {
del_bytes += ch_len;
} else if del_bytes > 0 {
let ptr = string.as_mut_slice().as_mut_ptr();
unsafe { ptr.add(index - del_bytes).copy_from(ptr.add(index), ch_len) };
}
index += ch_len;
}
if del_bytes > 0 {
string.set_size(len - del_bytes);
}
return;
}
}
self.try_demote();
}
pub fn drain<R>(&mut self, range: R) -> Drain<'_, Mode>
where
R: RangeBounds<usize>,
{
Drain::new(self, range)
}
pub fn replace_range<R>(&mut self, range: R, replace_with: &str)
where
R: RangeBounds<usize>,
{
match self.cast_mut() {
StringCastMut::Boxed(string) => {
string.string_mut().replace_range(range, replace_with);
}
StringCastMut::Inline(string) => {
let len = string.len();
let (start, end) = bounds_for(&range, len);
assert!(end >= start);
assert!(end <= len);
assert!(string.as_str().is_char_boundary(start));
assert!(string.as_str().is_char_boundary(end));
let replaced_len = end - start;
let replace_len = replace_with.len();
if (len - replaced_len) + replace_len > Mode::MAX_INLINE {
let mut string = string.as_str().to_string();
string.replace_range(range, replace_with);
self.promote_from(string);
} else {
let new_end = start + replace_len;
let end_size = len - end;
let ptr = string.as_mut_slice().as_mut_ptr();
unsafe {
ptr.add(end).copy_to(ptr.add(new_end), end_size);
ptr.add(start)
.copy_from(replace_with.as_bytes().as_ptr(), replace_len);
}
string.set_size(start + replace_len + end_size);
}
return;
}
}
self.try_demote();
}
}
fn bounds_for<R>(range: &R, max_len: usize) -> (usize, usize)
where
R: RangeBounds<usize>,
{
let start = match range.start_bound() {
Bound::Included(&n) => n,
Bound::Excluded(&n) => n.checked_add(1).unwrap(),
Bound::Unbounded => 0,
};
let end = match range.end_bound() {
Bound::Included(&n) => n.checked_add(1).unwrap(),
Bound::Excluded(&n) => n,
Bound::Unbounded => max_len,
};
(start, end)
}
impl<Mode: SmartStringMode> Default for SmartString<Mode> {
fn default() -> Self {
Self::new()
}
}
impl<Mode: SmartStringMode> AsRef<str> for SmartString<Mode> {
fn as_ref(&self) -> &str {
self.deref()
}
}
impl<Mode: SmartStringMode> AsMut<str> for SmartString<Mode> {
fn as_mut(&mut self) -> &mut str {
self.deref_mut()
}
}
impl<Mode: SmartStringMode> AsRef<[u8]> for SmartString<Mode> {
fn as_ref(&self) -> &[u8] {
self.deref().as_bytes()
}
}
impl<Mode: SmartStringMode> Borrow<str> for SmartString<Mode> {
fn borrow(&self) -> &str {
self.deref()
}
}
impl<Mode: SmartStringMode> BorrowMut<str> for SmartString<Mode> {
fn borrow_mut(&mut self) -> &mut str {
self.deref_mut()
}
}
impl<Mode: SmartStringMode> Index<Range<usize>> for SmartString<Mode> {
type Output = str;
fn index(&self, index: Range<usize>) -> &Self::Output {
&self.deref()[index]
}
}
impl<Mode: SmartStringMode> Index<RangeTo<usize>> for SmartString<Mode> {
type Output = str;
fn index(&self, index: RangeTo<usize>) -> &Self::Output {
&self.deref()[index]
}
}
impl<Mode: SmartStringMode> Index<RangeFrom<usize>> for SmartString<Mode> {
type Output = str;
fn index(&self, index: RangeFrom<usize>) -> &Self::Output {
&self.deref()[index]
}
}
impl<Mode: SmartStringMode> Index<RangeFull> for SmartString<Mode> {
type Output = str;
fn index(&self, _index: RangeFull) -> &Self::Output {
self.deref()
}
}
impl<Mode: SmartStringMode> Index<RangeInclusive<usize>> for SmartString<Mode> {
type Output = str;
fn index(&self, index: RangeInclusive<usize>) -> &Self::Output {
&self.deref()[index]
}
}
impl<Mode: SmartStringMode> Index<RangeToInclusive<usize>> for SmartString<Mode> {
type Output = str;
fn index(&self, index: RangeToInclusive<usize>) -> &Self::Output {
&self.deref()[index]
}
}
impl<Mode: SmartStringMode> IndexMut<Range<usize>> for SmartString<Mode> {
fn index_mut(&mut self, index: Range<usize>) -> &mut Self::Output {
&mut self.deref_mut()[index]
}
}
impl<Mode: SmartStringMode> IndexMut<RangeTo<usize>> for SmartString<Mode> {
fn index_mut(&mut self, index: RangeTo<usize>) -> &mut Self::Output {
&mut self.deref_mut()[index]
}
}
impl<Mode: SmartStringMode> IndexMut<RangeFrom<usize>> for SmartString<Mode> {
fn index_mut(&mut self, index: RangeFrom<usize>) -> &mut Self::Output {
&mut self.deref_mut()[index]
}
}
impl<Mode: SmartStringMode> IndexMut<RangeFull> for SmartString<Mode> {
fn index_mut(&mut self, _index: RangeFull) -> &mut Self::Output {
self.deref_mut()
}
}
impl<Mode: SmartStringMode> IndexMut<RangeInclusive<usize>> for SmartString<Mode> {
fn index_mut(&mut self, index: RangeInclusive<usize>) -> &mut Self::Output {
&mut self.deref_mut()[index]
}
}
impl<Mode: SmartStringMode> IndexMut<RangeToInclusive<usize>> for SmartString<Mode> {
fn index_mut(&mut self, index: RangeToInclusive<usize>) -> &mut Self::Output {
&mut self.deref_mut()[index]
}
}
impl<Mode: SmartStringMode> From<&'_ str> for SmartString<Mode> {
fn from(string: &'_ str) -> Self {
if string.len() > Mode::MAX_INLINE {
Self::from_boxed(string.to_string().into())
} else {
Self::from_inline(string.as_bytes().into())
}
}
}
impl<Mode: SmartStringMode> From<&'_ String> for SmartString<Mode> {
fn from(string: &'_ String) -> Self {
if string.len() > Mode::MAX_INLINE {
Self::from_boxed(string.clone().into())
} else {
Self::from_inline(string.as_bytes().into())
}
}
}
impl<Mode: SmartStringMode> From<String> for SmartString<Mode> {
fn from(string: String) -> Self {
if string.len() > Mode::MAX_INLINE {
Self::from_boxed(string.into())
} else {
Self::from_inline(string.as_bytes().into())
}
}
}
impl<Mode: SmartStringMode> From<Box<str>> for SmartString<Mode> {
fn from(string: Box<str>) -> Self {
if string.len() > Mode::MAX_INLINE {
String::from(string).into()
} else {
Self::from(&*string)
}
}
}
impl<'a, Mode: SmartStringMode> Extend<&'a str> for SmartString<Mode> {
fn extend<I: IntoIterator<Item = &'a str>>(&mut self, iter: I) {
for item in iter {
self.push_str(item);
}
}
}
impl<'a, Mode: SmartStringMode> Extend<&'a char> for SmartString<Mode> {
fn extend<I: IntoIterator<Item = &'a char>>(&mut self, iter: I) {
for item in iter {
self.push(*item);
}
}
}
impl<Mode: SmartStringMode> Extend<char> for SmartString<Mode> {
fn extend<I: IntoIterator<Item = char>>(&mut self, iter: I) {
for item in iter {
self.push(item);
}
}
}
impl<Mode: SmartStringMode> Extend<SmartString<Mode>> for SmartString<Mode> {
fn extend<I: IntoIterator<Item = SmartString<Mode>>>(&mut self, iter: I) {
for item in iter {
self.push_str(&item);
}
}
}
impl<Mode: SmartStringMode> Extend<String> for SmartString<Mode> {
fn extend<I: IntoIterator<Item = String>>(&mut self, iter: I) {
for item in iter {
self.push_str(&item);
}
}
}
impl<'a, Mode: SmartStringMode + 'a> Extend<&'a SmartString<Mode>> for SmartString<Mode> {
fn extend<I: IntoIterator<Item = &'a SmartString<Mode>>>(&mut self, iter: I) {
for item in iter {
self.push_str(item);
}
}
}
impl<'a, Mode: SmartStringMode> Extend<&'a String> for SmartString<Mode> {
fn extend<I: IntoIterator<Item = &'a String>>(&mut self, iter: I) {
for item in iter {
self.push_str(item);
}
}
}
impl<Mode: SmartStringMode> Add<Self> for SmartString<Mode> {
type Output = Self;
fn add(mut self, rhs: Self) -> Self::Output {
self.push_str(&rhs);
self
}
}
impl<Mode: SmartStringMode> Add<&'_ Self> for SmartString<Mode> {
type Output = Self;
fn add(mut self, rhs: &'_ Self) -> Self::Output {
self.push_str(rhs);
self
}
}
impl<Mode: SmartStringMode> Add<&'_ str> for SmartString<Mode> {
type Output = Self;
fn add(mut self, rhs: &'_ str) -> Self::Output {
self.push_str(rhs);
self
}
}
impl<Mode: SmartStringMode> Add<&'_ String> for SmartString<Mode> {
type Output = Self;
fn add(mut self, rhs: &'_ String) -> Self::Output {
self.push_str(rhs);
self
}
}
impl<Mode: SmartStringMode> Add<String> for SmartString<Mode> {
type Output = Self;
fn add(mut self, rhs: String) -> Self::Output {
self.push_str(&rhs);
self
}
}
impl<Mode: SmartStringMode> Add<SmartString<Mode>> for String {
type Output = Self;
fn add(mut self, rhs: SmartString<Mode>) -> Self::Output {
self.push_str(&rhs);
self
}
}
impl<Mode: SmartStringMode> FromIterator<Self> for SmartString<Mode> {
fn from_iter<I: IntoIterator<Item = Self>>(iter: I) -> Self {
let mut out = Self::new();
out.extend(iter.into_iter());
out
}
}
impl<Mode: SmartStringMode> FromIterator<String> for SmartString<Mode> {
fn from_iter<I: IntoIterator<Item = String>>(iter: I) -> Self {
let mut out = Self::new();
out.extend(iter.into_iter());
out
}
}
impl<'a, Mode: SmartStringMode + 'a> FromIterator<&'a Self> for SmartString<Mode> {
fn from_iter<I: IntoIterator<Item = &'a Self>>(iter: I) -> Self {
let mut out = Self::new();
out.extend(iter.into_iter());
out
}
}
impl<'a, Mode: SmartStringMode> FromIterator<&'a str> for SmartString<Mode> {
fn from_iter<I: IntoIterator<Item = &'a str>>(iter: I) -> Self {
let mut out = Self::new();
out.extend(iter.into_iter());
out
}
}
impl<'a, Mode: SmartStringMode> FromIterator<&'a String> for SmartString<Mode> {
fn from_iter<I: IntoIterator<Item = &'a String>>(iter: I) -> Self {
let mut out = Self::new();
out.extend(iter.into_iter());
out
}
}
impl<Mode: SmartStringMode> FromIterator<char> for SmartString<Mode> {
fn from_iter<I: IntoIterator<Item = char>>(iter: I) -> Self {
let mut out = Self::new();
for ch in iter {
out.push(ch);
}
out
}
}
impl<Mode: SmartStringMode> FromStr for SmartString<Mode> {
type Err = Infallible;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(Self::from(s))
}
}
impl<Mode: SmartStringMode> Into<String> for SmartString<Mode> {
fn into(self) -> String {
match self.cast_into() {
StringCastInto::Boxed(string) => string.into_string(),
StringCastInto::Inline(string) => string.as_str().to_string(),
}
}
}
impl<Mode: SmartStringMode> PartialEq<str> for SmartString<Mode> {
fn eq(&self, other: &str) -> bool {
self.as_str() == other
}
}
impl<Mode: SmartStringMode> PartialEq<SmartString<Mode>> for &'_ str {
fn eq(&self, other: &SmartString<Mode>) -> bool {
other.eq(*self)
}
}
impl<Mode: SmartStringMode> PartialEq<SmartString<Mode>> for str {
fn eq(&self, other: &SmartString<Mode>) -> bool {
other.eq(self)
}
}
impl<Mode: SmartStringMode> PartialEq<String> for SmartString<Mode> {
fn eq(&self, other: &String) -> bool {
self.eq(other.as_str())
}
}
impl<Mode: SmartStringMode> PartialEq<SmartString<Mode>> for String {
fn eq(&self, other: &SmartString<Mode>) -> bool {
other.eq(self.as_str())
}
}
impl<Mode: SmartStringMode> PartialEq for SmartString<Mode> {
fn eq(&self, other: &Self) -> bool {
self.as_str() == other.as_str()
}
}
impl<Mode: SmartStringMode> Eq for SmartString<Mode> {}
impl<Mode: SmartStringMode> PartialOrd<str> for SmartString<Mode> {
fn partial_cmp(&self, other: &str) -> Option<Ordering> {
self.as_str().partial_cmp(other)
}
}
impl<Mode: SmartStringMode> PartialOrd for SmartString<Mode> {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
self.partial_cmp(other.as_str())
}
}
impl<Mode: SmartStringMode> Ord for SmartString<Mode> {
fn cmp(&self, other: &Self) -> Ordering {
self.as_str().cmp(other.as_str())
}
}
impl<Mode: SmartStringMode> Hash for SmartString<Mode> {
fn hash<H: Hasher>(&self, state: &mut H) {
self.as_str().hash(state)
}
}
impl<Mode: SmartStringMode> Debug for SmartString<Mode> {
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
Debug::fmt(self.as_str(), f)
}
}
impl<Mode: SmartStringMode> Display for SmartString<Mode> {
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
Display::fmt(self.as_str(), f)
}
}
impl<Mode: SmartStringMode> Write for SmartString<Mode> {
fn write_str(&mut self, string: &str) -> Result<(), Error> {
self.push_str(string);
Ok(())
}
}
#[cfg(any(test, feature = "test"))]
#[allow(missing_docs)]
pub mod test;