├── .gitignore ├── .travis.yml ├── COPYRIGHT ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md └── src ├── inline_string.rs ├── lib.rs ├── serde_impl.rs └── string_ext.rs /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | 3 | language: rust 4 | 5 | addons: 6 | apt: 7 | packages: 8 | - libcurl4-openssl-dev 9 | - libelf-dev 10 | - libdw-dev 11 | - binutils-dev 12 | 13 | rust: 14 | # - nightly 15 | - beta 16 | - stable 17 | 18 | before_script: 19 | - | 20 | pip install 'travis-cargo<0.2' --user && 21 | export PATH=$HOME/.local/bin:$PATH 22 | 23 | script: 24 | - | 25 | travis-cargo test -- --features serde && 26 | travis-cargo bench && 27 | travis-cargo --only stable doc 28 | 29 | after_success: 30 | - travis-cargo --only stable doc-upload 31 | - travis-cargo coveralls --no-sudo --verify 32 | 33 | env: 34 | global: 35 | - TRAVIS_CARGO_NIGHTLY_FEATURE=nightly 36 | - secure: ZtdohAQFnky0rW44bPjPGEEHtYOffjh6PDhsyZzFCQfut2UMtlAZ506/l07gyFvpzMK+FFTtOnnJSUpsiu7Ypsa6OtxGDd2iVbZ4vVVitlCTXbSb1R5AUARXCd8F2RlXG6MNmZ7nZc/OAqjQIngNEM44yq+a+5loabU7QtX9a/cw9wDdhFFTch9pwhttQehSJqhsf8dB19W4TiV+pCEgvawede8dP0Gv5YJ5BsU4Jzxwr/tuUOuYPhDVBaSw0Uvyc7iAIq5WNz3STodZhfNeMj4kP4Uy7CD17mHtS1IzF2ftF3hPC0/ALAPXmtpPMJSLzaH1I2yZXiujfSowEAKJY3XvYWoX8pRFT0gzuF6A1WHqCWDgzyZSoRwkWWNYRWMBeX58SMoB48Wj4ZUz6hDwwtvKAmNOBz4ieixEuJuxP4flC17lwt/mX5sYw8fLA0ttuZ8GjFIfmkXI6v3imb40Z8+sdZ0SW/TH8xxiRllqdmJ0Pwes7bgcdvzgWnuNrb6l6dSNyyU4QGWQQrQ6BmR9qAPKiK8blK7bJgsI4J/tj+BR5DjSx+jpCwUjDx9WsXSkwxp0cXM8cR4504S+1n0+efGqdhiq6k6ELVAplWEFRrEA+QSmuVHVb1asTVuUMS4gsA+Uh+fpgU0RxQp/LzXkAEjoKAu2IgjRJOPbWxyP1mY= 37 | -------------------------------------------------------------------------------- /COPYRIGHT: -------------------------------------------------------------------------------- 1 | Short version for non-lawyers: 2 | 3 | The inlinable_string crate is dual-licensed under Apache 2.0 and MIT terms. 4 | 5 | Longer version: 6 | 7 | The inlinable_string crate is copyright 2015, The inlinable_string crate 8 | Developers (given in the file Cargo.toml). 9 | 10 | Licensed under the Apache License, Version 2.0 or the MIT license , at your option. All files in the project 13 | carrying such notice may not be copied, modified, or distributed except 14 | according to those terms. 15 | 16 | Portions of this code, its interfaces, and documentation are forked from 17 | https://github.com/rust-lang/rust/blob/master/src/libcollections/string.rs which 18 | is copyright 2014, The Rust Project Developers and licensed under the Apache 19 | License, Version 2.0 or the MIT license , at your option. 22 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Nick Fitzgerald "] 3 | name = "inlinable_string" 4 | 5 | description = "The `inlinable_string` crate provides the `InlinableString` type -- an owned, grow-able UTF-8 string that stores small strings inline and avoids heap-allocation -- and the `StringExt` trait which abstracts string operations over both `std::string::String` and `InlinableString` (or even your own custom string type)." 6 | 7 | version = "0.1.15" 8 | edition = "2018" 9 | license = "Apache-2.0/MIT" 10 | keywords = ["string", "inline", "inlinable"] 11 | readme = "./README.md" 12 | documentation = "http://fitzgen.github.io/inlinable_string/inlinable_string/index.html" 13 | repository = "https://github.com/fitzgen/inlinable_string" 14 | 15 | [dependencies] 16 | 17 | [dependencies.serde] 18 | optional = true 19 | version = "1" 20 | 21 | [features] 22 | nightly = [] 23 | no_std = [] 24 | 25 | [dev-dependencies] 26 | serde_test = "1" 27 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 The Rust Project Developers 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # `inlinable_string` 2 | 3 | [![](http://meritbadge.herokuapp.com/inlinable_string)![](https://img.shields.io/crates/d/inlinable_string.png)](https://crates.io/crates/inlinable_string) 4 | 5 | [![Build Status](https://travis-ci.org/fitzgen/inlinable_string.png?branch=master)](https://travis-ci.org/fitzgen/inlinable_string) 6 | 7 | [![Coverage Status](https://coveralls.io/repos/fitzgen/inlinable_string/badge.svg?branch=master&service=github)](https://coveralls.io/github/fitzgen/inlinable_string?branch=master) 8 | 9 | The `inlinable_string` crate provides the `InlinableString` type — an 10 | owned, grow-able UTF-8 string that stores small strings inline and avoids 11 | heap-allocation — and the `StringExt` trait which abstracts string 12 | operations over both `std::string::String` and `InlinableString` (or even your 13 | own custom string type). 14 | 15 | `StringExt`'s API is mostly identical to `std::string::String`; unstable and 16 | deprecated methods are not included. A `StringExt` implementation is provided 17 | for both `std::string::String` and `InlinableString`. This enables 18 | `InlinableString` to generally work as a drop-in replacement for 19 | `std::string::String` and `&StringExt` to work with references to either type. 20 | 21 | ## But is it actually faster than using `std::string::String`? 22 | 23 | Here are some current (micro)benchmark results. I encourage you to verify them 24 | yourself by running `cargo bench --feature nightly` with a nightly Rust! I am 25 | also very open to adding more realistic and representative benchmarks! Share 26 | some ideas with me! 27 | 28 | Constructing from a large `&str`: 29 | 30 | ``` 31 | test benches::bench_inlinable_string_from_large ... bench: 32 ns/iter (+/- 6) 32 | test benches::bench_std_string_from_large ... bench: 31 ns/iter (+/- 10) 33 | ``` 34 | 35 | Constructing from a small `&str`: 36 | 37 | ``` 38 | test benches::bench_inlinable_string_from_small ... bench: 1 ns/iter (+/- 0) 39 | test benches::bench_std_string_from_small ... bench: 26 ns/iter (+/- 14) 40 | ``` 41 | 42 | Pushing a large `&str` onto an empty string: 43 | 44 | ``` 45 | test benches::bench_inlinable_string_push_str_large_onto_empty ... bench: 37 ns/iter (+/- 12) 46 | test benches::bench_std_string_push_str_large_onto_empty ... bench: 30 ns/iter (+/- 9) 47 | ``` 48 | 49 | Pushing a small `&str` onto an empty string: 50 | 51 | ``` 52 | test benches::bench_inlinable_string_push_str_small_onto_empty ... bench: 11 ns/iter (+/- 4) 53 | test benches::bench_std_string_push_str_small_onto_empty ... bench: 23 ns/iter (+/- 10) 54 | ``` 55 | 56 | Pushing a large `&str` onto a large string: 57 | 58 | ``` 59 | test benches::bench_inlinable_string_push_str_large_onto_large ... bench: 80 ns/iter (+/- 24) 60 | test benches::bench_std_string_push_str_large_onto_large ... bench: 78 ns/iter (+/- 23) 61 | ``` 62 | 63 | Pushing a small `&str` onto a small string: 64 | 65 | ``` 66 | test benches::bench_inlinable_string_push_str_small_onto_small ... bench: 17 ns/iter (+/- 6) 67 | test benches::bench_std_string_push_str_small_onto_small ... bench: 60 ns/iter (+/- 15) 68 | ``` 69 | 70 | TLDR: If your string's size tends to stay within `INLINE_STRING_CAPACITY`, then 71 | `InlinableString` is much faster. Crossing the threshold and forcing a promotion 72 | from inline storage to heap allocation will slow it down more than 73 | `std::string::String` and you can see the expected drop off in such cases, but 74 | that is generally a one time cost. Once the strings are already larger than 75 | `INLINE_STRING_CAPACITY`, then the performance difference is 76 | negligible. However, take all this with a grain of salt! These are very micro 77 | benchmarks and your (hashtag) Real World workload may differ greatly! 78 | 79 | ## Install 80 | 81 | Either 82 | 83 | $ cargo add inlinable_string 84 | 85 | or add this to your `Cargo.toml`: 86 | 87 | [dependencies] 88 | inlinable_string = "0.1.0" 89 | 90 | ## Documentation 91 | 92 | [Documentation](http://fitzgen.github.io/inlinable_string/inlinable_string/index.html) 93 | -------------------------------------------------------------------------------- /src/inline_string.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2015, The inlinable_string crate Developers. See the COPYRIGHT file 2 | // at the top-level directory of this distribution. 3 | // 4 | // Licensed under the Apache License, Version 2.0 or the MIT license , at your option. This file may not be 7 | // copied, modified, or distributed except according to those terms. 8 | 9 | //! A short UTF-8 string that uses inline storage and does no heap 10 | //! allocation. It may be no longer than `INLINE_STRING_CAPACITY` bytes long. 11 | //! 12 | //! The capacity restriction makes many operations that would otherwise be 13 | //! infallible on `std::string::String` fallible. Additionally, many trait 14 | //! interfaces don't allow returning an error when a string runs out of space, 15 | //! and so the trait implementation simply panics. As such, `InlineString` does 16 | //! not implement `StringExt` and is ***not*** a drop-in replacement for 17 | //! `std::string::String` in the way that `inlinable_string::InlinableString` 18 | //! aims to be, and is generally difficult to work with. It is not recommended 19 | //! to use this type directly unless you really, really want to avoid heap 20 | //! allocation, can live with the imposed size restrictions, and are willing 21 | //! work around potential sources of panics (eg, in the `From` trait 22 | //! implementation). 23 | //! 24 | //! # Examples 25 | //! 26 | //! ``` 27 | //! use inlinable_string::InlineString; 28 | //! 29 | //! let mut s = InlineString::new(); 30 | //! assert!(s.push_str("hi world").is_ok()); 31 | //! assert_eq!(s, "hi world"); 32 | //! 33 | //! assert!(s.push_str("a really long string that is much bigger than `INLINE_STRING_CAPACITY`").is_err()); 34 | //! assert_eq!(s, "hi world"); 35 | //! ``` 36 | 37 | use alloc::borrow; 38 | use core::fmt; 39 | use core::hash; 40 | use core::ops; 41 | use core::ptr; 42 | use core::str; 43 | 44 | 45 | /// The capacity (in bytes) of inline storage for small strings. 46 | /// `InlineString::len()` may never be larger than this. 47 | /// 48 | /// Sometime in the future, when Rust's generics support specializing with 49 | /// compile-time static integers, this number should become configurable. 50 | #[cfg(target_pointer_width = "64")] 51 | pub const INLINE_STRING_CAPACITY: usize = 30; 52 | #[cfg(target_pointer_width = "32")] 53 | pub const INLINE_STRING_CAPACITY: usize = 14; 54 | 55 | /// A short UTF-8 string that uses inline storage and does no heap allocation. 56 | /// 57 | /// See the [module level documentation](./index.html) for more. 58 | #[derive(Clone, Debug, Eq)] 59 | pub struct InlineString { 60 | length: u8, 61 | bytes: [u8; INLINE_STRING_CAPACITY], 62 | } 63 | 64 | /// The error returned when there is not enough space in a `InlineString` for the 65 | /// requested operation. 66 | #[derive(Debug, PartialEq)] 67 | pub struct NotEnoughSpaceError; 68 | 69 | impl AsRef for InlineString { 70 | fn as_ref(&self) -> &str { 71 | self.assert_sanity(); 72 | unsafe { str::from_utf8_unchecked(&self.bytes[..self.len()]) } 73 | } 74 | } 75 | 76 | impl AsRef<[u8]> for InlineString { 77 | #[inline] 78 | fn as_ref(&self) -> &[u8] { 79 | self.as_bytes() 80 | } 81 | } 82 | 83 | impl AsMut for InlineString { 84 | fn as_mut(&mut self) -> &mut str { 85 | self.assert_sanity(); 86 | let length = self.len(); 87 | unsafe { str::from_utf8_unchecked_mut(&mut self.bytes[..length]) } 88 | } 89 | } 90 | 91 | impl AsMut<[u8]> for InlineString { 92 | #[inline] 93 | fn as_mut(&mut self) -> &mut [u8] { 94 | self.assert_sanity(); 95 | let length = self.len(); 96 | &mut self.bytes[0..length] 97 | } 98 | } 99 | 100 | /// Create a `InlineString` from the given `&str`. 101 | /// 102 | /// # Panics 103 | /// 104 | /// If the given string's size is greater than `INLINE_STRING_CAPACITY`, this 105 | /// method panics. 106 | impl<'a> From<&'a str> for InlineString { 107 | fn from(string: &'a str) -> InlineString { 108 | let string_len = string.len(); 109 | assert!(string_len <= INLINE_STRING_CAPACITY); 110 | 111 | let mut ss = InlineString::new(); 112 | unsafe { 113 | ptr::copy_nonoverlapping(string.as_ptr(), ss.bytes.as_mut_ptr(), string_len); 114 | } 115 | ss.length = string_len as u8; 116 | 117 | ss.assert_sanity(); 118 | ss 119 | } 120 | } 121 | 122 | impl fmt::Display for InlineString { 123 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { 124 | self.assert_sanity(); 125 | write!(f, "{}", self as &str) 126 | } 127 | } 128 | 129 | impl fmt::Write for InlineString { 130 | fn write_char(&mut self, ch: char) -> Result<(), fmt::Error> { 131 | self.push(ch).map_err(|_| fmt::Error) 132 | } 133 | fn write_str(&mut self, s: &str) -> Result<(), fmt::Error> { 134 | self.push_str(s).map_err(|_| fmt::Error) 135 | } 136 | } 137 | 138 | impl hash::Hash for InlineString { 139 | #[inline] 140 | fn hash(&self, hasher: &mut H) { 141 | (**self).hash(hasher) 142 | } 143 | } 144 | 145 | impl ops::Index> for InlineString { 146 | type Output = str; 147 | 148 | #[inline] 149 | fn index(&self, index: ops::Range) -> &str { 150 | self.assert_sanity(); 151 | &self[..][index] 152 | } 153 | } 154 | 155 | impl ops::Index> for InlineString { 156 | type Output = str; 157 | 158 | #[inline] 159 | fn index(&self, index: ops::RangeTo) -> &str { 160 | self.assert_sanity(); 161 | &self[..][index] 162 | } 163 | } 164 | 165 | impl ops::Index> for InlineString { 166 | type Output = str; 167 | 168 | #[inline] 169 | fn index(&self, index: ops::RangeFrom) -> &str { 170 | self.assert_sanity(); 171 | &self[..][index] 172 | } 173 | } 174 | 175 | impl ops::Index for InlineString { 176 | type Output = str; 177 | 178 | #[inline] 179 | fn index(&self, _index: ops::RangeFull) -> &str { 180 | self.assert_sanity(); 181 | unsafe { str::from_utf8_unchecked(&self.bytes[..self.len()]) } 182 | } 183 | } 184 | 185 | impl ops::IndexMut> for InlineString { 186 | #[inline] 187 | fn index_mut(&mut self, index: ops::Range) -> &mut str { 188 | self.assert_sanity(); 189 | &mut self[..][index] 190 | } 191 | } 192 | 193 | impl ops::IndexMut> for InlineString { 194 | #[inline] 195 | fn index_mut(&mut self, index: ops::RangeTo) -> &mut str { 196 | self.assert_sanity(); 197 | &mut self[..][index] 198 | } 199 | } 200 | 201 | impl ops::IndexMut> for InlineString { 202 | #[inline] 203 | fn index_mut(&mut self, index: ops::RangeFrom) -> &mut str { 204 | self.assert_sanity(); 205 | &mut self[..][index] 206 | } 207 | } 208 | 209 | impl ops::IndexMut for InlineString { 210 | #[inline] 211 | fn index_mut(&mut self, _index: ops::RangeFull) -> &mut str { 212 | self.assert_sanity(); 213 | let length = self.len(); 214 | unsafe { str::from_utf8_unchecked_mut(&mut self.bytes[..length]) } 215 | } 216 | } 217 | 218 | impl ops::Deref for InlineString { 219 | type Target = str; 220 | 221 | #[inline] 222 | fn deref(&self) -> &str { 223 | self.assert_sanity(); 224 | unsafe { str::from_utf8_unchecked(&self.bytes[..self.len()]) } 225 | } 226 | } 227 | 228 | impl ops::DerefMut for InlineString { 229 | #[inline] 230 | fn deref_mut(&mut self) -> &mut str { 231 | self.assert_sanity(); 232 | let length = self.len(); 233 | unsafe { str::from_utf8_unchecked_mut(&mut self.bytes[..length]) } 234 | } 235 | } 236 | 237 | impl Default for InlineString { 238 | #[inline] 239 | fn default() -> InlineString { 240 | InlineString::new() 241 | } 242 | } 243 | 244 | impl PartialEq for InlineString { 245 | #[inline] 246 | fn eq(&self, rhs: &InlineString) -> bool { 247 | self.assert_sanity(); 248 | rhs.assert_sanity(); 249 | PartialEq::eq(&self[..], &rhs[..]) 250 | } 251 | } 252 | 253 | macro_rules! impl_eq { 254 | ($lhs:ty, $rhs: ty) => { 255 | impl<'a> PartialEq<$rhs> for $lhs { 256 | #[inline] 257 | fn eq(&self, other: &$rhs) -> bool { 258 | PartialEq::eq(&self[..], &other[..]) 259 | } 260 | } 261 | 262 | impl<'a> PartialEq<$lhs> for $rhs { 263 | #[inline] 264 | fn eq(&self, other: &$lhs) -> bool { 265 | PartialEq::eq(&self[..], &other[..]) 266 | } 267 | } 268 | }; 269 | } 270 | 271 | impl_eq! { InlineString, str } 272 | impl_eq! { InlineString, &'a str } 273 | impl_eq! { borrow::Cow<'a, str>, InlineString } 274 | 275 | impl InlineString { 276 | #[cfg_attr(feature = "nightly", allow(inline_always))] 277 | #[inline(always)] 278 | fn assert_sanity(&self) { 279 | debug_assert!( 280 | self.length as usize <= INLINE_STRING_CAPACITY, 281 | "inlinable_string: internal error: length greater than capacity" 282 | ); 283 | debug_assert!( 284 | str::from_utf8(&self.bytes[0..self.length as usize]).is_ok(), 285 | "inlinable_string: internal error: contents are not valid UTF-8!" 286 | ); 287 | } 288 | 289 | /// Creates a new string buffer initialized with the empty string. 290 | /// 291 | /// # Examples 292 | /// 293 | /// ``` 294 | /// use inlinable_string::InlineString; 295 | /// 296 | /// let s = InlineString::new(); 297 | /// ``` 298 | #[inline] 299 | pub fn new() -> InlineString { 300 | InlineString { 301 | length: 0, 302 | bytes: [0; INLINE_STRING_CAPACITY], 303 | } 304 | } 305 | 306 | /// Returns the underlying byte buffer, encoded as UTF-8. Trailing bytes are 307 | /// zeroed. 308 | /// 309 | /// # Examples 310 | /// 311 | /// ``` 312 | /// use inlinable_string::InlineString; 313 | /// 314 | /// let s = InlineString::from("hello"); 315 | /// let bytes = s.into_bytes(); 316 | /// assert_eq!(&bytes[0..5], [104, 101, 108, 108, 111]); 317 | /// ``` 318 | #[inline] 319 | pub fn into_bytes(mut self) -> [u8; INLINE_STRING_CAPACITY] { 320 | self.assert_sanity(); 321 | for i in self.len()..INLINE_STRING_CAPACITY { 322 | self.bytes[i] = 0; 323 | } 324 | self.bytes 325 | } 326 | 327 | /// Pushes the given string onto this string buffer. 328 | /// 329 | /// # Examples 330 | /// 331 | /// ``` 332 | /// use inlinable_string::InlineString; 333 | /// 334 | /// let mut s = InlineString::from("foo"); 335 | /// s.push_str("bar"); 336 | /// assert_eq!(s, "foobar"); 337 | /// ``` 338 | #[inline] 339 | pub fn push_str(&mut self, string: &str) -> Result<(), NotEnoughSpaceError> { 340 | self.assert_sanity(); 341 | 342 | let string_len = string.len(); 343 | let new_length = self.len() + string_len; 344 | 345 | if new_length > INLINE_STRING_CAPACITY { 346 | return Err(NotEnoughSpaceError); 347 | } 348 | 349 | unsafe { 350 | ptr::copy_nonoverlapping( 351 | string.as_ptr(), 352 | self.bytes.as_mut_ptr().offset(self.length as isize), 353 | string_len, 354 | ); 355 | } 356 | self.length = new_length as u8; 357 | 358 | self.assert_sanity(); 359 | Ok(()) 360 | } 361 | 362 | /// Adds the given character to the end of the string. 363 | /// 364 | /// # Examples 365 | /// 366 | /// ``` 367 | /// use inlinable_string::InlineString; 368 | /// 369 | /// let mut s = InlineString::from("abc"); 370 | /// s.push('1'); 371 | /// s.push('2'); 372 | /// s.push('3'); 373 | /// assert_eq!(s, "abc123"); 374 | /// ``` 375 | #[inline] 376 | pub fn push(&mut self, ch: char) -> Result<(), NotEnoughSpaceError> { 377 | self.assert_sanity(); 378 | 379 | let char_len = ch.len_utf8(); 380 | let new_length = self.len() + char_len; 381 | 382 | if new_length > INLINE_STRING_CAPACITY { 383 | return Err(NotEnoughSpaceError); 384 | } 385 | 386 | { 387 | let mut slice = &mut self.bytes[self.length as usize..INLINE_STRING_CAPACITY]; 388 | ch.encode_utf8(&mut slice); 389 | } 390 | self.length = new_length as u8; 391 | 392 | self.assert_sanity(); 393 | Ok(()) 394 | } 395 | 396 | /// Works with the underlying buffer as a byte slice. 397 | /// 398 | /// # Examples 399 | /// 400 | /// ``` 401 | /// use inlinable_string::InlineString; 402 | /// 403 | /// let s = InlineString::from("hello"); 404 | /// assert_eq!(s.as_bytes(), [104, 101, 108, 108, 111]); 405 | /// ``` 406 | #[inline] 407 | pub fn as_bytes(&self) -> &[u8] { 408 | self.assert_sanity(); 409 | &self.bytes[0..self.len()] 410 | } 411 | 412 | /// Shortens a string to the specified length. 413 | /// 414 | /// # Panics 415 | /// 416 | /// Panics if `new_len` > current length, or if `new_len` is not a character 417 | /// boundary. 418 | /// 419 | /// # Examples 420 | /// 421 | /// ``` 422 | /// use inlinable_string::InlineString; 423 | /// 424 | /// let mut s = InlineString::from("hello"); 425 | /// s.truncate(2); 426 | /// assert_eq!(s, "he"); 427 | /// ``` 428 | #[inline] 429 | pub fn truncate(&mut self, new_len: usize) { 430 | self.assert_sanity(); 431 | 432 | assert!( 433 | self.is_char_boundary(new_len), 434 | "inlinable_string::InlineString::truncate: new_len is not a character 435 | boundary" 436 | ); 437 | assert!(new_len <= self.len()); 438 | 439 | self.length = new_len as u8; 440 | self.assert_sanity(); 441 | } 442 | 443 | /// Removes the last character from the string buffer and returns it. 444 | /// Returns `None` if this string buffer is empty. 445 | /// 446 | /// # Examples 447 | /// 448 | /// ``` 449 | /// use inlinable_string::InlineString; 450 | /// 451 | /// let mut s = InlineString::from("foo"); 452 | /// assert_eq!(s.pop(), Some('o')); 453 | /// assert_eq!(s.pop(), Some('o')); 454 | /// assert_eq!(s.pop(), Some('f')); 455 | /// assert_eq!(s.pop(), None); 456 | /// ``` 457 | #[inline] 458 | pub fn pop(&mut self) -> Option { 459 | self.assert_sanity(); 460 | 461 | match self.char_indices().rev().next() { 462 | None => None, 463 | Some((idx, ch)) => { 464 | self.length = idx as u8; 465 | self.assert_sanity(); 466 | Some(ch) 467 | } 468 | } 469 | } 470 | 471 | /// Removes the character from the string buffer at byte position `idx` and 472 | /// returns it. 473 | /// 474 | /// # Panics 475 | /// 476 | /// If `idx` does not lie on a character boundary, or if it is out of 477 | /// bounds, then this function will panic. 478 | /// 479 | /// # Examples 480 | /// 481 | /// ``` 482 | /// use inlinable_string::InlineString; 483 | /// 484 | /// let mut s = InlineString::from("foo"); 485 | /// assert_eq!(s.remove(0), 'f'); 486 | /// assert_eq!(s.remove(1), 'o'); 487 | /// assert_eq!(s.remove(0), 'o'); 488 | /// ``` 489 | #[inline] 490 | pub fn remove(&mut self, idx: usize) -> char { 491 | self.assert_sanity(); 492 | assert!(idx < self.len()); 493 | 494 | let ch = self 495 | .get(idx..) 496 | .expect( 497 | "inlinable_string::InlineString::remove: idx does not lie on a 498 | character boundary", 499 | ) 500 | .chars() 501 | .next() 502 | .expect("Should be `Some` because `idx < self.len()`"); 503 | let char_len = ch.len_utf8(); 504 | let next = idx + char_len; 505 | 506 | unsafe { 507 | let ptr = self.bytes.as_mut_ptr(); 508 | 509 | ptr::copy( 510 | ptr.add(next), 511 | ptr.add(idx), 512 | self.len() - next, 513 | ); 514 | } 515 | self.length -= char_len as u8; 516 | 517 | self.assert_sanity(); 518 | ch 519 | } 520 | 521 | /// Inserts the given bytes at the given position of the string. 522 | unsafe fn insert_bytes(&mut self, idx: usize, bytes: &[u8]) -> Result<(), NotEnoughSpaceError> { 523 | let len = self.len(); 524 | let amt = bytes.len(); 525 | 526 | // This subtraction does not overflow because `INLINE_STRING_CAPACITY >= self.len()` holds. 527 | if amt > INLINE_STRING_CAPACITY - len { 528 | return Err(NotEnoughSpaceError); 529 | } 530 | 531 | let ptr = self.bytes.as_mut_ptr().add(idx); 532 | 533 | // Shift the latter part. 534 | ptr::copy( 535 | ptr, 536 | ptr.add(amt), 537 | len - idx, 538 | ); 539 | // Copy the bytes into the buffer. 540 | ptr::copy(bytes.as_ptr(), self.bytes.as_mut_ptr().add(idx), amt); 541 | // `amt` is less than `u8::MAX` becuase `INLINE_STRING_CAPACITY < u8::MAX` holds. 542 | self.length += amt as u8; 543 | 544 | Ok(()) 545 | } 546 | 547 | /// Inserts a character into the string buffer at byte position `idx`. 548 | /// 549 | /// # Examples 550 | /// 551 | /// ``` 552 | /// use inlinable_string::InlineString; 553 | /// 554 | /// let mut s = InlineString::from("foo"); 555 | /// s.insert(2, 'f'); 556 | /// assert!(s == "fofo"); 557 | /// ``` 558 | /// 559 | /// # Panics 560 | /// 561 | /// If `idx` does not lie on a character boundary or is out of bounds, then 562 | /// this function will panic. 563 | #[inline] 564 | pub fn insert(&mut self, idx: usize, ch: char) -> Result<(), NotEnoughSpaceError> { 565 | self.assert_sanity(); 566 | assert!(idx <= self.len()); 567 | 568 | let mut bits = [0; 4]; 569 | let bits = ch.encode_utf8(&mut bits).as_bytes(); 570 | 571 | unsafe { 572 | self.insert_bytes(idx, bits)?; 573 | } 574 | 575 | self.assert_sanity(); 576 | Ok(()) 577 | } 578 | 579 | /// Inserts a string into the string buffer at byte position `idx`. 580 | /// 581 | /// # Examples 582 | /// 583 | /// ``` 584 | /// use inlinable_string::InlineString; 585 | /// 586 | /// let mut s = InlineString::from("foo"); 587 | /// s.insert_str(2, "bar"); 588 | /// assert!(s == "fobaro"); 589 | /// ``` 590 | #[inline] 591 | pub fn insert_str(&mut self, idx: usize, string: &str) -> Result<(), NotEnoughSpaceError> { 592 | self.assert_sanity(); 593 | assert!(idx <= self.len()); 594 | 595 | unsafe { 596 | self.insert_bytes(idx, string.as_bytes())?; 597 | } 598 | 599 | self.assert_sanity(); 600 | Ok(()) 601 | } 602 | 603 | /// Views the internal string buffer as a mutable sequence of bytes. 604 | /// 605 | /// # Safety 606 | /// 607 | /// This is unsafe because it does not check to ensure that the resulting 608 | /// string will be valid UTF-8. 609 | /// 610 | /// # Examples 611 | /// 612 | /// ``` 613 | /// use inlinable_string::InlineString; 614 | /// 615 | /// let mut s = InlineString::from("hello"); 616 | /// unsafe { 617 | /// let slice = s.as_mut_slice(); 618 | /// assert!(slice == &[104, 101, 108, 108, 111]); 619 | /// slice.reverse(); 620 | /// } 621 | /// assert_eq!(s, "olleh"); 622 | /// ``` 623 | #[inline] 624 | pub unsafe fn as_mut_slice(&mut self) -> &mut [u8] { 625 | self.assert_sanity(); 626 | &mut self.bytes[0..self.length as usize] 627 | } 628 | 629 | /// Returns the number of bytes in this string. 630 | /// 631 | /// # Examples 632 | /// 633 | /// ``` 634 | /// use inlinable_string::InlineString; 635 | /// 636 | /// let a = InlineString::from("foo"); 637 | /// assert_eq!(a.len(), 3); 638 | /// ``` 639 | #[inline] 640 | pub fn len(&self) -> usize { 641 | self.assert_sanity(); 642 | self.length as usize 643 | } 644 | 645 | /// Returns true if the string contains no bytes 646 | /// 647 | /// # Examples 648 | /// 649 | /// ``` 650 | /// use inlinable_string::InlineString; 651 | /// 652 | /// let mut v = InlineString::new(); 653 | /// assert!(v.is_empty()); 654 | /// v.push('a'); 655 | /// assert!(!v.is_empty()); 656 | /// ``` 657 | #[inline] 658 | pub fn is_empty(&self) -> bool { 659 | self.assert_sanity(); 660 | self.length == 0 661 | } 662 | 663 | /// Truncates the string, returning it to 0 length. 664 | /// 665 | /// # Examples 666 | /// 667 | /// ``` 668 | /// use inlinable_string::InlineString; 669 | /// 670 | /// let mut s = InlineString::from("foo"); 671 | /// s.clear(); 672 | /// assert!(s.is_empty()); 673 | /// ``` 674 | #[inline] 675 | pub fn clear(&mut self) { 676 | self.assert_sanity(); 677 | self.length = 0; 678 | self.assert_sanity(); 679 | } 680 | } 681 | 682 | #[cfg(test)] 683 | mod tests { 684 | use alloc::string::String; 685 | use super::{InlineString, NotEnoughSpaceError, INLINE_STRING_CAPACITY}; 686 | 687 | #[test] 688 | fn test_push_str() { 689 | let mut s = InlineString::new(); 690 | assert!(s.push_str("small").is_ok()); 691 | assert_eq!(s, "small"); 692 | 693 | let long_str = "this is a really long string that is much larger than 694 | INLINE_STRING_CAPACITY and so cannot be stored inline."; 695 | assert_eq!(s.push_str(long_str), Err(NotEnoughSpaceError)); 696 | assert_eq!(s, "small"); 697 | } 698 | 699 | #[test] 700 | fn test_push() { 701 | let mut s = InlineString::new(); 702 | 703 | for _ in 0..INLINE_STRING_CAPACITY { 704 | assert!(s.push('a').is_ok()); 705 | } 706 | 707 | assert_eq!(s.push('a'), Err(NotEnoughSpaceError)); 708 | } 709 | 710 | #[test] 711 | fn test_insert() { 712 | let mut s = InlineString::new(); 713 | 714 | for _ in 0..INLINE_STRING_CAPACITY { 715 | assert!(s.insert(0, 'a').is_ok()); 716 | } 717 | 718 | assert_eq!(s.insert(0, 'a'), Err(NotEnoughSpaceError)); 719 | } 720 | 721 | #[test] 722 | fn test_write() { 723 | use core::fmt::{Error, Write}; 724 | 725 | let mut s = InlineString::new(); 726 | let mut normal_string = String::new(); 727 | 728 | for _ in 0..INLINE_STRING_CAPACITY { 729 | assert!(write!(&mut s, "a").is_ok()); 730 | assert!(write!(&mut normal_string, "a").is_ok()); 731 | } 732 | 733 | assert_eq!(write!(&mut s, "a"), Err(Error)); 734 | assert_eq!(&normal_string[..], &s[..]); 735 | } 736 | } 737 | 738 | #[cfg(test)] 739 | #[cfg(feature = "nightly")] 740 | mod benches { 741 | use test::Bencher; 742 | 743 | #[bench] 744 | fn its_fast(_b: &mut Bencher) {} 745 | } 746 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2015, The inlinable_string crate Developers. See the COPYRIGHT file 2 | // at the top-level directory of this distribution. 3 | // 4 | // Licensed under the Apache License, Version 2.0 or the MIT license , at your option. This file may not be 7 | // copied, modified, or distributed except according to those terms. 8 | 9 | //! The `inlinable_string` crate provides the 10 | //! [`InlinableString`](./enum.InlinableString.html) type — an owned, 11 | //! grow-able UTF-8 string that stores small strings inline and avoids 12 | //! heap-allocation — and the 13 | //! [`StringExt`](./string_ext/trait.StringExt.html) trait which abstracts 14 | //! string operations over both `std::string::String` and `InlinableString` (or 15 | //! even your own custom string type). 16 | //! 17 | //! `StringExt`'s API is mostly identical to `std::string::String`; unstable and 18 | //! deprecated methods are not included. A `StringExt` implementation is 19 | //! provided for both `std::string::String` and `InlinableString`. This enables 20 | //! `InlinableString` to generally work as a drop-in replacement for 21 | //! `std::string::String` and `&StringExt` to work with references to either 22 | //! type. 23 | //! 24 | //! # Examples 25 | //! 26 | //! ``` 27 | //! use inlinable_string::{InlinableString, StringExt}; 28 | //! 29 | //! // Small strings are stored inline and don't perform heap-allocation. 30 | //! let mut s = InlinableString::from("small"); 31 | //! assert_eq!(s.capacity(), inlinable_string::INLINE_STRING_CAPACITY); 32 | //! 33 | //! // Inline strings are transparently promoted to heap-allocated strings when 34 | //! // they grow too big. 35 | //! s.push_str("a really long string that's bigger than `INLINE_STRING_CAPACITY`"); 36 | //! assert!(s.capacity() > inlinable_string::INLINE_STRING_CAPACITY); 37 | //! 38 | //! // This method can work on strings potentially stored inline on the stack, 39 | //! // on the heap, or plain old `std::string::String`s! 40 | //! fn takes_a_string_reference(string: &mut StringExt) { 41 | //! // Do something with the string... 42 | //! string.push_str("it works!"); 43 | //! } 44 | //! 45 | //! let mut s1 = String::from("this is a plain std::string::String"); 46 | //! let mut s2 = InlinableString::from("inline"); 47 | //! 48 | //! // Both work! 49 | //! takes_a_string_reference(&mut s1); 50 | //! takes_a_string_reference(&mut s2); 51 | //! ``` 52 | //! 53 | //! # Porting Your Code 54 | //! 55 | //! * If `my_string` is always on the stack: `let my_string = String::new();` → 56 | //! `let my_string = InlinableString::new();` 57 | //! 58 | //! * `fn foo(string: &mut String) { ... }` → `fn foo(string: &mut StringExt) { ... }` 59 | //! 60 | //! * `fn foo(string: &str) { ... }` does not need to be modified. 61 | //! 62 | //! * `struct S { member: String }` is a little trickier. If `S` is always stack 63 | //! allocated, it probably makes sense to make `member` be of type 64 | //! `InlinableString`. If `S` is heap-allocated and `member` is *always* small, 65 | //! consider using the more restrictive 66 | //! [`InlineString`](./inline_string/struct.InlineString.html) type. If `member` is 67 | //! not always small, then it should probably be left as a `String`. 68 | //! 69 | //! # Serialization 70 | //! 71 | //! `InlinableString` implements [`serde`][serde-docs]'s `Serialize` and `Deserialize` traits. 72 | //! Add the `serde` feature to your `Cargo.toml` to enable serialization. 73 | //! 74 | //! [serde-docs]: https://serde.rs 75 | 76 | #![forbid(missing_docs)] 77 | #![cfg_attr(feature = "nightly", feature(plugin))] 78 | #![cfg_attr(all(test, feature = "nightly"), feature(test))] 79 | #![cfg_attr(feature = "no_std", no_std)] 80 | 81 | #[allow(unused_imports)] 82 | #[cfg_attr(feature = "no_std", macro_use)] 83 | extern crate alloc; 84 | 85 | #[cfg(test)] 86 | #[cfg(feature = "nightly")] 87 | extern crate test; 88 | 89 | #[cfg(feature = "serde")] 90 | mod serde_impl; 91 | 92 | pub mod inline_string; 93 | pub mod string_ext; 94 | 95 | pub use inline_string::{InlineString, INLINE_STRING_CAPACITY}; 96 | pub use string_ext::StringExt; 97 | 98 | use alloc::borrow::{Borrow, Cow}; 99 | use alloc::vec::Vec; 100 | use alloc::string::{FromUtf16Error, FromUtf8Error, String}; 101 | use core::cmp::Ordering; 102 | use core::convert; 103 | use core::fmt; 104 | use core::hash; 105 | use core::iter; 106 | use core::mem; 107 | use core::ops; 108 | use core::str::FromStr; 109 | 110 | /// An owned, grow-able UTF-8 string that allocates short strings inline on the 111 | /// stack. 112 | /// 113 | /// See the [module level documentation](./index.html) for more. 114 | #[derive(Clone, Eq)] 115 | pub enum InlinableString { 116 | /// A heap-allocated string. 117 | Heap(String), 118 | /// A small string stored inline. 119 | Inline(InlineString), 120 | } 121 | 122 | impl fmt::Debug for InlinableString { 123 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 124 | fmt::Debug::fmt(&self as &str, f) 125 | } 126 | } 127 | 128 | impl iter::FromIterator for InlinableString { 129 | fn from_iter>(iter: I) -> InlinableString { 130 | let mut buf = InlinableString::new(); 131 | buf.extend(iter); 132 | buf 133 | } 134 | } 135 | 136 | impl<'a> iter::FromIterator<&'a str> for InlinableString { 137 | fn from_iter>(iter: I) -> InlinableString { 138 | let mut buf = InlinableString::new(); 139 | buf.extend(iter); 140 | buf 141 | } 142 | } 143 | 144 | impl Extend for InlinableString { 145 | fn extend>(&mut self, iterable: I) { 146 | let iterator = iterable.into_iter(); 147 | let (lower_bound, _) = iterator.size_hint(); 148 | self.reserve(lower_bound); 149 | for ch in iterator { 150 | self.push(ch); 151 | } 152 | } 153 | } 154 | 155 | impl<'a> Extend<&'a char> for InlinableString { 156 | fn extend>(&mut self, iter: I) { 157 | self.extend(iter.into_iter().cloned()); 158 | } 159 | } 160 | 161 | impl<'a> Extend<&'a str> for InlinableString { 162 | fn extend>(&mut self, iterable: I) { 163 | let iterator = iterable.into_iter(); 164 | let (lower_bound, _) = iterator.size_hint(); 165 | self.reserve(lower_bound); 166 | for s in iterator { 167 | self.push_str(s); 168 | } 169 | } 170 | } 171 | 172 | impl<'a> ops::Add<&'a str> for InlinableString { 173 | type Output = InlinableString; 174 | 175 | #[inline] 176 | fn add(mut self, other: &str) -> InlinableString { 177 | self.push_str(other); 178 | self 179 | } 180 | } 181 | 182 | impl PartialOrd for InlinableString { 183 | fn partial_cmp(&self, rhs: &InlinableString) -> Option { 184 | Some(Ord::cmp(&self[..], &rhs[..])) 185 | } 186 | } 187 | 188 | impl Ord for InlinableString { 189 | #[inline] 190 | fn cmp(&self, rhs: &InlinableString) -> Ordering { 191 | Ord::cmp(&self[..], &rhs[..]) 192 | } 193 | } 194 | 195 | impl hash::Hash for InlinableString { 196 | #[inline] 197 | fn hash(&self, hasher: &mut H) { 198 | (**self).hash(hasher) 199 | } 200 | } 201 | 202 | impl Borrow for InlinableString { 203 | fn borrow(&self) -> &str { 204 | &*self 205 | } 206 | } 207 | 208 | impl AsRef for InlinableString { 209 | fn as_ref(&self) -> &str { 210 | match *self { 211 | InlinableString::Heap(ref s) => &*s, 212 | InlinableString::Inline(ref s) => &*s, 213 | } 214 | } 215 | } 216 | 217 | impl AsMut for InlinableString { 218 | fn as_mut(&mut self) -> &mut str { 219 | match *self { 220 | InlinableString::Heap(ref mut s) => s.as_mut_str(), 221 | InlinableString::Inline(ref mut s) => &mut s[..], 222 | } 223 | } 224 | } 225 | 226 | impl<'a> From<&'a str> for InlinableString { 227 | #[inline] 228 | fn from(string: &'a str) -> InlinableString { 229 | if string.len() <= INLINE_STRING_CAPACITY { 230 | InlinableString::Inline(string.into()) 231 | } else { 232 | InlinableString::Heap(string.into()) 233 | } 234 | } 235 | } 236 | 237 | impl From for InlinableString { 238 | #[inline] 239 | fn from(string: String) -> InlinableString { 240 | if string.len() <= INLINE_STRING_CAPACITY { 241 | InlinableString::Inline(string.as_str().into()) 242 | } else { 243 | InlinableString::Heap(string) 244 | } 245 | } 246 | } 247 | 248 | impl FromStr for InlinableString { 249 | type Err = convert::Infallible; 250 | 251 | #[inline] 252 | fn from_str(s: &str) -> Result { 253 | Ok(InlinableString::from(s)) 254 | } 255 | } 256 | 257 | impl Default for InlinableString { 258 | fn default() -> Self { 259 | InlinableString::new() 260 | } 261 | } 262 | 263 | impl fmt::Display for InlinableString { 264 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { 265 | match *self { 266 | InlinableString::Heap(ref s) => s.fmt(f), 267 | InlinableString::Inline(ref s) => s.fmt(f), 268 | } 269 | } 270 | } 271 | 272 | impl fmt::Write for InlinableString { 273 | fn write_char(&mut self, ch: char) -> Result<(), fmt::Error> { 274 | self.push(ch); 275 | Ok(()) 276 | } 277 | fn write_str(&mut self, s: &str) -> Result<(), fmt::Error> { 278 | self.push_str(s); 279 | Ok(()) 280 | } 281 | } 282 | 283 | impl ops::Index> for InlinableString { 284 | type Output = str; 285 | 286 | #[inline] 287 | fn index(&self, index: ops::Range) -> &str { 288 | match *self { 289 | InlinableString::Heap(ref s) => s.index(index), 290 | InlinableString::Inline(ref s) => s.index(index), 291 | } 292 | } 293 | } 294 | 295 | impl ops::Index> for InlinableString { 296 | type Output = str; 297 | 298 | #[inline] 299 | fn index(&self, index: ops::RangeTo) -> &str { 300 | match *self { 301 | InlinableString::Heap(ref s) => s.index(index), 302 | InlinableString::Inline(ref s) => s.index(index), 303 | } 304 | } 305 | } 306 | 307 | impl ops::Index> for InlinableString { 308 | type Output = str; 309 | 310 | #[inline] 311 | fn index(&self, index: ops::RangeFrom) -> &str { 312 | match *self { 313 | InlinableString::Heap(ref s) => s.index(index), 314 | InlinableString::Inline(ref s) => s.index(index), 315 | } 316 | } 317 | } 318 | 319 | impl ops::Index for InlinableString { 320 | type Output = str; 321 | 322 | #[inline] 323 | fn index(&self, index: ops::RangeFull) -> &str { 324 | match *self { 325 | InlinableString::Heap(ref s) => s.index(index), 326 | InlinableString::Inline(ref s) => s.index(index), 327 | } 328 | } 329 | } 330 | 331 | impl ops::IndexMut> for InlinableString { 332 | #[inline] 333 | fn index_mut(&mut self, index: ops::Range) -> &mut str { 334 | match *self { 335 | InlinableString::Heap(ref mut s) => s.index_mut(index), 336 | InlinableString::Inline(ref mut s) => s.index_mut(index), 337 | } 338 | } 339 | } 340 | 341 | impl ops::IndexMut> for InlinableString { 342 | #[inline] 343 | fn index_mut(&mut self, index: ops::RangeTo) -> &mut str { 344 | match *self { 345 | InlinableString::Heap(ref mut s) => s.index_mut(index), 346 | InlinableString::Inline(ref mut s) => s.index_mut(index), 347 | } 348 | } 349 | } 350 | 351 | impl ops::IndexMut> for InlinableString { 352 | #[inline] 353 | fn index_mut(&mut self, index: ops::RangeFrom) -> &mut str { 354 | match *self { 355 | InlinableString::Heap(ref mut s) => s.index_mut(index), 356 | InlinableString::Inline(ref mut s) => s.index_mut(index), 357 | } 358 | } 359 | } 360 | 361 | impl ops::IndexMut for InlinableString { 362 | #[inline] 363 | fn index_mut(&mut self, index: ops::RangeFull) -> &mut str { 364 | match *self { 365 | InlinableString::Heap(ref mut s) => s.index_mut(index), 366 | InlinableString::Inline(ref mut s) => s.index_mut(index), 367 | } 368 | } 369 | } 370 | 371 | impl ops::Deref for InlinableString { 372 | type Target = str; 373 | 374 | #[inline] 375 | fn deref(&self) -> &str { 376 | match *self { 377 | InlinableString::Heap(ref s) => s.deref(), 378 | InlinableString::Inline(ref s) => s.deref(), 379 | } 380 | } 381 | } 382 | 383 | impl ops::DerefMut for InlinableString { 384 | #[inline] 385 | fn deref_mut(&mut self) -> &mut str { 386 | match *self { 387 | InlinableString::Heap(ref mut s) => s.deref_mut(), 388 | InlinableString::Inline(ref mut s) => s.deref_mut(), 389 | } 390 | } 391 | } 392 | 393 | impl PartialEq for InlinableString { 394 | #[inline] 395 | fn eq(&self, rhs: &InlinableString) -> bool { 396 | PartialEq::eq(&self[..], &rhs[..]) 397 | } 398 | } 399 | 400 | macro_rules! impl_eq { 401 | ($lhs:ty, $rhs: ty) => { 402 | impl<'a> PartialEq<$rhs> for $lhs { 403 | #[inline] 404 | fn eq(&self, other: &$rhs) -> bool { 405 | PartialEq::eq(&self[..], &other[..]) 406 | } 407 | } 408 | 409 | impl<'a> PartialEq<$lhs> for $rhs { 410 | #[inline] 411 | fn eq(&self, other: &$lhs) -> bool { 412 | PartialEq::eq(&self[..], &other[..]) 413 | } 414 | } 415 | }; 416 | } 417 | 418 | impl_eq! { InlinableString, str } 419 | impl_eq! { InlinableString, String } 420 | impl_eq! { InlinableString, &'a str } 421 | impl_eq! { InlinableString, InlineString } 422 | impl_eq! { Cow<'a, str>, InlinableString } 423 | 424 | impl<'a> StringExt<'a> for InlinableString { 425 | #[inline] 426 | fn new() -> Self { 427 | InlinableString::Inline(InlineString::new()) 428 | } 429 | 430 | #[inline] 431 | fn with_capacity(capacity: usize) -> Self { 432 | if capacity <= INLINE_STRING_CAPACITY { 433 | InlinableString::Inline(InlineString::new()) 434 | } else { 435 | InlinableString::Heap(String::with_capacity(capacity)) 436 | } 437 | } 438 | 439 | #[inline] 440 | fn from_utf8(vec: Vec) -> Result { 441 | String::from_utf8(vec).map(InlinableString::Heap) 442 | } 443 | 444 | #[inline] 445 | fn from_utf16(v: &[u16]) -> Result { 446 | String::from_utf16(v).map(InlinableString::Heap) 447 | } 448 | 449 | #[inline] 450 | fn from_utf16_lossy(v: &[u16]) -> Self { 451 | InlinableString::Heap(String::from_utf16_lossy(v)) 452 | } 453 | 454 | #[inline] 455 | unsafe fn from_raw_parts(buf: *mut u8, length: usize, capacity: usize) -> Self { 456 | InlinableString::Heap(String::from_raw_parts(buf, length, capacity)) 457 | } 458 | 459 | #[inline] 460 | unsafe fn from_utf8_unchecked(bytes: Vec) -> Self { 461 | InlinableString::Heap(String::from_utf8_unchecked(bytes)) 462 | } 463 | 464 | #[inline] 465 | fn into_bytes(self) -> Vec { 466 | match self { 467 | InlinableString::Heap(s) => s.into_bytes(), 468 | InlinableString::Inline(s) => Vec::from(&s[..]), 469 | } 470 | } 471 | 472 | #[inline] 473 | fn push_str(&mut self, string: &str) { 474 | let promoted = match *self { 475 | InlinableString::Inline(ref mut s) => { 476 | if s.push_str(string).is_ok() { 477 | return; 478 | } 479 | let mut promoted = String::with_capacity(string.len() + s.len()); 480 | promoted.push_str(&*s); 481 | promoted.push_str(string); 482 | promoted 483 | } 484 | InlinableString::Heap(ref mut s) => { 485 | s.push_str(string); 486 | return; 487 | } 488 | }; 489 | mem::swap(self, &mut InlinableString::Heap(promoted)); 490 | } 491 | 492 | #[inline] 493 | fn capacity(&self) -> usize { 494 | match *self { 495 | InlinableString::Heap(ref s) => s.capacity(), 496 | InlinableString::Inline(_) => INLINE_STRING_CAPACITY, 497 | } 498 | } 499 | 500 | #[inline] 501 | fn reserve(&mut self, additional: usize) { 502 | let promoted = match *self { 503 | InlinableString::Inline(ref s) => { 504 | let new_capacity = s.len() + additional; 505 | if new_capacity <= INLINE_STRING_CAPACITY { 506 | return; 507 | } 508 | let mut promoted = String::with_capacity(new_capacity); 509 | promoted.push_str(&s); 510 | promoted 511 | } 512 | InlinableString::Heap(ref mut s) => { 513 | s.reserve(additional); 514 | return; 515 | } 516 | }; 517 | mem::swap(self, &mut InlinableString::Heap(promoted)); 518 | } 519 | 520 | #[inline] 521 | fn reserve_exact(&mut self, additional: usize) { 522 | let promoted = match *self { 523 | InlinableString::Inline(ref s) => { 524 | let new_capacity = s.len() + additional; 525 | if new_capacity <= INLINE_STRING_CAPACITY { 526 | return; 527 | } 528 | let mut promoted = String::with_capacity(new_capacity); 529 | promoted.push_str(&s); 530 | promoted 531 | } 532 | InlinableString::Heap(ref mut s) => { 533 | s.reserve_exact(additional); 534 | return; 535 | } 536 | }; 537 | mem::swap(self, &mut InlinableString::Heap(promoted)); 538 | } 539 | 540 | #[inline] 541 | fn shrink_to_fit(&mut self) { 542 | if self.len() <= INLINE_STRING_CAPACITY { 543 | let demoted = if let InlinableString::Heap(ref s) = *self { 544 | InlineString::from(&s[..]) 545 | } else { 546 | return; 547 | }; 548 | mem::swap(self, &mut InlinableString::Inline(demoted)); 549 | return; 550 | } 551 | 552 | match *self { 553 | InlinableString::Heap(ref mut s) => s.shrink_to_fit(), 554 | _ => panic!("inlinable_string: internal error: this branch should be unreachable"), 555 | }; 556 | } 557 | 558 | #[inline] 559 | fn push(&mut self, ch: char) { 560 | let promoted = match *self { 561 | InlinableString::Inline(ref mut s) => { 562 | if s.push(ch).is_ok() { 563 | return; 564 | } 565 | 566 | let mut promoted = String::with_capacity(s.len() + 1); 567 | promoted.push_str(&*s); 568 | promoted.push(ch); 569 | promoted 570 | } 571 | InlinableString::Heap(ref mut s) => { 572 | s.push(ch); 573 | return; 574 | } 575 | }; 576 | 577 | mem::swap(self, &mut InlinableString::Heap(promoted)); 578 | } 579 | 580 | #[inline] 581 | fn as_bytes(&self) -> &[u8] { 582 | match *self { 583 | InlinableString::Heap(ref s) => s.as_bytes(), 584 | InlinableString::Inline(ref s) => s.as_bytes(), 585 | } 586 | } 587 | 588 | #[inline] 589 | fn truncate(&mut self, new_len: usize) { 590 | match *self { 591 | InlinableString::Heap(ref mut s) => s.truncate(new_len), 592 | InlinableString::Inline(ref mut s) => s.truncate(new_len), 593 | }; 594 | } 595 | 596 | #[inline] 597 | fn pop(&mut self) -> Option { 598 | match *self { 599 | InlinableString::Heap(ref mut s) => s.pop(), 600 | InlinableString::Inline(ref mut s) => s.pop(), 601 | } 602 | } 603 | 604 | #[inline] 605 | fn remove(&mut self, idx: usize) -> char { 606 | match *self { 607 | InlinableString::Heap(ref mut s) => s.remove(idx), 608 | InlinableString::Inline(ref mut s) => s.remove(idx), 609 | } 610 | } 611 | 612 | #[inline] 613 | fn insert(&mut self, idx: usize, ch: char) { 614 | let promoted = match *self { 615 | InlinableString::Heap(ref mut s) => { 616 | s.insert(idx, ch); 617 | return; 618 | } 619 | InlinableString::Inline(ref mut s) => { 620 | if s.insert(idx, ch).is_ok() { 621 | return; 622 | } 623 | 624 | let mut promoted = String::with_capacity(s.len() + 1); 625 | promoted.push_str(&s[..idx]); 626 | promoted.push(ch); 627 | promoted.push_str(&s[idx..]); 628 | promoted 629 | } 630 | }; 631 | 632 | mem::swap(self, &mut InlinableString::Heap(promoted)); 633 | } 634 | 635 | #[inline] 636 | fn insert_str(&mut self, idx: usize, string: &str) { 637 | let promoted = match *self { 638 | InlinableString::Heap(ref mut s) => { 639 | s.insert_str(idx, string); 640 | return; 641 | } 642 | InlinableString::Inline(ref mut s) => { 643 | if s.insert_str(idx, string).is_ok() { 644 | return; 645 | } 646 | 647 | let mut promoted = String::with_capacity(s.len() + string.len()); 648 | promoted.push_str(&s[..idx]); 649 | promoted.push_str(string); 650 | promoted.push_str(&s[idx..]); 651 | promoted 652 | } 653 | }; 654 | 655 | mem::swap(self, &mut InlinableString::Heap(promoted)); 656 | } 657 | 658 | #[inline] 659 | unsafe fn as_mut_slice(&mut self) -> &mut [u8] { 660 | match *self { 661 | InlinableString::Heap(ref mut s) => &mut s.as_mut_vec()[..], 662 | InlinableString::Inline(ref mut s) => s.as_mut_slice(), 663 | } 664 | } 665 | 666 | #[inline] 667 | fn len(&self) -> usize { 668 | match *self { 669 | InlinableString::Heap(ref s) => s.len(), 670 | InlinableString::Inline(ref s) => s.len(), 671 | } 672 | } 673 | } 674 | 675 | #[cfg(test)] 676 | mod tests { 677 | use alloc::string::{String, ToString}; 678 | use core::iter::FromIterator; 679 | use super::{InlinableString, StringExt, INLINE_STRING_CAPACITY}; 680 | use core::cmp::Ordering; 681 | use core::str::FromStr; 682 | 683 | #[test] 684 | fn test_size() { 685 | use core::mem::size_of; 686 | assert_eq!(size_of::(), 4 * size_of::()); 687 | } 688 | 689 | // First, specifically test operations that overflow InlineString's capacity 690 | // and require promoting the string to heap allocation. 691 | 692 | #[test] 693 | fn test_push_str() { 694 | let mut s = InlinableString::new(); 695 | s.push_str("small"); 696 | assert_eq!(s, "small"); 697 | 698 | let long_str = "this is a really long string that is much larger than 699 | INLINE_STRING_CAPACITY and so cannot be stored inline."; 700 | s.push_str(long_str); 701 | assert_eq!(s, String::from("small") + long_str); 702 | } 703 | 704 | #[test] 705 | fn test_write() { 706 | use core::fmt::Write; 707 | let mut s = InlinableString::new(); 708 | write!(&mut s, "small").expect("!write"); 709 | assert_eq!(s, "small"); 710 | 711 | let long_str = "this is a really long string that is much larger than 712 | INLINE_STRING_CAPACITY and so cannot be stored inline."; 713 | write!(&mut s, "{}", long_str).expect("!write"); 714 | assert_eq!(s, String::from("small") + long_str); 715 | } 716 | 717 | #[test] 718 | fn test_push() { 719 | let mut s = InlinableString::new(); 720 | 721 | for _ in 0..INLINE_STRING_CAPACITY { 722 | s.push('a'); 723 | } 724 | s.push('a'); 725 | 726 | assert_eq!( 727 | s, 728 | String::from_iter((0..INLINE_STRING_CAPACITY + 1).map(|_| 'a')) 729 | ); 730 | } 731 | 732 | #[test] 733 | fn test_insert() { 734 | let mut s = InlinableString::new(); 735 | 736 | for _ in 0..INLINE_STRING_CAPACITY { 737 | s.insert(0, 'a'); 738 | } 739 | s.insert(0, 'a'); 740 | 741 | assert_eq!( 742 | s, 743 | String::from_iter((0..INLINE_STRING_CAPACITY + 1).map(|_| 'a')) 744 | ); 745 | } 746 | 747 | #[test] 748 | fn test_insert_str() { 749 | let mut s = InlinableString::new(); 750 | 751 | for _ in 0..(INLINE_STRING_CAPACITY / 3) { 752 | s.insert_str(0, "foo"); 753 | } 754 | s.insert_str(0, "foo"); 755 | 756 | assert_eq!( 757 | s, 758 | String::from_iter((0..(INLINE_STRING_CAPACITY / 3) + 1).map(|_| "foo")) 759 | ); 760 | } 761 | 762 | // Next, some general sanity tests. 763 | 764 | #[test] 765 | fn test_new() { 766 | let s = ::new(); 767 | assert!(StringExt::is_empty(&s)); 768 | } 769 | 770 | #[test] 771 | fn test_with_capacity() { 772 | let s = ::with_capacity(10); 773 | assert!(StringExt::capacity(&s) >= 10); 774 | } 775 | 776 | #[test] 777 | fn test_from_utf8() { 778 | let s = ::from_utf8(vec![104, 101, 108, 108, 111]); 779 | assert_eq!(s.unwrap(), "hello"); 780 | } 781 | 782 | #[test] 783 | fn test_from_utf16() { 784 | let v = &mut [0xD834, 0xDD1E, 0x006d, 0x0075, 0x0073, 0x0069, 0x0063]; 785 | let s = ::from_utf16(v); 786 | assert_eq!(s.unwrap(), "𝄞music"); 787 | } 788 | 789 | #[test] 790 | fn test_from_utf16_lossy() { 791 | let input = b"Hello \xF0\x90\x80World"; 792 | let output = ::from_utf8_lossy(input); 793 | assert_eq!(output, "Hello \u{FFFD}World"); 794 | } 795 | 796 | #[test] 797 | fn test_into_bytes() { 798 | let s = InlinableString::from("hello"); 799 | let bytes = StringExt::into_bytes(s); 800 | assert_eq!(bytes, [104, 101, 108, 108, 111]); 801 | } 802 | 803 | #[test] 804 | fn test_capacity() { 805 | let s = ::with_capacity(100); 806 | assert!(InlinableString::capacity(&s) >= 100); 807 | } 808 | 809 | #[test] 810 | fn test_reserve() { 811 | let mut s = ::new(); 812 | StringExt::reserve(&mut s, 100); 813 | assert!(InlinableString::capacity(&s) >= 100); 814 | } 815 | 816 | #[test] 817 | fn test_reserve_exact() { 818 | let mut s = ::new(); 819 | StringExt::reserve_exact(&mut s, 100); 820 | assert!(InlinableString::capacity(&s) >= 100); 821 | } 822 | 823 | #[test] 824 | fn test_shrink_to_fit() { 825 | let mut s = ::with_capacity(100); 826 | StringExt::push_str(&mut s, "foo"); 827 | StringExt::shrink_to_fit(&mut s); 828 | assert_eq!(InlinableString::capacity(&s), INLINE_STRING_CAPACITY); 829 | } 830 | 831 | #[test] 832 | fn test_truncate() { 833 | let mut s = InlinableString::from("foo"); 834 | StringExt::truncate(&mut s, 1); 835 | assert_eq!(s, "f"); 836 | } 837 | 838 | #[test] 839 | fn test_pop() { 840 | let mut s = InlinableString::from("foo"); 841 | assert_eq!(StringExt::pop(&mut s), Some('o')); 842 | assert_eq!(StringExt::pop(&mut s), Some('o')); 843 | assert_eq!(StringExt::pop(&mut s), Some('f')); 844 | assert_eq!(StringExt::pop(&mut s), None); 845 | } 846 | 847 | #[test] 848 | fn test_ord() { 849 | let s1 = InlinableString::from("foo"); 850 | let s2 = InlinableString::from("bar"); 851 | assert_eq!(Ord::cmp(&s1, &s2), Ordering::Greater); 852 | assert_eq!(Ord::cmp(&s1, &s1), Ordering::Equal); 853 | } 854 | 855 | #[test] 856 | fn test_display() { 857 | let short = InlinableString::from("he"); 858 | let long = InlinableString::from("hello world"); 859 | assert_eq!(format!("{}", short), "he".to_string()); 860 | assert_eq!(format!("{}", long), "hello world".to_string()); 861 | } 862 | 863 | #[test] 864 | fn test_debug() { 865 | let short = InlinableString::from("he"); 866 | let long = InlinableString::from("hello world hello world hello world"); 867 | assert_eq!(format!("{:?}", short), "\"he\""); 868 | assert_eq!( 869 | format!("{:?}", long), 870 | "\"hello world hello world hello world\"" 871 | ); 872 | } 873 | 874 | // example generic function where impl FromStr for InlinableString is useful 875 | fn parse_non_empty(s: &str) -> Option { 876 | if s.len() == 0 { 877 | None 878 | } else { 879 | let val = T::from_str(s).unwrap_or_else(|_| panic!("unwrap")); 880 | Some(val) 881 | } 882 | } 883 | 884 | #[test] 885 | fn test_fromstr() { 886 | assert_eq!(parse_non_empty::(""), None); 887 | assert_eq!(parse_non_empty::("10"), Some(10u8)); 888 | assert_eq!( 889 | parse_non_empty::("foo"), 890 | Some(InlinableString::from("foo")) 891 | ); 892 | } 893 | } 894 | 895 | #[cfg(test)] 896 | #[cfg(feature = "nightly")] 897 | mod benches { 898 | #[cfg(feature = "no_std")] 899 | use alloc::string::String; 900 | use super::{InlinableString, StringExt}; 901 | use test::{black_box, Bencher}; 902 | 903 | const SMALL_STR: &'static str = "foobar"; 904 | 905 | const LARGE_STR: &'static str = 906 | "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz 907 | abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz 908 | abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz 909 | abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz 910 | abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz 911 | abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz 912 | abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz 913 | abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz"; 914 | 915 | #[bench] 916 | fn bench_std_string_push_str_small_onto_empty(b: &mut Bencher) { 917 | b.iter(|| { 918 | let mut s = String::new(); 919 | s.push_str(SMALL_STR); 920 | black_box(s); 921 | }); 922 | } 923 | 924 | #[bench] 925 | fn bench_inlinable_string_push_str_small_onto_empty(b: &mut Bencher) { 926 | b.iter(|| { 927 | let mut s = InlinableString::new(); 928 | s.push_str(SMALL_STR); 929 | black_box(s); 930 | }); 931 | } 932 | 933 | #[bench] 934 | fn bench_std_string_push_str_large_onto_empty(b: &mut Bencher) { 935 | b.iter(|| { 936 | let mut s = String::new(); 937 | s.push_str(LARGE_STR); 938 | black_box(s); 939 | }); 940 | } 941 | 942 | #[bench] 943 | fn bench_inlinable_string_push_str_large_onto_empty(b: &mut Bencher) { 944 | b.iter(|| { 945 | let mut s = InlinableString::new(); 946 | s.push_str(LARGE_STR); 947 | black_box(s); 948 | }); 949 | } 950 | 951 | #[bench] 952 | fn bench_std_string_push_str_small_onto_small(b: &mut Bencher) { 953 | b.iter(|| { 954 | let mut s = String::from(SMALL_STR); 955 | s.push_str(SMALL_STR); 956 | black_box(s); 957 | }); 958 | } 959 | 960 | #[bench] 961 | fn bench_inlinable_string_push_str_small_onto_small(b: &mut Bencher) { 962 | b.iter(|| { 963 | let mut s = InlinableString::from(SMALL_STR); 964 | s.push_str(SMALL_STR); 965 | black_box(s); 966 | }); 967 | } 968 | 969 | #[bench] 970 | fn bench_std_string_push_str_large_onto_large(b: &mut Bencher) { 971 | b.iter(|| { 972 | let mut s = String::from(LARGE_STR); 973 | s.push_str(LARGE_STR); 974 | black_box(s); 975 | }); 976 | } 977 | 978 | #[bench] 979 | fn bench_inlinable_string_push_str_large_onto_large(b: &mut Bencher) { 980 | b.iter(|| { 981 | let mut s = InlinableString::from(LARGE_STR); 982 | s.push_str(LARGE_STR); 983 | black_box(s); 984 | }); 985 | } 986 | 987 | #[bench] 988 | fn bench_std_string_from_small(b: &mut Bencher) { 989 | b.iter(|| { 990 | let s = String::from(SMALL_STR); 991 | black_box(s); 992 | }); 993 | } 994 | 995 | #[bench] 996 | fn bench_inlinable_string_from_small(b: &mut Bencher) { 997 | b.iter(|| { 998 | let s = InlinableString::from(SMALL_STR); 999 | black_box(s); 1000 | }); 1001 | } 1002 | 1003 | #[bench] 1004 | fn bench_std_string_from_large(b: &mut Bencher) { 1005 | b.iter(|| { 1006 | let s = String::from(LARGE_STR); 1007 | black_box(s); 1008 | }); 1009 | } 1010 | 1011 | #[bench] 1012 | fn bench_inlinable_string_from_large(b: &mut Bencher) { 1013 | b.iter(|| { 1014 | let s = InlinableString::from(LARGE_STR); 1015 | black_box(s); 1016 | }); 1017 | } 1018 | } 1019 | -------------------------------------------------------------------------------- /src/serde_impl.rs: -------------------------------------------------------------------------------- 1 | use alloc::string::String; 2 | use crate::InlinableString; 3 | use serde::de::{Deserialize, Deserializer, Error as DeError, Visitor}; 4 | use serde::{Serialize, Serializer}; 5 | use core::fmt; 6 | 7 | impl Serialize for InlinableString { 8 | fn serialize(&self, serializer: S) -> Result 9 | where 10 | S: Serializer, 11 | { 12 | serializer.serialize_str(self) 13 | } 14 | } 15 | 16 | impl<'de> Deserialize<'de> for InlinableString { 17 | fn deserialize(deserializer: D) -> Result 18 | where 19 | D: Deserializer<'de>, 20 | { 21 | struct InlinableStringVisitor; 22 | 23 | impl<'de> Visitor<'de> for InlinableStringVisitor { 24 | type Value = InlinableString; 25 | 26 | fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { 27 | formatter.write_str("a string") 28 | } 29 | 30 | fn visit_str(self, v: &str) -> Result 31 | where 32 | E: DeError, 33 | { 34 | Ok(v.into()) 35 | } 36 | 37 | fn visit_string(self, v: String) -> Result 38 | where 39 | E: DeError, 40 | { 41 | Ok(v.into()) 42 | } 43 | } 44 | 45 | deserializer.deserialize_str(InlinableStringVisitor) 46 | } 47 | } 48 | 49 | #[cfg(test)] 50 | mod tests { 51 | use crate::InlinableString; 52 | use serde_test::{assert_tokens, Token}; 53 | 54 | #[test] 55 | fn test_ser_de() { 56 | let s = InlinableString::from("small"); 57 | 58 | assert_tokens(&s, &[Token::String("small")]); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/string_ext.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2015, The inlinable_string crate Developers. See the COPYRIGHT file 2 | // at the top-level directory of this distribution. 3 | // 4 | // Licensed under the Apache License, Version 2.0 or the MIT license , at your option. This file may not be 7 | // copied, modified, or distributed except according to those terms. 8 | 9 | //! A trait that exists to abstract string operations over any number of 10 | //! concrete string type implementations. 11 | //! 12 | //! See the [crate level documentation](./../index.html) for more. 13 | 14 | use alloc::borrow::{Borrow, Cow}; 15 | use alloc::vec::Vec; 16 | use alloc::string::{String, FromUtf16Error, FromUtf8Error}; 17 | use core::cmp::PartialEq; 18 | use core::fmt::Display; 19 | 20 | /// A trait that exists to abstract string operations over any number of 21 | /// concrete string type implementations. 22 | /// 23 | /// See the [crate level documentation](./../index.html) for more. 24 | pub trait StringExt<'a>: 25 | Borrow 26 | + Display 27 | + PartialEq 28 | + PartialEq<&'a str> 29 | + PartialEq 30 | + PartialEq> 31 | { 32 | /// Creates a new string buffer initialized with the empty string. 33 | /// 34 | /// # Examples 35 | /// 36 | /// ``` 37 | /// use inlinable_string::{InlinableString, StringExt}; 38 | /// 39 | /// let s = InlinableString::new(); 40 | /// ``` 41 | fn new() -> Self 42 | where 43 | Self: Sized; 44 | 45 | /// Creates a new string buffer with the given capacity. The string will be 46 | /// able to hold at least `capacity` bytes without reallocating. If 47 | /// `capacity` is less than or equal to `INLINE_STRING_CAPACITY`, the string 48 | /// will not heap allocate. 49 | /// 50 | /// # Examples 51 | /// 52 | /// ``` 53 | /// use inlinable_string::{InlinableString, StringExt}; 54 | /// 55 | /// let s = InlinableString::with_capacity(10); 56 | /// ``` 57 | fn with_capacity(capacity: usize) -> Self 58 | where 59 | Self: Sized; 60 | 61 | /// Returns the vector as a string buffer, if possible, taking care not to 62 | /// copy it. 63 | /// 64 | /// # Failure 65 | /// 66 | /// If the given vector is not valid UTF-8, then the original vector and the 67 | /// corresponding error is returned. 68 | /// 69 | /// # Examples 70 | /// 71 | /// ``` 72 | /// use inlinable_string::{InlinableString, StringExt}; 73 | /// 74 | /// let hello_vec = vec![104, 101, 108, 108, 111]; 75 | /// let s = InlinableString::from_utf8(hello_vec).unwrap(); 76 | /// assert_eq!(s, "hello"); 77 | /// 78 | /// let invalid_vec = vec![240, 144, 128]; 79 | /// let s = InlinableString::from_utf8(invalid_vec).err().unwrap(); 80 | /// let err = s.utf8_error(); 81 | /// assert_eq!(s.into_bytes(), [240, 144, 128]); 82 | /// ``` 83 | fn from_utf8(vec: Vec) -> Result 84 | where 85 | Self: Sized; 86 | 87 | /// Converts a vector of bytes to a new UTF-8 string. 88 | /// Any invalid UTF-8 sequences are replaced with U+FFFD REPLACEMENT CHARACTER. 89 | /// 90 | /// # Examples 91 | /// 92 | /// ``` 93 | /// use inlinable_string::{InlinableString, StringExt}; 94 | /// 95 | /// let input = b"Hello \xF0\x90\x80World"; 96 | /// let output = InlinableString::from_utf8_lossy(input); 97 | /// assert_eq!(output, "Hello \u{FFFD}World"); 98 | /// ``` 99 | fn from_utf8_lossy(v: &'a [u8]) -> Cow<'a, str> 100 | where 101 | Self: Sized, 102 | { 103 | String::from_utf8_lossy(v) 104 | } 105 | 106 | /// Decode a UTF-16 encoded vector `v` into a `InlinableString`, returning `None` 107 | /// if `v` contains any invalid data. 108 | /// 109 | /// # Examples 110 | /// 111 | /// ``` 112 | /// use inlinable_string::{InlinableString, StringExt}; 113 | /// 114 | /// // 𝄞music 115 | /// let mut v = &mut [0xD834, 0xDD1E, 0x006d, 0x0075, 116 | /// 0x0073, 0x0069, 0x0063]; 117 | /// assert_eq!(InlinableString::from_utf16(v).unwrap(), 118 | /// InlinableString::from("𝄞music")); 119 | /// 120 | /// // 𝄞muic 121 | /// v[4] = 0xD800; 122 | /// assert!(InlinableString::from_utf16(v).is_err()); 123 | /// ``` 124 | fn from_utf16(v: &[u16]) -> Result 125 | where 126 | Self: Sized; 127 | 128 | /// Decode a UTF-16 encoded vector `v` into a string, replacing 129 | /// invalid data with the replacement character (U+FFFD). 130 | /// 131 | /// # Examples 132 | /// 133 | /// ``` 134 | /// use inlinable_string::{InlinableString, StringExt}; 135 | /// 136 | /// // 𝄞music 137 | /// let v = &[0xD834, 0xDD1E, 0x006d, 0x0075, 138 | /// 0x0073, 0xDD1E, 0x0069, 0x0063, 139 | /// 0xD834]; 140 | /// 141 | /// assert_eq!(InlinableString::from_utf16_lossy(v), 142 | /// InlinableString::from("𝄞mus\u{FFFD}ic\u{FFFD}")); 143 | /// ``` 144 | fn from_utf16_lossy(v: &[u16]) -> Self 145 | where 146 | Self: Sized; 147 | 148 | /// Creates a new `InlinableString` from a length, capacity, and pointer. 149 | /// 150 | /// # Safety 151 | /// 152 | /// This is _very_ unsafe because: 153 | /// 154 | /// * We call `String::from_raw_parts` to get a `Vec`. Therefore, this 155 | /// function inherits all of its unsafety, see [its 156 | /// documentation](https://doc.rust-lang.org/nightly/collections/vec/struct.Vec.html#method.from_raw_parts) 157 | /// for the invariants it expects, they also apply to this function. 158 | /// 159 | /// * We assume that the `Vec` contains valid UTF-8. 160 | unsafe fn from_raw_parts(buf: *mut u8, length: usize, capacity: usize) -> Self 161 | where 162 | Self: Sized; 163 | 164 | /// Converts a vector of bytes to a new `InlinableString` without checking 165 | /// if it contains valid UTF-8. 166 | /// 167 | /// # Safety 168 | /// 169 | /// This is unsafe because it assumes that the UTF-8-ness of the vector has 170 | /// already been validated. 171 | unsafe fn from_utf8_unchecked(bytes: Vec) -> Self 172 | where 173 | Self: Sized; 174 | 175 | /// Returns the underlying byte buffer, encoded as UTF-8. 176 | /// 177 | /// # Examples 178 | /// 179 | /// ``` 180 | /// use inlinable_string::{InlinableString, StringExt}; 181 | /// 182 | /// let s = InlinableString::from("hello"); 183 | /// let bytes = s.into_bytes(); 184 | /// assert_eq!(bytes, [104, 101, 108, 108, 111]); 185 | /// ``` 186 | fn into_bytes(self) -> Vec; 187 | 188 | /// Pushes the given string onto this string buffer. 189 | /// 190 | /// # Examples 191 | /// 192 | /// ``` 193 | /// use inlinable_string::{InlinableString, StringExt}; 194 | /// 195 | /// let mut s = InlinableString::from("foo"); 196 | /// s.push_str("bar"); 197 | /// assert_eq!(s, "foobar"); 198 | /// ``` 199 | fn push_str(&mut self, string: &str); 200 | 201 | /// Returns the number of bytes that this string buffer can hold without 202 | /// reallocating. 203 | /// 204 | /// # Examples 205 | /// 206 | /// ``` 207 | /// use inlinable_string::{InlinableString, StringExt}; 208 | /// 209 | /// let s = InlinableString::with_capacity(10); 210 | /// assert!(s.capacity() >= 10); 211 | /// ``` 212 | fn capacity(&self) -> usize; 213 | 214 | /// Reserves capacity for at least `additional` more bytes to be inserted 215 | /// in the given `InlinableString`. The collection may reserve more space to avoid 216 | /// frequent reallocations. 217 | /// 218 | /// # Panics 219 | /// 220 | /// Panics if the new capacity overflows `usize`. 221 | /// 222 | /// # Examples 223 | /// 224 | /// ``` 225 | /// use inlinable_string::{InlinableString, StringExt}; 226 | /// 227 | /// let mut s = InlinableString::new(); 228 | /// s.reserve(10); 229 | /// assert!(s.capacity() >= 10); 230 | /// ``` 231 | fn reserve(&mut self, additional: usize); 232 | 233 | /// Reserves the minimum capacity for exactly `additional` more bytes to be 234 | /// inserted in the given `InlinableString`. Does nothing if the capacity is already 235 | /// sufficient. 236 | /// 237 | /// Note that the allocator may give the collection more space than it 238 | /// requests. Therefore capacity can not be relied upon to be precisely 239 | /// minimal. Prefer `reserve` if future insertions are expected. 240 | /// 241 | /// # Panics 242 | /// 243 | /// Panics if the new capacity overflows `usize`. 244 | /// 245 | /// # Examples 246 | /// 247 | /// ``` 248 | /// use inlinable_string::{InlinableString, StringExt}; 249 | /// 250 | /// let mut s = InlinableString::new(); 251 | /// s.reserve_exact(10); 252 | /// assert!(s.capacity() >= 10); 253 | /// ``` 254 | fn reserve_exact(&mut self, additional: usize); 255 | 256 | /// Shrinks the capacity of this string buffer to match its length. If the 257 | /// string's length is less than `INLINE_STRING_CAPACITY` and the string is 258 | /// heap-allocated, then it is demoted to inline storage. 259 | /// 260 | /// # Examples 261 | /// 262 | /// ``` 263 | /// use inlinable_string::{InlinableString, StringExt}; 264 | /// 265 | /// let mut s = InlinableString::from("foo"); 266 | /// s.reserve(100); 267 | /// assert!(s.capacity() >= 100); 268 | /// s.shrink_to_fit(); 269 | /// assert_eq!(s.capacity(), inlinable_string::INLINE_STRING_CAPACITY); 270 | /// ``` 271 | fn shrink_to_fit(&mut self); 272 | 273 | /// Adds the given character to the end of the string. 274 | /// 275 | /// # Examples 276 | /// 277 | /// ``` 278 | /// use inlinable_string::{InlinableString, StringExt}; 279 | /// 280 | /// let mut s = InlinableString::from("abc"); 281 | /// s.push('1'); 282 | /// s.push('2'); 283 | /// s.push('3'); 284 | /// assert_eq!(s, "abc123"); 285 | /// ``` 286 | fn push(&mut self, ch: char); 287 | 288 | /// Works with the underlying buffer as a byte slice. 289 | /// 290 | /// # Examples 291 | /// 292 | /// ``` 293 | /// use inlinable_string::{InlinableString, StringExt}; 294 | /// 295 | /// let s = InlinableString::from("hello"); 296 | /// assert_eq!(s.as_bytes(), [104, 101, 108, 108, 111]); 297 | /// ``` 298 | fn as_bytes(&self) -> &[u8]; 299 | 300 | /// Shortens a string to the specified length. 301 | /// 302 | /// # Panics 303 | /// 304 | /// Panics if `new_len` > current length, or if `new_len` is not a character 305 | /// boundary. 306 | /// 307 | /// # Examples 308 | /// 309 | /// ``` 310 | /// use inlinable_string::{InlinableString, StringExt}; 311 | /// 312 | /// let mut s = InlinableString::from("hello"); 313 | /// s.truncate(2); 314 | /// assert_eq!(s, "he"); 315 | /// ``` 316 | fn truncate(&mut self, new_len: usize); 317 | 318 | /// Removes the last character from the string buffer and returns it. 319 | /// Returns `None` if this string buffer is empty. 320 | /// 321 | /// # Examples 322 | /// 323 | /// ``` 324 | /// use inlinable_string::{InlinableString, StringExt}; 325 | /// 326 | /// let mut s = InlinableString::from("foo"); 327 | /// assert_eq!(s.pop(), Some('o')); 328 | /// assert_eq!(s.pop(), Some('o')); 329 | /// assert_eq!(s.pop(), Some('f')); 330 | /// assert_eq!(s.pop(), None); 331 | /// ``` 332 | fn pop(&mut self) -> Option; 333 | 334 | /// Removes the character from the string buffer at byte position `idx` and 335 | /// returns it. 336 | /// 337 | /// # Warning 338 | /// 339 | /// This is an O(n) operation as it requires copying every element in the 340 | /// buffer. 341 | /// 342 | /// # Panics 343 | /// 344 | /// If `idx` does not lie on a character boundary, or if it is out of 345 | /// bounds, then this function will panic. 346 | /// 347 | /// # Examples 348 | /// 349 | /// ``` 350 | /// use inlinable_string::{InlinableString, StringExt}; 351 | /// 352 | /// let mut s = InlinableString::from("foo"); 353 | /// assert_eq!(s.remove(0), 'f'); 354 | /// assert_eq!(s.remove(1), 'o'); 355 | /// assert_eq!(s.remove(0), 'o'); 356 | /// ``` 357 | fn remove(&mut self, idx: usize) -> char; 358 | 359 | /// Inserts a character into the string buffer at byte position `idx`. 360 | /// 361 | /// # Warning 362 | /// 363 | /// This is an O(n) operation as it requires copying every element in the 364 | /// buffer. 365 | /// 366 | /// # Examples 367 | /// 368 | /// ``` 369 | /// use inlinable_string::{InlinableString, StringExt}; 370 | /// 371 | /// let mut s = InlinableString::from("foo"); 372 | /// s.insert(2, 'f'); 373 | /// assert!(s == "fofo"); 374 | /// ``` 375 | /// 376 | /// # Panics 377 | /// 378 | /// If `idx` does not lie on a character boundary or is out of bounds, then 379 | /// this function will panic. 380 | fn insert(&mut self, idx: usize, ch: char); 381 | 382 | /// Inserts a string into the string buffer at byte position `idx`. 383 | /// 384 | /// # Warning 385 | /// 386 | /// This is an O(n) operation as it requires copying every element in the 387 | /// buffer. 388 | /// 389 | /// # Examples 390 | /// 391 | /// ``` 392 | /// use inlinable_string::{InlinableString, StringExt}; 393 | /// 394 | /// let mut s = InlinableString::from("foo"); 395 | /// s.insert_str(2, "bar"); 396 | /// assert!(s == "fobaro"); 397 | /// ``` 398 | /// 399 | /// # Panics 400 | /// 401 | /// If `idx` does not lie on a character boundary or is out of bounds, then 402 | /// this function will panic. 403 | fn insert_str(&mut self, idx: usize, string: &str); 404 | 405 | /// Views the string buffer as a mutable sequence of bytes. 406 | /// 407 | /// # Safety 408 | /// 409 | /// This is unsafe because it does not check to ensure that the resulting 410 | /// string will be valid UTF-8. 411 | /// 412 | /// # Examples 413 | /// 414 | /// ``` 415 | /// use inlinable_string::{InlinableString, StringExt}; 416 | /// 417 | /// let mut s = InlinableString::from("hello"); 418 | /// unsafe { 419 | /// let slice = s.as_mut_slice(); 420 | /// assert!(slice == &[104, 101, 108, 108, 111]); 421 | /// slice.reverse(); 422 | /// } 423 | /// assert_eq!(s, "olleh"); 424 | /// ``` 425 | unsafe fn as_mut_slice(&mut self) -> &mut [u8]; 426 | 427 | /// Returns the number of bytes in this string. 428 | /// 429 | /// # Examples 430 | /// 431 | /// ``` 432 | /// use inlinable_string::{InlinableString, StringExt}; 433 | /// 434 | /// let a = InlinableString::from("foo"); 435 | /// assert_eq!(a.len(), 3); 436 | /// ``` 437 | fn len(&self) -> usize; 438 | 439 | /// Returns true if the string contains no bytes 440 | /// 441 | /// # Examples 442 | /// 443 | /// ``` 444 | /// use inlinable_string::{InlinableString, StringExt}; 445 | /// 446 | /// let mut v = InlinableString::new(); 447 | /// assert!(v.is_empty()); 448 | /// v.push('a'); 449 | /// assert!(!v.is_empty()); 450 | /// ``` 451 | #[inline] 452 | fn is_empty(&self) -> bool { 453 | self.len() == 0 454 | } 455 | 456 | /// Truncates the string, returning it to 0 length. 457 | /// 458 | /// # Examples 459 | /// 460 | /// ``` 461 | /// use inlinable_string::{InlinableString, StringExt}; 462 | /// 463 | /// let mut s = InlinableString::from("foo"); 464 | /// s.clear(); 465 | /// assert!(s.is_empty()); 466 | /// ``` 467 | #[inline] 468 | fn clear(&mut self) { 469 | self.truncate(0); 470 | } 471 | } 472 | 473 | impl<'a> StringExt<'a> for String { 474 | #[inline] 475 | fn new() -> Self { 476 | String::new() 477 | } 478 | 479 | #[inline] 480 | fn with_capacity(capacity: usize) -> Self { 481 | String::with_capacity(capacity) 482 | } 483 | 484 | #[inline] 485 | fn from_utf8(vec: Vec) -> Result { 486 | String::from_utf8(vec) 487 | } 488 | 489 | #[inline] 490 | fn from_utf16(v: &[u16]) -> Result { 491 | String::from_utf16(v) 492 | } 493 | 494 | #[inline] 495 | fn from_utf16_lossy(v: &[u16]) -> Self { 496 | String::from_utf16_lossy(v) 497 | } 498 | 499 | #[inline] 500 | unsafe fn from_raw_parts(buf: *mut u8, length: usize, capacity: usize) -> Self { 501 | String::from_raw_parts(buf, length, capacity) 502 | } 503 | 504 | #[inline] 505 | unsafe fn from_utf8_unchecked(bytes: Vec) -> Self { 506 | String::from_utf8_unchecked(bytes) 507 | } 508 | 509 | #[inline] 510 | fn into_bytes(self) -> Vec { 511 | String::into_bytes(self) 512 | } 513 | 514 | #[inline] 515 | fn push_str(&mut self, string: &str) { 516 | String::push_str(self, string) 517 | } 518 | 519 | #[inline] 520 | fn capacity(&self) -> usize { 521 | String::capacity(self) 522 | } 523 | 524 | #[inline] 525 | fn reserve(&mut self, additional: usize) { 526 | String::reserve(self, additional) 527 | } 528 | 529 | #[inline] 530 | fn reserve_exact(&mut self, additional: usize) { 531 | String::reserve_exact(self, additional) 532 | } 533 | 534 | #[inline] 535 | fn shrink_to_fit(&mut self) { 536 | String::shrink_to_fit(self) 537 | } 538 | 539 | #[inline] 540 | fn push(&mut self, ch: char) { 541 | String::push(self, ch) 542 | } 543 | 544 | #[inline] 545 | fn as_bytes(&self) -> &[u8] { 546 | String::as_bytes(self) 547 | } 548 | 549 | #[inline] 550 | fn truncate(&mut self, new_len: usize) { 551 | String::truncate(self, new_len) 552 | } 553 | 554 | #[inline] 555 | fn pop(&mut self) -> Option { 556 | String::pop(self) 557 | } 558 | 559 | #[inline] 560 | fn remove(&mut self, idx: usize) -> char { 561 | String::remove(self, idx) 562 | } 563 | 564 | #[inline] 565 | fn insert(&mut self, idx: usize, ch: char) { 566 | String::insert(self, idx, ch) 567 | } 568 | 569 | #[inline] 570 | fn insert_str(&mut self, idx: usize, string: &str) { 571 | String::insert_str(self, idx, string) 572 | } 573 | 574 | #[inline] 575 | unsafe fn as_mut_slice(&mut self) -> &mut [u8] { 576 | &mut *(self.as_mut_str() as *mut str as *mut [u8]) 577 | } 578 | 579 | #[inline] 580 | fn len(&self) -> usize { 581 | String::len(self) 582 | } 583 | } 584 | 585 | #[cfg(test)] 586 | mod std_string_stringext_sanity_tests { 587 | // Sanity tests for std::string::String's StringExt implementation. 588 | 589 | use alloc::string::String; 590 | use super::StringExt; 591 | 592 | #[test] 593 | fn test_new() { 594 | let s = ::new(); 595 | assert!(StringExt::is_empty(&s)); 596 | } 597 | 598 | #[test] 599 | fn test_with_capacity() { 600 | let s = ::with_capacity(10); 601 | assert!(StringExt::capacity(&s) >= 10); 602 | } 603 | 604 | #[test] 605 | fn test_from_utf8() { 606 | let s = ::from_utf8(vec![104, 101, 108, 108, 111]); 607 | assert_eq!(s.unwrap(), "hello"); 608 | } 609 | 610 | #[test] 611 | fn test_from_utf16() { 612 | let v = &mut [0xD834, 0xDD1E, 0x006d, 0x0075, 0x0073, 0x0069, 0x0063]; 613 | let s = ::from_utf16(v); 614 | assert_eq!(s.unwrap(), "𝄞music"); 615 | } 616 | 617 | #[test] 618 | fn test_from_utf16_lossy() { 619 | let input = b"Hello \xF0\x90\x80World"; 620 | let output = ::from_utf8_lossy(input); 621 | assert_eq!(output, "Hello \u{FFFD}World"); 622 | } 623 | 624 | #[test] 625 | fn test_into_bytes() { 626 | let s = String::from("hello"); 627 | let bytes = StringExt::into_bytes(s); 628 | assert_eq!(bytes, [104, 101, 108, 108, 111]); 629 | } 630 | 631 | #[test] 632 | fn test_push_str() { 633 | let mut s = String::from("hello"); 634 | StringExt::push_str(&mut s, " world"); 635 | assert_eq!(s, "hello world"); 636 | } 637 | 638 | #[test] 639 | fn test_capacity() { 640 | let s = ::with_capacity(100); 641 | assert!(String::capacity(&s) >= 100); 642 | } 643 | 644 | #[test] 645 | fn test_reserve() { 646 | let mut s = ::new(); 647 | StringExt::reserve(&mut s, 100); 648 | assert!(String::capacity(&s) >= 100); 649 | } 650 | 651 | #[test] 652 | fn test_reserve_exact() { 653 | let mut s = ::new(); 654 | StringExt::reserve_exact(&mut s, 100); 655 | assert!(String::capacity(&s) >= 100); 656 | } 657 | 658 | #[test] 659 | fn test_shrink_to_fit() { 660 | let mut s = ::with_capacity(100); 661 | StringExt::push_str(&mut s, "foo"); 662 | StringExt::shrink_to_fit(&mut s); 663 | assert_eq!(String::capacity(&s), 3); 664 | } 665 | 666 | #[test] 667 | fn test_push() { 668 | let mut s = String::new(); 669 | StringExt::push(&mut s, 'a'); 670 | assert_eq!(s, "a"); 671 | } 672 | 673 | #[test] 674 | fn test_truncate() { 675 | let mut s = String::from("foo"); 676 | StringExt::truncate(&mut s, 1); 677 | assert_eq!(s, "f"); 678 | } 679 | 680 | #[test] 681 | fn test_pop() { 682 | let mut s = String::from("foo"); 683 | assert_eq!(StringExt::pop(&mut s), Some('o')); 684 | assert_eq!(StringExt::pop(&mut s), Some('o')); 685 | assert_eq!(StringExt::pop(&mut s), Some('f')); 686 | assert_eq!(StringExt::pop(&mut s), None); 687 | } 688 | } 689 | --------------------------------------------------------------------------------