├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── Cargo.toml ├── README.md ├── LICENSE └── src └── lib.rs /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | **/*.rs.bk 3 | Cargo.lock 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | --- 2 | language: rust 3 | sudo: false 4 | 5 | rust: 6 | - stable 7 | 8 | os: 9 | - linux 10 | 11 | matrix: 12 | include: 13 | - os: linux 14 | rust: 1.39.0 15 | 16 | script: 17 | - cargo build --no-default-features 18 | - cargo test --no-default-features 19 | - cargo build 20 | - cargo test 21 | - rustup component add rustfmt 22 | - cargo fmt -- --check 23 | - rustup component add clippy 24 | - cargo clippy 25 | 26 | notifications: 27 | email: 28 | on_success: never 29 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 0.3.1 (September 13, 2024) 2 | 3 | ### Added 4 | - implement `inspect` 5 | 6 | # 0.3.0 (December 2, 2021) 7 | 8 | ### Changed 9 | - Depend on `bytes` 1.0 (#27) 10 | 11 | # 0.2.1 (June 27, 2019) 12 | 13 | ### Fixed 14 | - Stack overflow on `PartialEq` (#15). 15 | 16 | # 0.2.0 (March 6, 2019) 17 | 18 | ### Changed 19 | - Require byte store to implement `StableAsRef` (#10). 20 | 21 | # 0.1.3 (January 10, 2019) 22 | 23 | * Implement `Borrow`. 24 | * Implement `PartialEq`. 25 | 26 | # 0.1.2 (November 21, 2018) 27 | 28 | * Implement `DerefMut` and `Display` (#5). 29 | * Implement `from_str` (#7). 30 | 31 | # 0.1.1 (July 13, 2018) 32 | 33 | * Fix doc gen (#2). 34 | 35 | # 0.1.0 (January 11, 2018) 36 | 37 | * Initial release. 38 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "string" 3 | # When releasing to crates.io: 4 | # - Update html_root_url. 5 | # - Update CHANGELOG.md. 6 | # - Update README.md. 7 | # - Update doc URL. 8 | # - Create "v0.3.x" git tag. 9 | version = "0.3.1" 10 | license = "MIT" 11 | authors = ["Carl Lerche "] 12 | description = "A UTF-8 encoded string with configurable byte storage." 13 | documentation = "https://docs.rs/string/0.3.1/string/" 14 | homepage = "https://github.com/carllerche/string" 15 | repository = "https://github.com/carllerche/string" 16 | readme = "README.md" 17 | keywords = ["string"] 18 | categories = ["data-structures"] 19 | edition = "2018" 20 | 21 | [features] 22 | default = ["bytes"] 23 | valuable = ["inspect"] 24 | 25 | [badges.travis-ci] 26 | repository = "carllerche/string" 27 | branch = "master" 28 | 29 | [dependencies] 30 | bytes = { version = "1", optional = true } 31 | inspect = { version = "0.1", optional = true } 32 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # String 2 | 3 | A UTF-8 encoded string with configurable byte storage. 4 | 5 | [![Build Status](https://travis-ci.org/carllerche/string.svg?branch=master)](https://travis-ci.org/carllerche/string) 6 | [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT) 7 | [![Crates.io](https://img.shields.io/crates/v/string.svg?maxAge=2592000)](https://crates.io/crates/string) 8 | [![Documentation](https://docs.rs/string/badge.svg)](https://docs.rs/string/0.2.1/string/) 9 | 10 | ## Usage 11 | 12 | To use `string`, first add this to your `Cargo.toml`: 13 | 14 | ```toml 15 | [dependencies] 16 | string = "0.3.1" 17 | ``` 18 | 19 | Next, add this to your crate: 20 | 21 | ```rust 22 | extern crate string; 23 | 24 | use string::{String, TryFrom}; 25 | 26 | let s: String<[u8; 2]> = String::try_from([b'h', b'i']).unwrap(); 27 | assert_eq!(&s[..], "hi"); 28 | ``` 29 | 30 | See [documentation](https://docs.rs/string) for more details. 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2019 Carl Lerche 2 | 3 | Permission is hereby granted, free of charge, to any 4 | person obtaining a copy of this software and associated 5 | documentation files (the "Software"), to deal in the 6 | Software without restriction, including without 7 | limitation the rights to use, copy, modify, merge, 8 | publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software 10 | is furnished to do so, subject to the following 11 | conditions: 12 | 13 | The above copyright notice and this permission notice 14 | shall be included in all copies or substantial portions 15 | of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![deny(warnings, missing_docs, missing_debug_implementations, clippy::all)] 2 | #![doc(html_root_url = "https://docs.rs/string/0.3.1")] 3 | 4 | //! A UTF-8 encoded string with configurable byte storage. 5 | //! 6 | //! This crate provides `String`, a type similar to its std counterpart, but 7 | //! with one significant difference: the underlying byte storage is 8 | //! configurable. In other words, `String` is a marker type wrapping `T`, 9 | //! indicating that it represents a UTF-8 encoded string. 10 | //! 11 | //! For example, one can represent small strings (stack allocated) by wrapping 12 | //! an array: 13 | //! 14 | //! ``` 15 | //! # use string::*; 16 | //! let s: String<[u8; 2]> = String::try_from([b'h', b'i']).unwrap(); 17 | //! assert_eq!(&s[..], "hi"); 18 | //! ``` 19 | 20 | use std::{borrow, default::Default, fmt, hash, ops, str}; 21 | 22 | /// A UTF-8 encoded string with configurable byte storage. 23 | /// 24 | /// This type differs from `std::String` in that it is generic over the 25 | /// underlying byte storage, enabling it to use `Vec<[u8]>`, `&[u8]`, or third 26 | /// party types, such as [`Bytes`]. 27 | /// 28 | /// In order to construct `String` via any of the non-unsafe constructors, 29 | /// the backing storage needs to implement the `StableAsRef` marker trait. 30 | /// If you wish to construct `String` with a type that does not implement `StableAsRef`, 31 | /// you can use the `from_utf8_unchecked` constructor. 32 | /// 33 | /// [`Bytes`]: https://docs.rs/bytes/0.4.8/bytes/struct.Bytes.html 34 | #[derive(Clone, Eq, PartialEq, Ord, PartialOrd)] 35 | pub struct String> { 36 | value: T, 37 | } 38 | 39 | impl String { 40 | /// Get a reference to the underlying byte storage. 41 | /// 42 | /// # Examples 43 | /// 44 | /// ``` 45 | /// # use string::*; 46 | /// let s = String::new(); 47 | /// let vec = s.get_ref(); 48 | /// ``` 49 | pub fn get_ref(&self) -> &T { 50 | &self.value 51 | } 52 | 53 | /// Get a mutable reference to the underlying byte storage. 54 | /// 55 | /// # Safety 56 | /// 57 | /// It is inadvisable to directly manipulate the byte storage. This function 58 | /// is unsafe as the bytes could no longer be valid UTF-8 after mutation. 59 | /// 60 | /// # Examples 61 | /// 62 | /// ``` 63 | /// # use string::*; 64 | /// let mut s = String::new(); 65 | /// 66 | /// unsafe { 67 | /// let vec = s.get_mut(); 68 | /// } 69 | /// ``` 70 | pub unsafe fn get_mut(&mut self) -> &mut T { 71 | &mut self.value 72 | } 73 | 74 | /// Unwraps this `String`, returning the underlying byte storage. 75 | /// 76 | /// # Examples 77 | /// 78 | /// ``` 79 | /// # use string::*; 80 | /// let s = String::new(); 81 | /// let vec = s.into_inner(); 82 | /// ``` 83 | pub fn into_inner(self) -> T { 84 | self.value 85 | } 86 | 87 | /// Creates a new `String` from a &str. 88 | /// 89 | /// Use `TryFrom` for conversion from &[u8]. 90 | /// 91 | /// ``` 92 | /// # use string::*; 93 | /// let _: String> = String::from_str("nice str"); 94 | /// ``` 95 | #[allow(clippy::should_implement_trait)] 96 | pub fn from_str<'a>(src: &'a str) -> String 97 | where 98 | T: From<&'a [u8]> + StableAsRef, 99 | { 100 | let value: T = src.as_bytes().into(); 101 | Self { value } 102 | } 103 | } 104 | 105 | impl String { 106 | /// Creates a new empty `String`. 107 | /// 108 | /// Given that the `String` is empty, this will not allocate. 109 | /// 110 | /// # Examples 111 | /// 112 | /// Basic usage 113 | /// 114 | /// ``` 115 | /// let s = String::new(); 116 | /// assert_eq!(s, ""); 117 | /// ``` 118 | pub fn new() -> String { 119 | String::default() 120 | } 121 | } 122 | 123 | impl String 124 | where 125 | T: AsRef<[u8]>, 126 | { 127 | /// Converts the provided value to a `String` without checking that the 128 | /// given value is valid UTF-8. 129 | /// 130 | /// Use `TryFrom` for a safe conversion. 131 | /// 132 | /// # Safety 133 | /// 134 | /// You must ensure that: 135 | /// 136 | /// 1. The backing storage type `T` adheres to the contract as documented on the `StableAsRef` 137 | /// marker trait. 138 | /// 2. If `T` implements `AsRef<[u8]>` and/or `AsMut<[u8]>`, the byte slice returned 139 | /// by calling `as_ref` and/or `as_mut` on the provided value represents valid utf-8. 140 | pub unsafe fn from_utf8_unchecked(value: T) -> String { 141 | String { value } 142 | } 143 | } 144 | 145 | impl PartialEq for String 146 | where 147 | T: AsRef<[u8]>, 148 | { 149 | fn eq(&self, other: &str) -> bool { 150 | &self[..] == other 151 | } 152 | } 153 | 154 | #[allow(clippy::derive_hash_xor_eq)] 155 | impl hash::Hash for String 156 | where 157 | T: AsRef<[u8]>, 158 | { 159 | fn hash(&self, state: &mut H) { 160 | ops::Deref::deref(self).hash(state); 161 | } 162 | } 163 | 164 | impl ops::Deref for String 165 | where 166 | T: AsRef<[u8]>, 167 | { 168 | type Target = str; 169 | 170 | #[inline] 171 | fn deref(&self) -> &str { 172 | let b = self.value.as_ref(); 173 | // SAFETY: The `StableAsRef` marker trait ensures that 174 | // the impl of `AsRef<[u8]>` for `T` behaves sanely. 175 | unsafe { str::from_utf8_unchecked(b) } 176 | } 177 | } 178 | 179 | impl ops::DerefMut for String 180 | where 181 | T: AsRef<[u8]> + AsMut<[u8]>, 182 | { 183 | #[inline] 184 | fn deref_mut(&mut self) -> &mut str { 185 | let b = self.value.as_mut(); 186 | // SAFETY: The `StableAsRef` marker trait ensures that 187 | // the impl of `AsMut<[u8]>` for `T` behaves sanely. 188 | unsafe { str::from_utf8_unchecked_mut(b) } 189 | } 190 | } 191 | 192 | impl borrow::Borrow for String 193 | where 194 | T: AsRef<[u8]>, 195 | { 196 | fn borrow(&self) -> &str { 197 | &*self 198 | } 199 | } 200 | 201 | impl From<::std::string::String> for String<::std::string::String> { 202 | fn from(value: ::std::string::String) -> Self { 203 | String { value } 204 | } 205 | } 206 | 207 | impl Default for String 208 | where 209 | T: Default + StableAsRef, 210 | { 211 | fn default() -> Self { 212 | String { 213 | value: T::default(), 214 | } 215 | } 216 | } 217 | 218 | impl TryFrom for String 219 | where 220 | T: AsRef<[u8]> + StableAsRef, 221 | { 222 | type Error = str::Utf8Error; 223 | 224 | fn try_from(value: T) -> Result { 225 | let _ = str::from_utf8(value.as_ref())?; 226 | Ok(String { value }) 227 | } 228 | } 229 | 230 | impl> fmt::Debug for String { 231 | fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { 232 | (**self).fmt(fmt) 233 | } 234 | } 235 | 236 | impl> fmt::Display for String { 237 | fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { 238 | (**self).fmt(fmt) 239 | } 240 | } 241 | 242 | #[cfg(feature = "valuable")] 243 | impl inspect::Valuable for String 244 | where 245 | T: AsRef<[u8]>, 246 | { 247 | fn as_value(&self) -> inspect::Value<'_> { 248 | inspect::Value::String(&self[..]) 249 | } 250 | 251 | fn visit(&self, visit: &mut dyn inspect::Visit) { 252 | visit.visit_value(inspect::Value::String(self)); 253 | } 254 | } 255 | 256 | /// Attempt to construct `Self` via a conversion. 257 | /// 258 | /// This trait will be deprecated in favor of [std::convert::TryFrom] once it 259 | /// reaches stable Rust. 260 | pub trait TryFrom: Sized + sealed::Sealed { 261 | /// The type returned in the event of a conversion error. 262 | type Error; 263 | 264 | /// Performs the conversion. 265 | fn try_from(value: T) -> Result; 266 | } 267 | 268 | impl sealed::Sealed for String {} 269 | 270 | mod sealed { 271 | /// Private trait to this crate to prevent traits from being implemented in 272 | /// downstream crates. 273 | pub trait Sealed {} 274 | } 275 | 276 | /// Marker trait that indicates that a type is guaranteed safe to use as backing storage 277 | /// for `String`. 278 | /// 279 | /// In order to be safe, a storage type `T` needs to guarantee the following: 280 | /// 281 | /// - If `T` implements `AsRef<[u8]>` and/or `AsMut<[u8]>`, the contents of `T` as visible 282 | /// the byte slice returned by `as_ref` and `as_mut` may only be mutated through mutable 283 | /// references or owned access. In other words, no use of interior mutability. 284 | /// 285 | /// - If `T` implements `AsRef<[u8]>`, the `as_ref` method must always return the same 286 | /// slice of bytes (unless the storage is mutated). 287 | /// 288 | /// - If `T` implements `AsRef<[u8]>` and `AsMut<[u8]>`, the `as_mut` method must return 289 | /// a mutable reference to the same slice of bytes as the `as_ref` method returns. 290 | /// 291 | /// - If `T` implements `AsRef<[u8]>` and `Default`, the default value must represent the 292 | /// empty byte sequence. In other words, `T::default().as_ref().len() == 0`. 293 | /// 294 | /// - If `T` implements `AsRef<[u8]>` and `From<&[u8]>`, it must do so in such a way that 295 | /// the byte slice returned by `as_ref` is equal to the byte slice provided to the `from` 296 | /// method. 297 | pub unsafe trait StableAsRef {} 298 | 299 | unsafe impl<'a, T> StableAsRef for &'a T where T: StableAsRef {} 300 | unsafe impl<'a, T> StableAsRef for &'a mut T where T: StableAsRef {} 301 | unsafe impl StableAsRef for Box where T: StableAsRef {} 302 | unsafe impl StableAsRef for std::rc::Rc where T: StableAsRef {} 303 | unsafe impl StableAsRef for std::sync::Arc where T: StableAsRef {} 304 | 305 | unsafe impl StableAsRef for std::string::String {} 306 | unsafe impl StableAsRef for str {} 307 | unsafe impl StableAsRef for Vec {} 308 | unsafe impl StableAsRef for [u8] {} 309 | 310 | #[cfg(feature = "bytes")] 311 | unsafe impl StableAsRef for bytes::Bytes {} 312 | 313 | #[cfg(feature = "bytes")] 314 | unsafe impl StableAsRef for bytes::BytesMut {} 315 | 316 | macro_rules! array_impls { 317 | ($($len:expr)+) => { 318 | $( 319 | unsafe impl StableAsRef for [u8; $len] {} 320 | )+ 321 | } 322 | } 323 | 324 | array_impls!(0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16); 325 | 326 | #[cfg(test)] 327 | mod test { 328 | use super::*; 329 | 330 | #[test] 331 | fn test_from_std_string() { 332 | let s: String<_> = "hello".to_string().into(); 333 | assert_eq!(&s, "hello"); 334 | } 335 | 336 | #[test] 337 | fn test_from_str() { 338 | let _: String> = String::from_str("nice str"); 339 | } 340 | 341 | #[test] 342 | fn test_try_from_bytes() { 343 | let _ = String::try_from(b"nice bytes").unwrap(); 344 | } 345 | } 346 | --------------------------------------------------------------------------------