├── .github └── workflows │ └── ci.yml ├── .gitignore ├── .tarpaulin.toml ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── LICENSE-ZLIB ├── README.md ├── codecov.yml ├── src ├── arc_str.rs ├── impl_serde.rs ├── lib.rs ├── mac.rs └── substr.rs └── tests ├── arc_str.rs └── substr.rs /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | 9 | env: 10 | RUST_BACKTRACE: short 11 | CARGO_INCREMENTAL: 0 12 | CARGO_NET_RETRY: 10 13 | RUSTUP_MAX_RETRIES: 10 14 | RUSTFLAGS: -Dwarnings 15 | RUSTDOCFLAGS: -Dwarnings 16 | 17 | jobs: 18 | # Test on stable and MSRV 19 | test: 20 | name: Test Rust - ${{ matrix.build }} 21 | runs-on: ${{ matrix.os }} 22 | env: 23 | CARGO: cargo 24 | TARGET: "" 25 | strategy: 26 | fail-fast: false 27 | matrix: 28 | build: 29 | - macos 30 | - linux 31 | - linux32 32 | - win64-msvc 33 | - win64-gnu 34 | - win32-msvc 35 | - win32-gnu 36 | # - msrv 37 | - beta 38 | - nightly 39 | - arm32 40 | - arm64 41 | - ppc32 42 | - ppc64 43 | include: 44 | - build: linux 45 | os: ubuntu-latest 46 | rust: stable 47 | - build: macos 48 | os: macos-latest 49 | rust: stable 50 | - build: win64-msvc 51 | os: windows-2019 52 | rust: stable 53 | - build: win64-gnu 54 | os: windows-2019 55 | rust: stable-x86_64-gnu 56 | - build: win32-msvc 57 | os: windows-2019 58 | rust: stable-i686-msvc 59 | - build: win32-gnu 60 | os: windows-2019 61 | rust: stable-i686-gnu 62 | - build: msrv 63 | os: ubuntu-latest 64 | rust: "1.57.0" 65 | - build: beta 66 | os: ubuntu-latest 67 | rust: beta 68 | - build: nightly 69 | os: ubuntu-latest 70 | rust: nightly 71 | - build: linux32 72 | os: ubuntu-latest 73 | rust: stable 74 | target: i686-unknown-linux-gnu 75 | # These should prob. be more generic arm targets and not android. 76 | - build: arm32 77 | os: ubuntu-latest 78 | rust: stable 79 | target: armv7-unknown-linux-gnueabihf 80 | - build: arm64 81 | os: ubuntu-latest 82 | rust: stable 83 | target: aarch64-unknown-linux-gnu 84 | # PPC is big endian. Nothing currently in here cares... but will if I 85 | # ever get around to that `key` stuff. 86 | - build: ppc32 87 | os: ubuntu-latest 88 | rust: stable 89 | target: powerpc-unknown-linux-gnu 90 | - build: ppc64 91 | os: ubuntu-latest 92 | rust: stable 93 | target: powerpc64-unknown-linux-gnu 94 | # Requested by a user not sure if it adds anything we aren't already 95 | # testing but it's easy enough so *shrug*. 96 | - build: riscv 97 | os: ubuntu-latest 98 | rust: stable 99 | target: riscv64gc-unknown-linux-gnu 100 | 101 | steps: 102 | - uses: actions/checkout@v4 103 | - uses: hecrj/setup-rust-action@v2 104 | with: 105 | rust-version: ${{ matrix.rust }} 106 | 107 | - uses: taiki-e/install-action@cross 108 | if: matrix.target != '' 109 | 110 | - if: matrix.target != '' 111 | run: | 112 | echo "CARGO=cross" >> $GITHUB_ENV 113 | echo "TARGET=--target ${{ matrix.target }}" >> $GITHUB_ENV 114 | 115 | # We have some tests that make sure functionality not present in old 116 | # versions behaves as expected (for example, `arcstr::format!("{foo}")`) 117 | # To test this, we put them behind `#[cfg(not(msrv))]`. 118 | - if: matrix.build == 'msrv' 119 | run: echo "RUSTFLAGS=--cfg msrv" >> $GITHUB_ENV 120 | 121 | - run: | 122 | echo "cargo command is: ${{ env.CARGO }}" 123 | echo "target flag is: ${{ env.TARGET }}" 124 | echo "rustflags are: ${{ env.RUSTFLAGS }}" 125 | 126 | - run: ${{ env.CARGO }} test --no-default-features --verbose ${{ env.TARGET }} 127 | - run: ${{ env.CARGO }} test --verbose ${{ env.TARGET }} 128 | - run: ${{ env.CARGO }} test --verbose --features="serde substr std" ${{ env.TARGET }} 129 | - run: ${{ env.CARGO }} test --all-features --verbose ${{ env.TARGET }} 130 | 131 | loom: 132 | name: Loom tests 133 | runs-on: ubuntu-latest 134 | env: 135 | RUSTFLAGS: --cfg loom -Dwarnings 136 | steps: 137 | - uses: actions/checkout@v4 138 | - uses: hecrj/setup-rust-action@v2 139 | - run: cargo test --all-features --lib 140 | - run: cargo test --no-default-features --lib 141 | 142 | miri: 143 | name: Miri 144 | runs-on: ubuntu-latest 145 | env: 146 | # the tests for `ArcStr::leak` intentionally leak memory. 147 | MIRIFLAGS: -Zmiri-ignore-leaks 148 | steps: 149 | - uses: actions/checkout@v4 150 | - uses: hecrj/setup-rust-action@v2 151 | with: 152 | rust-version: nightly 153 | components: miri, rust-src 154 | - run: cargo miri test --all-features 155 | - run: cargo miri test --features="std serde substr" 156 | - run: cargo miri test 157 | 158 | cargo-check: 159 | name: Lint 160 | runs-on: ubuntu-latest 161 | env: 162 | RUSTFLAGS: -Dwarnings 163 | steps: 164 | - uses: actions/checkout@v4 165 | - uses: hecrj/setup-rust-action@v2 166 | - run: cargo check --workspace --all-targets --verbose 167 | - run: cargo check --workspace --all-targets --verbose --all-features 168 | - run: cargo check --workspace --all-targets --verbose --features="serde std substr" 169 | - run: cargo check --workspace --all-targets --verbose --no-default-features 170 | 171 | # Ensure patch is formatted. 172 | fmt: 173 | name: Format 174 | runs-on: ubuntu-latest 175 | steps: 176 | - uses: actions/checkout@v4 177 | - uses: hecrj/setup-rust-action@v2 178 | - run: cargo fmt --all -- --check 179 | 180 | sanitizers: 181 | name: Test sanitizer ${{ matrix.sanitizer }} 182 | runs-on: ubuntu-latest 183 | env: 184 | RUST_BACKTRACE: 0 185 | # only used by asan, but we set it for all of them cuz its easy 186 | ASAN_OPTIONS: detect_stack_use_after_return=1 187 | LSAN_OPTIONS: "suppressions=lsan_suppressions.txt" 188 | strategy: 189 | fail-fast: false 190 | matrix: 191 | sanitizer: [address, thread, memory] 192 | # could do this instead of repeating 3x in the test invocation, but lets not be wasteful 193 | # test_flags: ['--features="std serde substr"', '--no-default-features', '--all-features'] 194 | include: 195 | - sanitizer: memory 196 | extra_rustflags: "-Zsanitizer-memory-track-origins" 197 | - sanitizer: address 198 | # to disable the ArcStr::leak test (can't get suppressions to work in CI) 199 | extra_rustflags: "--cfg=asan" 200 | 201 | steps: 202 | - uses: actions/checkout@v4 203 | - uses: actions-rs/toolchain@v1 204 | with: 205 | profile: minimal 206 | toolchain: nightly 207 | override: true 208 | components: rust-src 209 | 210 | - name: Test with sanitizer 211 | env: 212 | RUSTFLAGS: -Zsanitizer=${{ matrix.sanitizer }} ${{ matrix.extra_rustflags }} 213 | RUSTDOCFLAGS: -Zsanitizer=${{ matrix.sanitizer }} ${{ matrix.extra_rustflags }} 214 | run: | 215 | echo "note: RUSTFLAGS='$RUSTFLAGS'" 216 | cargo -Zbuild-std test --target=x86_64-unknown-linux-gnu --features="std serde substr" 217 | cargo -Zbuild-std test --target=x86_64-unknown-linux-gnu --all-features 218 | cargo -Zbuild-std test --target=x86_64-unknown-linux-gnu --no-default-features 219 | 220 | codecov-tarpaulin: 221 | name: coverage 222 | runs-on: ubuntu-latest 223 | container: 224 | image: xd009642/tarpaulin:develop-nightly 225 | options: --security-opt seccomp=unconfined 226 | steps: 227 | - uses: actions/checkout@v4 228 | - run: cargo tarpaulin --verbose --doc --all-features --all-targets --engine llvm --out xml 229 | - uses: codecov/codecov-action@v4 230 | with: 231 | token: ${{ secrets.CODECOV_TOKEN }} 232 | 233 | semver-checks: 234 | runs-on: ubuntu-latest 235 | steps: 236 | - uses: actions/checkout@v4 237 | - uses: obi1kenobi/cargo-semver-checks-action@v2 238 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /.tarpaulin.toml: -------------------------------------------------------------------------------- 1 | [all_features] 2 | features = "std serde substr substr-usize-indices" 3 | 4 | [substr_u32] 5 | features = "std serde substr" 6 | 7 | [no_features] 8 | features = "" 9 | 10 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "arcstr" 3 | version = "1.2.0" 4 | rust-version = "1.57.0" 5 | authors = ["Thom Chiovoloni "] 6 | edition = "2021" 7 | description = "A better reference-counted string type, with zero-cost (allocation-free) support for string literals, and reference counted substrings." 8 | license = "Apache-2.0 OR MIT OR Zlib" 9 | readme = "README.md" 10 | keywords = ["arc", "refcount", "arc_str", "rc_str", "string"] 11 | categories = [ 12 | "concurrency", 13 | "memory-management", 14 | "data-structures", 15 | "no-std", 16 | "rust-patterns", 17 | ] 18 | repository = "https://github.com/thomcc/arcstr" 19 | documentation = "https://docs.rs/arcstr" 20 | homepage = "https://github.com/thomcc/arcstr" 21 | include = ["src/**/*", "LICENSE-*", "README.md"] 22 | 23 | [features] 24 | std = [] 25 | default = ["substr"] 26 | substr = [] 27 | substr-usize-indices = ["substr"] 28 | 29 | [dependencies] 30 | serde = { version = "1", default-features = false, optional = true } 31 | 32 | [dev-dependencies] 33 | serde_test = { version = "1", default-features = false } 34 | 35 | [target.'cfg(loom)'.dev-dependencies] 36 | loom = "0.7.1" 37 | 38 | [package.metadata.docs.rs] 39 | features = ["std", "substr"] 40 | -------------------------------------------------------------------------------- /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 2016 The Miri Developers 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) 2020 Thom Chiovoloni 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. -------------------------------------------------------------------------------- /LICENSE-ZLIB: -------------------------------------------------------------------------------- 1 | Copyright (c) 2020 Thom Chiovoloni 2 | 3 | This software is provided 'as-is', without any express or implied warranty. In 4 | no event will the authors be held liable for any damages arising from the use of 5 | this software. 6 | 7 | Permission is granted to anyone to use this software for any purpose, including 8 | commercial applications, and to alter it and redistribute it freely, subject to 9 | the following restrictions: 10 | 11 | 1. The origin of this software must not be misrepresented; you must not claim 12 | that you wrote the original software. If you use this software in a product, an 13 | acknowledgment in the product documentation would be appreciated but is not 14 | required. 15 | 16 | 2. Altered source versions must be plainly marked as such, and must not be 17 | misrepresented as being the original software. 18 | 19 | 3. This notice may not be removed or altered from any source distribution. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # `arcstr`: Better reference-counted strings. 2 | 3 | [![Build Status](https://github.com/thomcc/arcstr/workflows/CI/badge.svg)](https://github.com/thomcc/arcstr/actions) 4 | [![codecov](https://codecov.io/gh/thomcc/arcstr/branch/main/graph/badge.svg)](https://codecov.io/gh/thomcc/arcstr) 5 | [![Docs](https://docs.rs/arcstr/badge.svg)](https://docs.rs/arcstr) 6 | [![Latest Version](https://img.shields.io/crates/v/arcstr.svg)](https://crates.io/crates/arcstr) 7 | ![Minimum Rust Version](https://img.shields.io/badge/MSRV%201.57-blue.svg) 8 | 9 | This crate defines `ArcStr`, a reference counted string type. It's essentially trying to be a better `Arc` or `Arc`, at least for most use cases. 10 | 11 | ArcStr intentionally gives up some of the features of `Arc` which are rarely-used for `Arc` (`Weak`, `Arc::make_mut`, ...). And in exchange, it gets a number of features that are very useful, especially for strings. Notably robust support for cheap/zero-cost `ArcStr`s holding static data (for example, string literals). 12 | 13 | (Aside from this, it's also a single pointer, which can be good for performance and FFI) 14 | 15 | Additionally, if the `substr` feature is enabled (and it is by default) we provide a `Substr` type which is essentially a `(ArcStr, Range)` with better ergonomics and more functionality, which represents a shared slice of a "parent" `ArcStr` (Note that in reality, `u32` is used for the index type, but this is not exposed in the API, and can be transparently changed via a cargo feature). 16 | 17 | ## Feature overview 18 | 19 | A quick tour of the distinguishing features (note that there's a list of [benefits](https://docs.rs/arcstr/%2a/arcstr/struct.ArcStr.html#benefits-of-arcstr-over-arcstr) in the `ArcStr` documentation which covers some of the reasons you might want to use it over other alternatives). Note that it offers essentially the full set of functionality string-like functionality you probably would expect from an immutable string type — these are just the unique selling points: 20 | 21 | ```rust 22 | use arcstr::ArcStr; 23 | // Works in const: 24 | const AMAZING: ArcStr = arcstr::literal!("amazing constant"); 25 | assert_eq!(AMAZING, "amazing constant"); 26 | 27 | // `arcstr::literal!` input can come from `include_str!` too: 28 | const MY_BEST_FILES: ArcStr = arcstr::literal!(include_str!("my-best-files.txt")); 29 | ``` 30 | 31 | Or, you can define the literals in normal expressions. Note that these literals are essentially ["Zero Cost"][zero-cost]. Specifically, below we not only don't allocate any heap memory to instantiate `wow` or any of the clones, we also don't have to perform any atomic reads or writes when cloning, or dropping them (or during any other operations on them). 32 | 33 | [zero-cost]: https://docs.rs/arcstr/%2a/arcstr/struct.ArcStr.html#what-does-zero-cost-literals-mean 34 | 35 | ```rust 36 | let wow: ArcStr = arcstr::literal!("Wow!"); 37 | assert_eq!("Wow!", wow); 38 | // This line is probably not something you want to do regularly, 39 | // but as mentioned, causes no extra allocations, nor performs any 40 | // atomic loads, stores, rmws, etc. 41 | let wowzers = wow.clone().clone().clone().clone(); 42 | 43 | // At some point in the future, we can get a `&'static str` out of one 44 | // of the literal `ArcStr`s too. 45 | let static_str: Option<&'static str> = ArcStr::as_static(&wowzers); 46 | assert_eq!(static_str, Some("Wow!")); 47 | 48 | // Note that this returns `None` for dynamically allocated `ArcStr`: 49 | let dynamic_arc = ArcStr::from(format!("cool {}", 123)); 50 | assert_eq!(ArcStr::as_static(&dynamic_arc), None); 51 | ``` 52 | 53 | Open TODO: Include `Substr` usage here, as it has some compelling use cases too! 54 | 55 | ## Usage 56 | 57 | It's a normal rust crate, drop it in your `Cargo.toml`'s dependencies section. In the somewhat unlikely case that you're here and don't know how: 58 | 59 | ```toml 60 | [dependencies] 61 | # ... 62 | arcstr = { version = "...", features = ["..."] } 63 | ``` 64 | 65 | The following cargo features are available. Only `substr` is on by default currently. 66 | 67 | - `std` (off by default): Turn on to use `std::process`'s aborting, instead of triggering an abort using the "double-panic trick". 68 | 69 | Essentially, there's one case we need to abort, and that's during a catastrophic error where you leak the same (dynamic) `ArcStr` 2^31 on 32-bit systems, or 2^63 in 64-bit systems. If this happens, we follow `libstd`'s lead and just abort because we're hosed anyway. If `std` is enabled, we use the real `std::process::abort`. If `std` is not enabled, we trigger an `abort` by triggering a panic while another panic is unwinding, which is either defined to cause an abort, or causes one in practice. 70 | 71 | In pratice you will never hit this edge case, and it still works in no_std, so no_std is the default. If you have to turn this on, because you hit this ridiculous case and found our handling bad, let me know. 72 | 73 | Concretely, the difference here is that without this, this case becomes a call to `core::intrinsics::abort`, and not `std::process::abort`. It's a ridiculously unlikely edge case to hit, but if you are to hit it, `std::process::abort` results in a `SIGABRT` whereas `core::intrinsics::abort` results in a `SIGILL`, and the former has meaningfully better UX. That said, it's extraordinarially unlikely that you manage to leak `2^31` or `2^63` copies of the same `ArcStr`, so it's not really worth depending on `std` by default for in our opinion. 74 | 75 | - `serde` (off by default): enable serde serialization of `ArcStr`. Note that this doesn't do any fancy deduping or whatever. 76 | 77 | - `substr` (**on by default**): implement the `Substr` type and related functions. 78 | 79 | - `substr-usize-indices` (off by default, implies `substr`): Use `usize` under the hood for the boundaries, instead of `u32`. 80 | 81 | Without this, if you use `Substr` and an index would overflow a `u32` we unceremoniously panic. 82 | 83 | ## Use of `unsafe` and testing strategy 84 | 85 | While this crate does contain a decent amount of unsafe code, we justify this in the following ways: 86 | 87 | 1. We have a very high test coverage ratio (essentially the only uncovered functions are the out-of-memory handler (which just calls `alloc::handle_alloc_error`), and an extremely pathological integer overflow where we just abort). 88 | 2. All tests pass under various sanitizers: `asan`, `msan`, `tsan`, and `miri`. 89 | 3. We have a few [`loom`](https://crates.io/crates/loom) models although I'd love to have more. 90 | 4. Our tests pass on a ton of different targets (thanks to [`cross`](https://github.com/rust-embedded/cross/) for many of these possible — easy even): 91 | - Linux x86, x86_64, armv7 (arm32), aarch64 (arm64), riscv64, mips32, and mips64 (the mips32 and mips64 targets allow us to check both big-endian 32bit and 64bit. Although we don't have any endian-specific code at the moment). 92 | - Windows 32-bit and 64-bit, on both GNU and MSVC toolchains. 93 | - MacOS on x86_64. 94 | 95 | Additionally, we test on Rust stable, beta, nightly, and our MSRV (see badge above for MSRV). 96 | 97 | #### Supported platforms 98 | 99 | Note that the above is *not* a list of supported platforms. In general I expect `arcstr` to support all platform's Rust supports, except for ones with `target_pointer_width="16"`, which *should* work if you turn off the `substr` feature. That said, if you'd like me to add a platform to the CI coverage to ensure it doesn't break, just ask\* (although, if it's harder than adding a line for another `cross` target, I'll probably need you to justify why it's likely to not be covered by the existing platform tests). 100 | 101 | \* This is why there are riscv64. 102 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | coverage: 2 | # Don't trigger CI failure on coverage reduction. I care about code coverage a 3 | # lot in this project, but I'm capable of making the decision on my own, and 4 | # can't run certain tests under coverage (loom's thread fuzzing stuff, for 5 | # example). 6 | status: 7 | project: 8 | default: 9 | informational: true 10 | branches: 11 | - main 12 | patch: 13 | default: 14 | informational: true 15 | branches: 16 | - main 17 | -------------------------------------------------------------------------------- /src/arc_str.rs: -------------------------------------------------------------------------------- 1 | #![allow( 2 | // We follow libstd's lead and prefer to define both. 3 | clippy::partialeq_ne_impl, 4 | // This is a really annoying clippy lint, since it's required for so many cases... 5 | clippy::cast_ptr_alignment, 6 | // For macros 7 | clippy::redundant_slicing, 8 | )] 9 | use core::alloc::Layout; 10 | use core::mem::{align_of, size_of, MaybeUninit}; 11 | use core::ptr::NonNull; 12 | #[cfg(not(all(loom, test)))] 13 | pub(crate) use core::sync::atomic::{AtomicUsize, Ordering}; 14 | #[cfg(all(loom, test))] 15 | pub(crate) use loom::sync::atomic::{AtomicUsize, Ordering}; 16 | 17 | #[cfg(feature = "substr")] 18 | use crate::Substr; 19 | use alloc::borrow::Cow; 20 | use alloc::boxed::Box; 21 | use alloc::string::String; 22 | 23 | /// A better atomically-reference counted string type. 24 | /// 25 | /// ## Benefits of `ArcStr` over `Arc` 26 | /// 27 | /// - It's possible to create a const `ArcStr` from a literal via the 28 | /// [`arcstr::literal!`][crate::literal] macro. This is probably the killer 29 | /// feature, to be honest. 30 | /// 31 | /// These "static" `ArcStr`s are zero cost, take no heap allocation, and don't 32 | /// even need to perform atomic reads/writes when being cloned or dropped (nor 33 | /// at any other time). 34 | /// 35 | /// They even get stored in the read-only memory of your executable, which can 36 | /// be beneficial for performance and memory usage. (In theory your linker may 37 | /// even dedupe these for you, but usually not) 38 | /// 39 | /// - `ArcStr`s from `arcstr::literal!` can be turned into `&'static str` safely 40 | /// at any time using [`ArcStr::as_static`]. (This returns an Option, which is 41 | /// `None` if the `ArcStr` was not static) 42 | /// 43 | /// - This should be unsurprising given the literal functionality, but 44 | /// [`ArcStr::new`] is able to be a `const` function. 45 | /// 46 | /// - `ArcStr` is thin, e.g. only a single pointer. Great for cases where you 47 | /// want to keep the data structure lightweight or need to do some FFI stuff 48 | /// with it. 49 | /// 50 | /// - `ArcStr` is totally immutable. No need to lose sleep because you're afraid 51 | /// of code which thinks it has a right to mutate your `Arc`s just because it 52 | /// holds the only reference... 53 | /// 54 | /// - Lower reference counting operations are lower overhead because we don't 55 | /// support `Weak` references. This can be a drawback for some use cases, but 56 | /// improves performance for the common case of no-weak-refs. 57 | /// 58 | /// ## What does "zero-cost literals" mean? 59 | /// 60 | /// In a few places I call the literal arcstrs "zero-cost". No overhead most 61 | /// accesses accesses (aside from stuff like `as_static` which obviously 62 | /// requires it). and it imposes a extra branch in both `clone` and `drop`. 63 | /// 64 | /// This branch in `clone`/`drop` is not on the result of an atomic load, and is 65 | /// just a normal memory read. This is actually what allows literal/static 66 | /// `ArcStr`s to avoid needing to perform any atomic operations in those 67 | /// functions, which seems likely more than cover the cost. 68 | /// 69 | /// (Additionally, it's almost certain that in the future we'll be able to 70 | /// reduce the synchronization required for atomic instructions. This is due to 71 | /// our guarantee of immutability and lack of support for `Weak`.) 72 | /// 73 | /// # Usage 74 | /// 75 | /// ## As a `const` 76 | /// 77 | /// The big unique feature of `ArcStr` is the ability to create static/const 78 | /// `ArcStr`s. (See [the macro](crate::literal) docs or the [feature 79 | /// overview][feats] 80 | /// 81 | /// [feats]: index.html#feature-overview 82 | /// 83 | /// ``` 84 | /// # use arcstr::ArcStr; 85 | /// const WOW: ArcStr = arcstr::literal!("cool robot!"); 86 | /// assert_eq!(WOW, "cool robot!"); 87 | /// ``` 88 | /// 89 | /// ## As a `str` 90 | /// 91 | /// (This is not unique to `ArcStr`, but is a frequent source of confusion I've 92 | /// seen): `ArcStr` implements `Deref`, and so all functions and 93 | /// methods from `str` work on it, even though we don't expose them on `ArcStr` 94 | /// directly. 95 | /// 96 | /// ``` 97 | /// # use arcstr::ArcStr; 98 | /// let s = ArcStr::from("something"); 99 | /// // These go through `Deref`, so they work even though 100 | /// // there is no `ArcStr::eq_ignore_ascii_case` function 101 | /// assert!(s.eq_ignore_ascii_case("SOMETHING")); 102 | /// ``` 103 | /// 104 | /// Additionally, `&ArcStr` can be passed to any function which accepts `&str`. 105 | /// For example: 106 | /// 107 | /// ``` 108 | /// # use arcstr::ArcStr; 109 | /// fn accepts_str(s: &str) { 110 | /// # let _ = s; 111 | /// // s... 112 | /// } 113 | /// 114 | /// let test_str: ArcStr = "test".into(); 115 | /// // This works even though `&test_str` is normally an `&ArcStr` 116 | /// accepts_str(&test_str); 117 | /// 118 | /// // Of course, this works for functionality from the standard library as well. 119 | /// let test_but_loud = ArcStr::from("TEST"); 120 | /// assert!(test_str.eq_ignore_ascii_case(&test_but_loud)); 121 | /// ``` 122 | 123 | #[repr(transparent)] 124 | pub struct ArcStr(NonNull); 125 | 126 | unsafe impl Sync for ArcStr {} 127 | unsafe impl Send for ArcStr {} 128 | 129 | impl ArcStr { 130 | /// Construct a new empty string. 131 | /// 132 | /// # Examples 133 | /// 134 | /// ``` 135 | /// # use arcstr::ArcStr; 136 | /// let s = ArcStr::new(); 137 | /// assert_eq!(s, ""); 138 | /// ``` 139 | #[inline] 140 | pub const fn new() -> Self { 141 | EMPTY 142 | } 143 | 144 | /// Attempt to copy the provided string into a newly allocated `ArcStr`, but 145 | /// return `None` if we cannot allocate the required memory. 146 | /// 147 | /// # Examples 148 | /// 149 | /// ``` 150 | /// # use arcstr::ArcStr; 151 | /// 152 | /// # fn do_stuff_with(s: ArcStr) {} 153 | /// 154 | /// let some_big_str = "please pretend this is a very long string"; 155 | /// if let Some(s) = ArcStr::try_alloc(some_big_str) { 156 | /// do_stuff_with(s); 157 | /// } else { 158 | /// // Complain about allocation failure, somehow. 159 | /// } 160 | /// ``` 161 | #[inline] 162 | pub fn try_alloc(copy_from: &str) -> Option { 163 | if let Ok(inner) = ThinInner::try_allocate(copy_from, false) { 164 | Some(Self(inner)) 165 | } else { 166 | None 167 | } 168 | } 169 | 170 | /// Attempt to allocate memory for an [`ArcStr`] of length `n`, and use the 171 | /// provided callback to fully initialize the provided buffer with valid 172 | /// UTF-8 text. 173 | /// 174 | /// This function returns `None` if memory allocation fails, see 175 | /// [`ArcStr::init_with_unchecked`] for a version which calls 176 | /// [`handle_alloc_error`](alloc::alloc::handle_alloc_error). 177 | /// 178 | /// # Safety 179 | /// The provided `initializer` callback must fully initialize the provided 180 | /// buffer with valid UTF-8 text. 181 | /// 182 | /// # Examples 183 | /// 184 | /// ``` 185 | /// # use arcstr::ArcStr; 186 | /// # use core::mem::MaybeUninit; 187 | /// let arcstr = unsafe { 188 | /// ArcStr::try_init_with_unchecked(10, |s: &mut [MaybeUninit]| { 189 | /// s.fill(MaybeUninit::new(b'a')); 190 | /// }).unwrap() 191 | /// }; 192 | /// assert_eq!(arcstr, "aaaaaaaaaa") 193 | /// ``` 194 | #[inline] 195 | pub unsafe fn try_init_with_unchecked(n: usize, initializer: F) -> Option 196 | where 197 | F: FnOnce(&mut [MaybeUninit]), 198 | { 199 | if let Ok(inner) = ThinInner::try_allocate_with(n, false, AllocInit::Uninit, initializer) { 200 | Some(Self(inner)) 201 | } else { 202 | None 203 | } 204 | } 205 | 206 | /// Allocate memory for an [`ArcStr`] of length `n`, and use the provided 207 | /// callback to fully initialize the provided buffer with valid UTF-8 text. 208 | /// 209 | /// This function calls 210 | /// [`handle_alloc_error`](alloc::alloc::handle_alloc_error) if memory 211 | /// allocation fails, see [`ArcStr::try_init_with_unchecked`] for a version 212 | /// which returns `None` 213 | /// 214 | /// # Safety 215 | /// The provided `initializer` callback must fully initialize the provided 216 | /// buffer with valid UTF-8 text. 217 | /// 218 | /// # Examples 219 | /// 220 | /// ``` 221 | /// # use arcstr::ArcStr; 222 | /// # use core::mem::MaybeUninit; 223 | /// let arcstr = unsafe { 224 | /// ArcStr::init_with_unchecked(10, |s: &mut [MaybeUninit]| { 225 | /// s.fill(MaybeUninit::new(b'a')); 226 | /// }) 227 | /// }; 228 | /// assert_eq!(arcstr, "aaaaaaaaaa") 229 | /// ``` 230 | #[inline] 231 | pub unsafe fn init_with_unchecked(n: usize, initializer: F) -> Self 232 | where 233 | F: FnOnce(&mut [MaybeUninit]), 234 | { 235 | match ThinInner::try_allocate_with(n, false, AllocInit::Uninit, initializer) { 236 | Ok(inner) => Self(inner), 237 | Err(None) => panic!("capacity overflow"), 238 | Err(Some(layout)) => alloc::alloc::handle_alloc_error(layout), 239 | } 240 | } 241 | 242 | /// Attempt to allocate memory for an [`ArcStr`] of length `n`, and use the 243 | /// provided callback to initialize the provided (initially-zeroed) buffer 244 | /// with valid UTF-8 text. 245 | /// 246 | /// Note: This function is provided with a zeroed buffer, and performs UTF-8 247 | /// validation after calling the initializer. While both of these are fast 248 | /// operations, some high-performance use cases will be better off using 249 | /// [`ArcStr::try_init_with_unchecked`] as the building block. 250 | /// 251 | /// # Errors 252 | /// The provided `initializer` callback must initialize the provided buffer 253 | /// with valid UTF-8 text, or a UTF-8 error will be returned. 254 | /// 255 | /// # Examples 256 | /// 257 | /// ``` 258 | /// # use arcstr::ArcStr; 259 | /// 260 | /// let s = ArcStr::init_with(5, |slice| { 261 | /// slice 262 | /// .iter_mut() 263 | /// .zip(b'0'..b'5') 264 | /// .for_each(|(db, sb)| *db = sb); 265 | /// }).unwrap(); 266 | /// assert_eq!(s, "01234"); 267 | /// ``` 268 | #[inline] 269 | pub fn init_with(n: usize, initializer: F) -> Result 270 | where 271 | F: FnOnce(&mut [u8]), 272 | { 273 | let mut failed = None::; 274 | let wrapper = |zeroed_slice: &mut [MaybeUninit]| { 275 | debug_assert_eq!(n, zeroed_slice.len()); 276 | // Safety: we pass `AllocInit::Zero`, so this is actually initialized 277 | let slice = unsafe { 278 | core::slice::from_raw_parts_mut(zeroed_slice.as_mut_ptr().cast::(), n) 279 | }; 280 | initializer(slice); 281 | if let Err(e) = core::str::from_utf8(slice) { 282 | failed = Some(e); 283 | } 284 | }; 285 | match unsafe { ThinInner::try_allocate_with(n, false, AllocInit::Zero, wrapper) } { 286 | Ok(inner) => { 287 | // Ensure we clean up the allocation even on error. 288 | let this = Self(inner); 289 | if let Some(e) = failed { 290 | Err(e) 291 | } else { 292 | Ok(this) 293 | } 294 | } 295 | Err(None) => panic!("capacity overflow"), 296 | Err(Some(layout)) => alloc::alloc::handle_alloc_error(layout), 297 | } 298 | } 299 | 300 | /// Extract a string slice containing our data. 301 | /// 302 | /// Note: This is an equivalent to our `Deref` implementation, but can be 303 | /// more readable than `&*s` in the cases where a manual invocation of 304 | /// `Deref` would be required. 305 | /// 306 | /// # Examples 307 | // TODO: find a better example where `&*` would have been required. 308 | /// ``` 309 | /// # use arcstr::ArcStr; 310 | /// let s = ArcStr::from("abc"); 311 | /// assert_eq!(s.as_str(), "abc"); 312 | /// ``` 313 | #[inline] 314 | pub fn as_str(&self) -> &str { 315 | self 316 | } 317 | 318 | /// Returns the length of this `ArcStr` in bytes. 319 | /// 320 | /// # Examples 321 | /// 322 | /// ``` 323 | /// # use arcstr::ArcStr; 324 | /// let a = ArcStr::from("foo"); 325 | /// assert_eq!(a.len(), 3); 326 | /// ``` 327 | #[inline] 328 | pub fn len(&self) -> usize { 329 | self.get_inner_len_flag().uint_part() 330 | } 331 | 332 | #[inline] 333 | fn get_inner_len_flag(&self) -> PackedFlagUint { 334 | unsafe { ThinInner::get_len_flag(self.0.as_ptr()) } 335 | } 336 | 337 | /// Returns true if this `ArcStr` is empty. 338 | /// 339 | /// # Examples 340 | /// 341 | /// ``` 342 | /// # use arcstr::ArcStr; 343 | /// assert!(!ArcStr::from("foo").is_empty()); 344 | /// assert!(ArcStr::new().is_empty()); 345 | /// ``` 346 | #[inline] 347 | pub fn is_empty(&self) -> bool { 348 | self.len() == 0 349 | } 350 | 351 | /// Convert us to a `std::string::String`. 352 | /// 353 | /// This is provided as an inherent method to avoid needing to route through 354 | /// the `Display` machinery, but is equivalent to `ToString::to_string`. 355 | /// 356 | /// # Examples 357 | /// 358 | /// ``` 359 | /// # use arcstr::ArcStr; 360 | /// let s = ArcStr::from("abc"); 361 | /// assert_eq!(s.to_string(), "abc"); 362 | /// ``` 363 | #[inline] 364 | #[allow(clippy::inherent_to_string_shadow_display)] 365 | pub fn to_string(&self) -> String { 366 | #[cfg(not(feature = "std"))] 367 | use alloc::borrow::ToOwned; 368 | self.as_str().to_owned() 369 | } 370 | 371 | /// Extract a byte slice containing the string's data. 372 | /// 373 | /// # Examples 374 | /// 375 | /// ``` 376 | /// # use arcstr::ArcStr; 377 | /// let foobar = ArcStr::from("foobar"); 378 | /// assert_eq!(foobar.as_bytes(), b"foobar"); 379 | /// ``` 380 | #[inline] 381 | pub fn as_bytes(&self) -> &[u8] { 382 | let len = self.len(); 383 | let p = self.0.as_ptr(); 384 | unsafe { 385 | let data = p.cast::().add(OFFSET_DATA); 386 | debug_assert_eq!(core::ptr::addr_of!((*p).data).cast::(), data); 387 | core::slice::from_raw_parts(data, len) 388 | } 389 | } 390 | 391 | /// Return the raw pointer this `ArcStr` wraps, for advanced use cases. 392 | /// 393 | /// Note that in addition to the `NonNull` constraint expressed in the type 394 | /// signature, we also guarantee the pointer has an alignment of at least 8 395 | /// bytes, even on platforms where a lower alignment would be acceptable. 396 | /// 397 | /// # Examples 398 | /// 399 | /// ``` 400 | /// # use arcstr::ArcStr; 401 | /// let s = ArcStr::from("abcd"); 402 | /// let p = ArcStr::into_raw(s); 403 | /// // Some time later... 404 | /// let s = unsafe { ArcStr::from_raw(p) }; 405 | /// assert_eq!(s, "abcd"); 406 | /// ``` 407 | #[inline] 408 | pub fn into_raw(this: Self) -> NonNull<()> { 409 | let p = this.0; 410 | core::mem::forget(this); 411 | p.cast() 412 | } 413 | 414 | /// The opposite version of [`Self::into_raw`]. Still intended only for 415 | /// advanced use cases. 416 | /// 417 | /// # Safety 418 | /// 419 | /// This function must be used on a valid pointer returned from 420 | /// [`ArcStr::into_raw`]. Additionally, you must ensure that a given `ArcStr` 421 | /// instance is only dropped once. 422 | /// 423 | /// # Examples 424 | /// 425 | /// ``` 426 | /// # use arcstr::ArcStr; 427 | /// let s = ArcStr::from("abcd"); 428 | /// let p = ArcStr::into_raw(s); 429 | /// // Some time later... 430 | /// let s = unsafe { ArcStr::from_raw(p) }; 431 | /// assert_eq!(s, "abcd"); 432 | /// ``` 433 | #[inline] 434 | pub unsafe fn from_raw(ptr: NonNull<()>) -> Self { 435 | Self(ptr.cast()) 436 | } 437 | 438 | /// Returns true if the two `ArcStr`s point to the same allocation. 439 | /// 440 | /// Note that functions like `PartialEq` check this already, so there's 441 | /// no performance benefit to doing something like `ArcStr::ptr_eq(&a1, &a2) || (a1 == a2)`. 442 | /// 443 | /// Caveat: `const`s aren't guaranteed to only occur in an executable a 444 | /// single time, and so this may be non-deterministic for `ArcStr` defined 445 | /// in a `const` with [`arcstr::literal!`][crate::literal], unless one 446 | /// was created by a `clone()` on the other. 447 | /// 448 | /// # Examples 449 | /// 450 | /// ``` 451 | /// use arcstr::ArcStr; 452 | /// 453 | /// let foobar = ArcStr::from("foobar"); 454 | /// let same_foobar = foobar.clone(); 455 | /// let other_foobar = ArcStr::from("foobar"); 456 | /// assert!(ArcStr::ptr_eq(&foobar, &same_foobar)); 457 | /// assert!(!ArcStr::ptr_eq(&foobar, &other_foobar)); 458 | /// 459 | /// const YET_AGAIN_A_DIFFERENT_FOOBAR: ArcStr = arcstr::literal!("foobar"); 460 | /// let strange_new_foobar = YET_AGAIN_A_DIFFERENT_FOOBAR.clone(); 461 | /// let wild_blue_foobar = strange_new_foobar.clone(); 462 | /// assert!(ArcStr::ptr_eq(&strange_new_foobar, &wild_blue_foobar)); 463 | /// ``` 464 | #[inline] 465 | pub fn ptr_eq(lhs: &Self, rhs: &Self) -> bool { 466 | core::ptr::eq(lhs.0.as_ptr(), rhs.0.as_ptr()) 467 | } 468 | 469 | /// Returns the number of references that exist to this `ArcStr`. If this is 470 | /// a static `ArcStr` (For example, one from 471 | /// [`arcstr::literal!`][crate::literal]), returns `None`. 472 | /// 473 | /// Despite the difference in return type, this is named to match the method 474 | /// from the stdlib's Arc: 475 | /// [`Arc::strong_count`][alloc::sync::Arc::strong_count]. 476 | /// 477 | /// If you aren't sure how to handle static `ArcStr` in the context of this 478 | /// return value, `ArcStr::strong_count(&s).unwrap_or(usize::MAX)` is 479 | /// frequently reasonable. 480 | /// 481 | /// # Safety 482 | /// 483 | /// This method by itself is safe, but using it correctly requires extra 484 | /// care. Another thread can change the strong count at any time, including 485 | /// potentially between calling this method and acting on the result. 486 | /// 487 | /// However, it may never change from `None` to `Some` or from `Some` to 488 | /// `None` for a given `ArcStr` — whether or not it is static is determined 489 | /// at construction, and never changes. 490 | /// 491 | /// # Examples 492 | /// 493 | /// ### Dynamic ArcStr 494 | /// ``` 495 | /// # use arcstr::ArcStr; 496 | /// let foobar = ArcStr::from("foobar"); 497 | /// assert_eq!(Some(1), ArcStr::strong_count(&foobar)); 498 | /// let also_foobar = ArcStr::clone(&foobar); 499 | /// assert_eq!(Some(2), ArcStr::strong_count(&foobar)); 500 | /// assert_eq!(Some(2), ArcStr::strong_count(&also_foobar)); 501 | /// ``` 502 | /// 503 | /// ### Static ArcStr 504 | /// ``` 505 | /// # use arcstr::ArcStr; 506 | /// let baz = arcstr::literal!("baz"); 507 | /// assert_eq!(None, ArcStr::strong_count(&baz)); 508 | /// // Similarly: 509 | /// assert_eq!(None, ArcStr::strong_count(&ArcStr::default())); 510 | /// ``` 511 | #[inline] 512 | pub fn strong_count(this: &Self) -> Option { 513 | let cf = Self::load_count_flag(this, Ordering::Acquire)?; 514 | if cf.flag_part() { 515 | None 516 | } else { 517 | Some(cf.uint_part()) 518 | } 519 | } 520 | 521 | /// Safety: Unsafe to use `this` is stored in static memory (check 522 | /// `Self::has_static_lenflag`) 523 | #[inline] 524 | unsafe fn load_count_flag_raw(this: &Self, ord_if_needed: Ordering) -> PackedFlagUint { 525 | PackedFlagUint::from_encoded((*this.0.as_ptr()).count_flag.load(ord_if_needed)) 526 | } 527 | 528 | #[inline] 529 | fn load_count_flag(this: &Self, ord_if_needed: Ordering) -> Option { 530 | if Self::has_static_lenflag(this) { 531 | None 532 | } else { 533 | let count_and_flag = PackedFlagUint::from_encoded(unsafe { 534 | (*this.0.as_ptr()).count_flag.load(ord_if_needed) 535 | }); 536 | Some(count_and_flag) 537 | } 538 | } 539 | 540 | /// Convert the `ArcStr` into a "static" `ArcStr`, even if it was originally 541 | /// created from runtime values. The `&'static str` is returned. 542 | /// 543 | /// This is useful if you want to use [`ArcStr::as_static`] or 544 | /// [`ArcStr::is_static`] on a value only known at runtime. 545 | /// 546 | /// If the `ArcStr` is already static, then this is a noop. 547 | /// 548 | /// # Caveats 549 | /// Calling this function on an ArcStr will cause us to never free it, thus 550 | /// leaking it's memory. Doing this excessively can lead to problems. 551 | /// 552 | /// # Examples 553 | /// ```no_run 554 | /// # // This isn't run because it needs a leakcheck suppression, 555 | /// # // which I can't seem to make work in CI (no symbols for 556 | /// # // doctests?). Instead, we test this in tests/arc_str.rs 557 | /// # use arcstr::ArcStr; 558 | /// let s = ArcStr::from("foobar"); 559 | /// assert!(!ArcStr::is_static(&s)); 560 | /// assert!(ArcStr::as_static(&s).is_none()); 561 | /// 562 | /// let leaked: &'static str = s.leak(); 563 | /// assert_eq!(leaked, s); 564 | /// assert!(ArcStr::is_static(&s)); 565 | /// assert_eq!(ArcStr::as_static(&s), Some("foobar")); 566 | /// ``` 567 | #[inline] 568 | pub fn leak(&self) -> &'static str { 569 | if Self::has_static_lenflag(self) { 570 | return unsafe { Self::to_static_unchecked(self) }; 571 | } 572 | let is_static_count = unsafe { 573 | // Not sure about ordering, maybe relaxed would be fine. 574 | Self::load_count_flag_raw(self, Ordering::Acquire) 575 | }; 576 | if is_static_count.flag_part() { 577 | return unsafe { Self::to_static_unchecked(self) }; 578 | } 579 | unsafe { Self::become_static(self, is_static_count.uint_part() == 1) }; 580 | debug_assert!(Self::is_static(self)); 581 | unsafe { Self::to_static_unchecked(self) } 582 | } 583 | 584 | unsafe fn become_static(this: &Self, is_unique: bool) { 585 | if is_unique { 586 | core::ptr::addr_of_mut!((*this.0.as_ptr()).count_flag).write(AtomicUsize::new( 587 | PackedFlagUint::new_raw(true, 1).encoded_value(), 588 | )); 589 | let lenp = core::ptr::addr_of_mut!((*this.0.as_ptr()).len_flag); 590 | debug_assert!(!lenp.read().flag_part()); 591 | lenp.write(lenp.read().with_flag(true)); 592 | } else { 593 | let flag_bit = PackedFlagUint::new_raw(true, 0).encoded_value(); 594 | let atomic_count_flag = &*core::ptr::addr_of!((*this.0.as_ptr()).count_flag); 595 | atomic_count_flag.fetch_or(flag_bit, Ordering::Release); 596 | } 597 | } 598 | 599 | #[inline] 600 | unsafe fn to_static_unchecked(this: &Self) -> &'static str { 601 | &*Self::str_ptr(this) 602 | } 603 | 604 | #[inline] 605 | fn bytes_ptr(this: &Self) -> *const [u8] { 606 | let len = this.get_inner_len_flag().uint_part(); 607 | unsafe { 608 | let p: *const ThinInner = this.0.as_ptr(); 609 | let data = p.cast::().add(OFFSET_DATA); 610 | debug_assert_eq!(core::ptr::addr_of!((*p).data).cast::(), data,); 611 | core::ptr::slice_from_raw_parts(data, len) 612 | } 613 | } 614 | 615 | #[inline] 616 | fn str_ptr(this: &Self) -> *const str { 617 | Self::bytes_ptr(this) as *const str 618 | } 619 | 620 | /// Returns true if `this` is a "static" ArcStr. For example, if it was 621 | /// created from a call to [`arcstr::literal!`][crate::literal]), 622 | /// returned by `ArcStr::new`, etc. 623 | /// 624 | /// Static `ArcStr`s can be converted to `&'static str` for free using 625 | /// [`ArcStr::as_static`], without leaking memory — they're static constants 626 | /// in the program (somewhere). 627 | /// 628 | /// # Examples 629 | /// 630 | /// ``` 631 | /// # use arcstr::ArcStr; 632 | /// const STATIC: ArcStr = arcstr::literal!("Electricity!"); 633 | /// assert!(ArcStr::is_static(&STATIC)); 634 | /// 635 | /// let still_static = arcstr::literal!("Shocking!"); 636 | /// assert!(ArcStr::is_static(&still_static)); 637 | /// assert!( 638 | /// ArcStr::is_static(&still_static.clone()), 639 | /// "Cloned statics are still static" 640 | /// ); 641 | /// 642 | /// let nonstatic = ArcStr::from("Grounded..."); 643 | /// assert!(!ArcStr::is_static(&nonstatic)); 644 | /// ``` 645 | #[inline] 646 | pub fn is_static(this: &Self) -> bool { 647 | // We align this to 16 bytes and keep the `is_static` flags in the same 648 | // place. In theory this means that if `cfg(target_feature = "avx")` 649 | // (where aligned 16byte loads are atomic), the compiler *could* 650 | // implement this function using the equivalent of: 651 | // ``` 652 | // let vec = _mm_load_si128(self.0.as_ptr().cast()); 653 | // let mask = _mm_movemask_pd(_mm_srli_epi64(vac, 63)); 654 | // mask != 0 655 | // ``` 656 | // and that's all; one load, no branching. (I don't think it *does*, but 657 | // I haven't checked so I'll be optimistic and keep the `#[repr(align)]` 658 | // -- hey, maybe the CPU can peephole-optimize it). 659 | // 660 | // That said, unless I did it in asm, *I* can't implement it that way, 661 | // since Rust's semantics don't allow me to make that change 662 | // optimization on my own (that load isn't considered atomic, for 663 | // example). 664 | this.get_inner_len_flag().flag_part() 665 | || unsafe { Self::load_count_flag_raw(this, Ordering::Relaxed).flag_part() } 666 | } 667 | 668 | /// This is true for any `ArcStr` that has been static from the time when it 669 | /// was created. It's cheaper than `has_static_rcflag`. 670 | #[inline] 671 | fn has_static_lenflag(this: &Self) -> bool { 672 | this.get_inner_len_flag().flag_part() 673 | } 674 | 675 | /// Returns true if `this` is a "static"/`"literal"` ArcStr. For example, if 676 | /// it was created from a call to [`literal!`][crate::literal]), returned by 677 | /// `ArcStr::new`, etc. 678 | /// 679 | /// Static `ArcStr`s can be converted to `&'static str` for free using 680 | /// [`ArcStr::as_static`], without leaking memory — they're static constants 681 | /// in the program (somewhere). 682 | /// 683 | /// # Examples 684 | /// 685 | /// ``` 686 | /// # use arcstr::ArcStr; 687 | /// const STATIC: ArcStr = arcstr::literal!("Electricity!"); 688 | /// assert_eq!(ArcStr::as_static(&STATIC), Some("Electricity!")); 689 | /// 690 | /// // Note that they don't have to be consts, just made using `literal!`: 691 | /// let still_static = arcstr::literal!("Shocking!"); 692 | /// assert_eq!(ArcStr::as_static(&still_static), Some("Shocking!")); 693 | /// // Cloning a static still produces a static. 694 | /// assert_eq!(ArcStr::as_static(&still_static.clone()), Some("Shocking!")); 695 | /// 696 | /// // But it won't work for strings from other sources. 697 | /// let nonstatic = ArcStr::from("Grounded..."); 698 | /// assert_eq!(ArcStr::as_static(&nonstatic), None); 699 | /// ``` 700 | #[inline] 701 | pub fn as_static(this: &Self) -> Option<&'static str> { 702 | if Self::is_static(this) { 703 | // We know static strings live forever, so they can have a static lifetime. 704 | Some(unsafe { &*(this.as_str() as *const str) }) 705 | } else { 706 | None 707 | } 708 | } 709 | 710 | // Not public API. Exists so the `arcstr::literal` macro can call it. 711 | #[inline] 712 | #[doc(hidden)] 713 | pub const unsafe fn _private_new_from_static_data( 714 | ptr: &'static StaticArcStrInner, 715 | ) -> Self { 716 | Self(NonNull::new_unchecked(ptr as *const _ as *mut ThinInner)) 717 | } 718 | 719 | /// `feature = "substr"` Returns a substr of `self` over the given range. 720 | /// 721 | /// # Examples 722 | /// 723 | /// ``` 724 | /// use arcstr::{ArcStr, Substr}; 725 | /// 726 | /// let a = ArcStr::from("abcde"); 727 | /// let b: Substr = a.substr(2..); 728 | /// 729 | /// assert_eq!(b, "cde"); 730 | /// ``` 731 | /// 732 | /// # Panics 733 | /// If any of the following are untrue, we panic 734 | /// - `range.start() <= range.end()` 735 | /// - `range.end() <= self.len()` 736 | /// - `self.is_char_boundary(start) && self.is_char_boundary(end)` 737 | /// - These can be conveniently verified in advance using 738 | /// `self.get(start..end).is_some()` if needed. 739 | #[cfg(feature = "substr")] 740 | #[inline] 741 | pub fn substr(&self, range: impl core::ops::RangeBounds) -> Substr { 742 | Substr::from_parts(self, range) 743 | } 744 | 745 | /// `feature = "substr"` Returns a [`Substr`] of self over the given `&str`. 746 | /// 747 | /// It is not rare to end up with a `&str` which holds a view into a 748 | /// `ArcStr`'s backing data. A common case is when using functionality that 749 | /// takes and returns `&str` and are entirely unaware of `arcstr`, for 750 | /// example: `str::trim()`. 751 | /// 752 | /// This function allows you to reconstruct a [`Substr`] from a `&str` which 753 | /// is a view into this `ArcStr`'s backing string. 754 | /// 755 | /// # Examples 756 | /// 757 | /// ``` 758 | /// use arcstr::{ArcStr, Substr}; 759 | /// let text = ArcStr::from(" abc"); 760 | /// let trimmed = text.trim(); 761 | /// let substr: Substr = text.substr_from(trimmed); 762 | /// assert_eq!(substr, "abc"); 763 | /// // for illustration 764 | /// assert!(ArcStr::ptr_eq(substr.parent(), &text)); 765 | /// assert_eq!(substr.range(), 3..6); 766 | /// ``` 767 | /// 768 | /// # Panics 769 | /// 770 | /// Panics if `substr` isn't a view into our memory. 771 | /// 772 | /// Also panics if `substr` is a view into our memory but is >= `u32::MAX` 773 | /// bytes away from our start, if we're a 64-bit machine and 774 | /// `substr-usize-indices` is not enabled. 775 | #[cfg(feature = "substr")] 776 | pub fn substr_from(&self, substr: &str) -> Substr { 777 | if substr.is_empty() { 778 | return Substr::new(); 779 | } 780 | 781 | let self_start = self.as_ptr() as usize; 782 | let self_end = self_start + self.len(); 783 | 784 | let substr_start = substr.as_ptr() as usize; 785 | let substr_end = substr_start + substr.len(); 786 | if substr_start < self_start || substr_end > self_end { 787 | out_of_range(self, &substr); 788 | } 789 | 790 | let index = substr_start - self_start; 791 | let end = index + substr.len(); 792 | self.substr(index..end) 793 | } 794 | 795 | /// `feature = "substr"` If possible, returns a [`Substr`] of self over the 796 | /// given `&str`. 797 | /// 798 | /// This is a fallible version of [`ArcStr::substr_from`]. 799 | /// 800 | /// It is not rare to end up with a `&str` which holds a view into a 801 | /// `ArcStr`'s backing data. A common case is when using functionality that 802 | /// takes and returns `&str` and are entirely unaware of `arcstr`, for 803 | /// example: `str::trim()`. 804 | /// 805 | /// This function allows you to reconstruct a [`Substr`] from a `&str` which 806 | /// is a view into this `ArcStr`'s backing string. 807 | /// 808 | /// # Examples 809 | /// 810 | /// ``` 811 | /// use arcstr::{ArcStr, Substr}; 812 | /// let text = ArcStr::from(" abc"); 813 | /// let trimmed = text.trim(); 814 | /// let substr: Option = text.try_substr_from(trimmed); 815 | /// assert_eq!(substr.unwrap(), "abc"); 816 | /// // `&str`s not derived from `self` will return None. 817 | /// let not_substr = text.try_substr_from("abc"); 818 | /// assert!(not_substr.is_none()); 819 | /// ``` 820 | /// 821 | /// # Panics 822 | /// 823 | /// Panics if `substr` is a view into our memory but is >= `u32::MAX` bytes 824 | /// away from our start, if we're a 64-bit machine and 825 | /// `substr-usize-indices` is not enabled. 826 | #[cfg(feature = "substr")] 827 | pub fn try_substr_from(&self, substr: &str) -> Option { 828 | if substr.is_empty() { 829 | return Some(Substr::new()); 830 | } 831 | 832 | let self_start = self.as_ptr() as usize; 833 | let self_end = self_start + self.len(); 834 | 835 | let substr_start = substr.as_ptr() as usize; 836 | let substr_end = substr_start + substr.len(); 837 | if substr_start < self_start || substr_end > self_end { 838 | return None; 839 | } 840 | 841 | let index = substr_start - self_start; 842 | let end = index + substr.len(); 843 | debug_assert!(self.get(index..end).is_some()); 844 | Some(self.substr(index..end)) 845 | } 846 | 847 | /// `feature = "substr"` Compute a derived `&str` a function of `&str` => 848 | /// `&str`, and produce a Substr of the result if possible. 849 | /// 850 | /// The function may return either a derived string, or any empty string. 851 | /// 852 | /// This function is mainly a wrapper around [`ArcStr::try_substr_from`]. If 853 | /// you're coming to `arcstr` from the `shared_string` crate, this is the 854 | /// moral equivalent of the `slice_with` function. 855 | /// 856 | /// # Examples 857 | /// 858 | /// ``` 859 | /// use arcstr::{ArcStr, Substr}; 860 | /// let text = ArcStr::from(" abc"); 861 | /// let trimmed: Option = text.try_substr_using(str::trim); 862 | /// assert_eq!(trimmed.unwrap(), "abc"); 863 | /// let other = text.try_substr_using(|_s| "different string!"); 864 | /// assert_eq!(other, None); 865 | /// // As a special case, this is allowed. 866 | /// let empty = text.try_substr_using(|_s| ""); 867 | /// assert_eq!(empty.unwrap(), ""); 868 | /// ``` 869 | #[cfg(feature = "substr")] 870 | pub fn try_substr_using(&self, f: impl FnOnce(&str) -> &str) -> Option { 871 | self.try_substr_from(f(self.as_str())) 872 | } 873 | 874 | /// `feature = "substr"` Compute a derived `&str` a function of `&str` => 875 | /// `&str`, and produce a Substr of the result. 876 | /// 877 | /// The function may return either a derived string, or any empty string. 878 | /// Returning anything else will result in a panic. 879 | /// 880 | /// This function is mainly a wrapper around [`ArcStr::try_substr_from`]. If 881 | /// you're coming to `arcstr` from the `shared_string` crate, this is the 882 | /// likely closest to the `slice_with_unchecked` function, but this panics 883 | /// instead of UB on dodginess. 884 | /// 885 | /// # Examples 886 | /// 887 | /// ``` 888 | /// use arcstr::{ArcStr, Substr}; 889 | /// let text = ArcStr::from(" abc"); 890 | /// let trimmed: Substr = text.substr_using(str::trim); 891 | /// assert_eq!(trimmed, "abc"); 892 | /// // As a special case, this is allowed. 893 | /// let empty = text.substr_using(|_s| ""); 894 | /// assert_eq!(empty, ""); 895 | /// ``` 896 | #[cfg(feature = "substr")] 897 | pub fn substr_using(&self, f: impl FnOnce(&str) -> &str) -> Substr { 898 | self.substr_from(f(self.as_str())) 899 | } 900 | 901 | /// Creates an `ArcStr` by repeating the source string `n` times 902 | /// 903 | /// # Errors 904 | /// 905 | /// This function returns an error if the capacity overflows or allocation 906 | /// fails. 907 | /// 908 | /// # Examples 909 | /// 910 | /// ``` 911 | /// use arcstr::ArcStr; 912 | /// 913 | /// let source = "A"; 914 | /// let repeated = ArcStr::try_repeat(source, 10); 915 | /// assert_eq!(repeated.unwrap(), "AAAAAAAAAA"); 916 | /// ``` 917 | pub fn try_repeat(source: &str, n: usize) -> Option { 918 | // If the source string is empty or the user asked for zero repetitions, 919 | // return an empty string 920 | if source.is_empty() || n == 0 { 921 | return Some(Self::new()); 922 | } 923 | 924 | // Calculate the capacity for the allocated string 925 | let capacity = source.len().checked_mul(n)?; 926 | let inner = 927 | ThinInner::try_allocate_maybe_uninit(capacity, false, AllocInit::Uninit).ok()?; 928 | 929 | unsafe { 930 | let mut data_ptr = ThinInner::data_ptr(inner); 931 | let data_end = data_ptr.add(capacity); 932 | 933 | // Copy `source` into the allocated string `n` times 934 | while data_ptr < data_end { 935 | core::ptr::copy_nonoverlapping(source.as_ptr(), data_ptr, source.len()); 936 | data_ptr = data_ptr.add(source.len()); 937 | } 938 | } 939 | 940 | Some(Self(inner)) 941 | } 942 | 943 | /// Creates an `ArcStr` by repeating the source string `n` times 944 | /// 945 | /// # Panics 946 | /// 947 | /// This function panics if the capacity overflows, see 948 | /// [`try_repeat`](ArcStr::try_repeat) if this is undesirable. 949 | /// 950 | /// # Examples 951 | /// 952 | /// Basic usage: 953 | /// ``` 954 | /// use arcstr::ArcStr; 955 | /// 956 | /// let source = "A"; 957 | /// let repeated = ArcStr::repeat(source, 10); 958 | /// assert_eq!(repeated, "AAAAAAAAAA"); 959 | /// ``` 960 | /// 961 | /// A panic upon overflow: 962 | /// ```should_panic 963 | /// # use arcstr::ArcStr; 964 | /// 965 | /// // this will panic at runtime 966 | /// let huge = ArcStr::repeat("A", usize::MAX); 967 | /// ``` 968 | pub fn repeat(source: &str, n: usize) -> Self { 969 | Self::try_repeat(source, n).expect("capacity overflow") 970 | } 971 | } 972 | 973 | #[cold] 974 | #[inline(never)] 975 | #[cfg(feature = "substr")] 976 | fn out_of_range(arc: &ArcStr, substr: &&str) -> ! { 977 | let arc_start = arc.as_ptr(); 978 | let arc_end = arc_start.wrapping_add(arc.len()); 979 | let substr_start = substr.as_ptr(); 980 | let substr_end = substr_start.wrapping_add(substr.len()); 981 | panic!( 982 | "ArcStr over ({:p}..{:p}) does not contain substr over ({:p}..{:p})", 983 | arc_start, arc_end, substr_start, substr_end, 984 | ); 985 | } 986 | 987 | impl Clone for ArcStr { 988 | #[inline] 989 | fn clone(&self) -> Self { 990 | if !Self::is_static(self) { 991 | // From libstd's impl: 992 | // 993 | // > Using a relaxed ordering is alright here, as knowledge of the 994 | // > original reference prevents other threads from erroneously deleting 995 | // > the object. 996 | // 997 | // See: https://doc.rust-lang.org/src/alloc/sync.rs.html#1073 998 | let n: PackedFlagUint = PackedFlagUint::from_encoded(unsafe { 999 | let step = PackedFlagUint::FALSE_ONE.encoded_value(); 1000 | (*self.0.as_ptr()) 1001 | .count_flag 1002 | .fetch_add(step, Ordering::Relaxed) 1003 | }); 1004 | // Protect against aggressive leaking of Arcs causing us to 1005 | // overflow. Technically, we could probably transition it to static 1006 | // here, but I haven't thought it through. 1007 | if n.uint_part() > RC_MAX && !n.flag_part() { 1008 | let val = PackedFlagUint::new_raw(true, 0).encoded_value(); 1009 | unsafe { 1010 | (*self.0.as_ptr()) 1011 | .count_flag 1012 | .fetch_or(val, Ordering::Release) 1013 | }; 1014 | // abort(); 1015 | } 1016 | } 1017 | Self(self.0) 1018 | } 1019 | } 1020 | const RC_MAX: usize = PackedFlagUint::UINT_PART_MAX / 2; 1021 | 1022 | impl Drop for ArcStr { 1023 | #[inline] 1024 | fn drop(&mut self) { 1025 | if Self::is_static(self) { 1026 | return; 1027 | } 1028 | unsafe { 1029 | let this = self.0.as_ptr(); 1030 | let enc = PackedFlagUint::from_encoded( 1031 | (*this) 1032 | .count_flag 1033 | .fetch_sub(PackedFlagUint::FALSE_ONE.encoded_value(), Ordering::Release), 1034 | ); 1035 | // Note: `enc == PackedFlagUint::FALSE_ONE` 1036 | if enc == PackedFlagUint::FALSE_ONE { 1037 | let _ = (*this).count_flag.load(Ordering::Acquire); 1038 | ThinInner::destroy_cold(this) 1039 | } 1040 | } 1041 | } 1042 | } 1043 | // Caveat on the `static`/`strong` fields: "is_static" indicates if we're 1044 | // located in static data (as with empty string). is_static being false meanse 1045 | // we are a normal arc-ed string. 1046 | // 1047 | // While `ArcStr` claims to hold a pointer to a `ThinInner`, for the static case 1048 | // we actually are using a pointer to a `StaticArcStrInner<[u8; N]>`. These have 1049 | // almost identical layouts, except the static contains a explicit trailing 1050 | // array, and does not have a `AtomicUsize` The issue is: We kind of want the 1051 | // static ones to not have any interior mutability, so that `const`s can use 1052 | // them, and so that they may be stored in read-only memory. 1053 | // 1054 | // We do this by keeping a flag in `len_flag` flag to indicate which case we're 1055 | // in, and maintaining the invariant that if we're a `StaticArcStrInner` **we 1056 | // may never access `.strong` in any way or produce a `&ThinInner` pointing to 1057 | // our data**. 1058 | // 1059 | // This is more subtle than you might think, sinc AFAIK we're not legally 1060 | // allowed to create an `&ThinInner` until we're 100% sure it's nonstatic, and 1061 | // prior to determining it, we are forced to work from entirely behind a raw 1062 | // pointer... 1063 | // 1064 | // That said, a bit of this hoop jumping might be not required in the future, 1065 | // but for now what we're doing works and is apparently sound: 1066 | // https://github.com/rust-lang/unsafe-code-guidelines/issues/246 1067 | #[repr(C, align(8))] 1068 | struct ThinInner { 1069 | // Both of these are `PackedFlagUint`s that store `is_static` as the flag. 1070 | // 1071 | // The reason it's not just stored in len is because an ArcStr may become 1072 | // static after creation (via `ArcStr::leak`) and we don't need to do an 1073 | // atomic load to access the length (and not only because it would mess with 1074 | // optimization). 1075 | // 1076 | // The reason it's not just stored in the count is because it may be UB to 1077 | // do atomic loads from read-only memory. This is also the reason it's not 1078 | // stored in a separate atomic, and why doing an atomic load to access the 1079 | // length wouldn't be acceptable even if compilers were really good. 1080 | len_flag: PackedFlagUint, 1081 | count_flag: AtomicUsize, 1082 | data: [u8; 0], 1083 | } 1084 | 1085 | const OFFSET_LENFLAGS: usize = 0; 1086 | const OFFSET_COUNTFLAGS: usize = size_of::(); 1087 | const OFFSET_DATA: usize = OFFSET_COUNTFLAGS + size_of::(); 1088 | 1089 | // Not public API, exists for macros. 1090 | #[repr(C, align(8))] 1091 | #[doc(hidden)] 1092 | pub struct StaticArcStrInner { 1093 | pub len_flag: usize, 1094 | pub count_flag: usize, 1095 | pub data: Buf, 1096 | } 1097 | 1098 | impl StaticArcStrInner { 1099 | #[doc(hidden)] 1100 | pub const STATIC_COUNT_VALUE: usize = PackedFlagUint::new_raw(true, 1).encoded_value(); 1101 | #[doc(hidden)] 1102 | #[inline] 1103 | pub const fn encode_len(v: usize) -> Option { 1104 | match PackedFlagUint::new(true, v) { 1105 | Some(v) => Some(v.encoded_value()), 1106 | None => None, 1107 | } 1108 | } 1109 | } 1110 | 1111 | const _: [(); size_of::>()] = [(); 2 * size_of::()]; 1112 | const _: [(); align_of::>()] = [(); 8]; 1113 | 1114 | const _: [(); size_of::()]>>()] = 1115 | [(); 4 * size_of::()]; 1116 | const _: [(); align_of::()]>>()] = [(); 8]; 1117 | 1118 | const _: [(); size_of::()] = [(); 2 * size_of::()]; 1119 | const _: [(); align_of::()] = [(); 8]; 1120 | 1121 | const _: [(); align_of::()] = [(); align_of::()]; 1122 | const _: [(); align_of::()] = [(); size_of::()]; 1123 | const _: [(); size_of::()] = [(); size_of::()]; 1124 | 1125 | const _: [(); align_of::()] = [(); align_of::()]; 1126 | const _: [(); size_of::()] = [(); size_of::()]; 1127 | 1128 | #[derive(Clone, Copy, PartialEq, Eq)] 1129 | #[repr(transparent)] 1130 | struct PackedFlagUint(usize); 1131 | impl PackedFlagUint { 1132 | const UINT_PART_MAX: usize = (1 << (usize::BITS - 1)) - 1; 1133 | /// Encodes `false` as the flag and `1` as the uint. Used for a few things, 1134 | /// such as the amount we `fetch_add` by for refcounting, and so on. 1135 | const FALSE_ONE: Self = Self::new_raw(false, 1); 1136 | 1137 | #[inline] 1138 | const fn new(flag_part: bool, uint_part: usize) -> Option { 1139 | if uint_part > Self::UINT_PART_MAX { 1140 | None 1141 | } else { 1142 | Some(Self::new_raw(flag_part, uint_part)) 1143 | } 1144 | } 1145 | 1146 | #[inline(always)] 1147 | const fn new_raw(flag_part: bool, uint_part: usize) -> Self { 1148 | Self(flag_part as usize | (uint_part << 1)) 1149 | } 1150 | 1151 | #[inline(always)] 1152 | const fn uint_part(self) -> usize { 1153 | self.0 >> 1 1154 | } 1155 | 1156 | #[inline(always)] 1157 | const fn flag_part(self) -> bool { 1158 | (self.0 & 1) != 0 1159 | } 1160 | 1161 | #[inline(always)] 1162 | const fn from_encoded(v: usize) -> Self { 1163 | Self(v) 1164 | } 1165 | 1166 | #[inline(always)] 1167 | const fn encoded_value(self) -> usize { 1168 | self.0 1169 | } 1170 | 1171 | #[inline(always)] 1172 | #[must_use] 1173 | const fn with_flag(self, v: bool) -> Self { 1174 | Self(v as usize | self.0) 1175 | } 1176 | } 1177 | 1178 | const EMPTY: ArcStr = literal!(""); 1179 | 1180 | impl ThinInner { 1181 | #[inline] 1182 | fn allocate(data: &str, initially_static: bool) -> NonNull { 1183 | match Self::try_allocate(data, initially_static) { 1184 | Ok(v) => v, 1185 | Err(None) => alloc_overflow(), 1186 | Err(Some(layout)) => alloc::alloc::handle_alloc_error(layout), 1187 | } 1188 | } 1189 | 1190 | #[inline] 1191 | fn data_ptr(this: NonNull) -> *mut u8 { 1192 | unsafe { this.as_ptr().cast::().add(OFFSET_DATA) } 1193 | } 1194 | 1195 | /// Allocates a `ThinInner` where the data segment is uninitialized or 1196 | /// zeroed. 1197 | /// 1198 | /// Returns `Err(Some(layout))` if we failed to allocate that layout, and 1199 | /// `Err(None)` for integer overflow when computing layout 1200 | fn try_allocate_maybe_uninit( 1201 | capacity: usize, 1202 | initially_static: bool, 1203 | init_how: AllocInit, 1204 | ) -> Result, Option> { 1205 | const ALIGN: usize = align_of::(); 1206 | 1207 | debug_assert_ne!(capacity, 0); 1208 | if capacity >= (isize::MAX as usize) - (OFFSET_DATA + ALIGN) { 1209 | return Err(None); 1210 | } 1211 | 1212 | debug_assert!(Layout::from_size_align(capacity + OFFSET_DATA, ALIGN).is_ok()); 1213 | let layout = unsafe { Layout::from_size_align_unchecked(capacity + OFFSET_DATA, ALIGN) }; 1214 | let ptr = match init_how { 1215 | AllocInit::Uninit => unsafe { alloc::alloc::alloc(layout) as *mut ThinInner }, 1216 | AllocInit::Zero => unsafe { alloc::alloc::alloc_zeroed(layout) as *mut ThinInner }, 1217 | }; 1218 | if ptr.is_null() { 1219 | return Err(Some(layout)); 1220 | } 1221 | 1222 | // we actually already checked this above... 1223 | debug_assert!(PackedFlagUint::new(initially_static, capacity).is_some()); 1224 | 1225 | let len_flag = PackedFlagUint::new_raw(initially_static, capacity); 1226 | debug_assert_eq!(len_flag.uint_part(), capacity); 1227 | debug_assert_eq!(len_flag.flag_part(), initially_static); 1228 | 1229 | unsafe { 1230 | core::ptr::addr_of_mut!((*ptr).len_flag).write(len_flag); 1231 | 1232 | let initial_count_flag = PackedFlagUint::new_raw(initially_static, 1); 1233 | let count_flag: AtomicUsize = AtomicUsize::new(initial_count_flag.encoded_value()); 1234 | core::ptr::addr_of_mut!((*ptr).count_flag).write(count_flag); 1235 | 1236 | debug_assert_eq!( 1237 | (ptr as *const u8).wrapping_add(OFFSET_DATA), 1238 | (*ptr).data.as_ptr(), 1239 | ); 1240 | 1241 | Ok(NonNull::new_unchecked(ptr)) 1242 | } 1243 | } 1244 | 1245 | // returns `Err(Some(l))` if we failed to allocate that layout, and 1246 | // `Err(None)` for integer overflow when computing layout. 1247 | #[inline] 1248 | fn try_allocate(data: &str, initially_static: bool) -> Result, Option> { 1249 | // Safety: we initialize the whole buffer by copying `data` into it. 1250 | unsafe { 1251 | // Allocate a enough space to hold the given string 1252 | Self::try_allocate_with( 1253 | data.len(), 1254 | initially_static, 1255 | AllocInit::Uninit, 1256 | // Copy the given string into the allocation 1257 | |uninit_slice| { 1258 | debug_assert_eq!(uninit_slice.len(), data.len()); 1259 | core::ptr::copy_nonoverlapping( 1260 | data.as_ptr(), 1261 | uninit_slice.as_mut_ptr().cast::(), 1262 | data.len(), 1263 | ) 1264 | }, 1265 | ) 1266 | } 1267 | } 1268 | 1269 | /// Safety: caller must fully initialize the provided buffer with valid 1270 | /// UTF-8 in the `initializer` function (well, you at least need to handle 1271 | /// it before giving it back to the user). 1272 | #[inline] 1273 | unsafe fn try_allocate_with( 1274 | len: usize, 1275 | initially_static: bool, 1276 | init_style: AllocInit, 1277 | initializer: impl FnOnce(&mut [core::mem::MaybeUninit]), 1278 | ) -> Result, Option> { 1279 | // Allocate a enough space to hold the given string 1280 | let this = Self::try_allocate_maybe_uninit(len, initially_static, init_style)?; 1281 | 1282 | initializer(core::slice::from_raw_parts_mut( 1283 | Self::data_ptr(this).cast::>(), 1284 | len, 1285 | )); 1286 | 1287 | Ok(this) 1288 | } 1289 | 1290 | #[inline] 1291 | unsafe fn get_len_flag(p: *const ThinInner) -> PackedFlagUint { 1292 | debug_assert_eq!(OFFSET_LENFLAGS, 0); 1293 | *p.cast() 1294 | } 1295 | 1296 | #[cold] 1297 | unsafe fn destroy_cold(p: *mut ThinInner) { 1298 | let lf = Self::get_len_flag(p); 1299 | let (is_static, len) = (lf.flag_part(), lf.uint_part()); 1300 | debug_assert!(!is_static); 1301 | let layout = { 1302 | let size = len + OFFSET_DATA; 1303 | let align = align_of::(); 1304 | Layout::from_size_align_unchecked(size, align) 1305 | }; 1306 | alloc::alloc::dealloc(p as *mut _, layout); 1307 | } 1308 | } 1309 | 1310 | #[derive(Clone, Copy, PartialEq)] 1311 | enum AllocInit { 1312 | Uninit, 1313 | Zero, 1314 | } 1315 | 1316 | #[inline(never)] 1317 | #[cold] 1318 | fn alloc_overflow() -> ! { 1319 | panic!("overflow during Layout computation") 1320 | } 1321 | 1322 | impl From<&str> for ArcStr { 1323 | #[inline] 1324 | fn from(s: &str) -> Self { 1325 | if s.is_empty() { 1326 | Self::new() 1327 | } else { 1328 | Self(ThinInner::allocate(s, false)) 1329 | } 1330 | } 1331 | } 1332 | 1333 | impl core::ops::Deref for ArcStr { 1334 | type Target = str; 1335 | #[inline] 1336 | fn deref(&self) -> &str { 1337 | unsafe { core::str::from_utf8_unchecked(self.as_bytes()) } 1338 | } 1339 | } 1340 | 1341 | impl Default for ArcStr { 1342 | #[inline] 1343 | fn default() -> Self { 1344 | Self::new() 1345 | } 1346 | } 1347 | 1348 | impl From for ArcStr { 1349 | #[inline] 1350 | fn from(v: String) -> Self { 1351 | v.as_str().into() 1352 | } 1353 | } 1354 | 1355 | impl From<&mut str> for ArcStr { 1356 | #[inline] 1357 | fn from(s: &mut str) -> Self { 1358 | let s: &str = s; 1359 | Self::from(s) 1360 | } 1361 | } 1362 | 1363 | impl From> for ArcStr { 1364 | #[inline] 1365 | fn from(s: Box) -> Self { 1366 | Self::from(&s[..]) 1367 | } 1368 | } 1369 | impl From for Box { 1370 | #[inline] 1371 | fn from(s: ArcStr) -> Self { 1372 | s.as_str().into() 1373 | } 1374 | } 1375 | impl From for alloc::rc::Rc { 1376 | #[inline] 1377 | fn from(s: ArcStr) -> Self { 1378 | s.as_str().into() 1379 | } 1380 | } 1381 | impl From for alloc::sync::Arc { 1382 | #[inline] 1383 | fn from(s: ArcStr) -> Self { 1384 | s.as_str().into() 1385 | } 1386 | } 1387 | impl From> for ArcStr { 1388 | #[inline] 1389 | fn from(s: alloc::rc::Rc) -> Self { 1390 | Self::from(&*s) 1391 | } 1392 | } 1393 | impl From> for ArcStr { 1394 | #[inline] 1395 | fn from(s: alloc::sync::Arc) -> Self { 1396 | Self::from(&*s) 1397 | } 1398 | } 1399 | impl<'a> From> for ArcStr { 1400 | #[inline] 1401 | fn from(s: Cow<'a, str>) -> Self { 1402 | Self::from(&*s) 1403 | } 1404 | } 1405 | impl<'a> From<&'a ArcStr> for Cow<'a, str> { 1406 | #[inline] 1407 | fn from(s: &'a ArcStr) -> Self { 1408 | Cow::Borrowed(s) 1409 | } 1410 | } 1411 | 1412 | impl<'a> From for Cow<'a, str> { 1413 | #[inline] 1414 | fn from(s: ArcStr) -> Self { 1415 | if let Some(st) = ArcStr::as_static(&s) { 1416 | Cow::Borrowed(st) 1417 | } else { 1418 | Cow::Owned(s.to_string()) 1419 | } 1420 | } 1421 | } 1422 | 1423 | impl From<&String> for ArcStr { 1424 | #[inline] 1425 | fn from(s: &String) -> Self { 1426 | Self::from(s.as_str()) 1427 | } 1428 | } 1429 | impl From<&ArcStr> for ArcStr { 1430 | #[inline] 1431 | fn from(s: &ArcStr) -> Self { 1432 | s.clone() 1433 | } 1434 | } 1435 | 1436 | impl core::fmt::Debug for ArcStr { 1437 | #[inline] 1438 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { 1439 | core::fmt::Debug::fmt(self.as_str(), f) 1440 | } 1441 | } 1442 | 1443 | impl core::fmt::Display for ArcStr { 1444 | #[inline] 1445 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { 1446 | core::fmt::Display::fmt(self.as_str(), f) 1447 | } 1448 | } 1449 | 1450 | impl PartialEq for ArcStr { 1451 | #[inline] 1452 | fn eq(&self, o: &Self) -> bool { 1453 | ArcStr::ptr_eq(self, o) || PartialEq::eq(self.as_str(), o.as_str()) 1454 | } 1455 | #[inline] 1456 | fn ne(&self, o: &Self) -> bool { 1457 | !ArcStr::ptr_eq(self, o) && PartialEq::ne(self.as_str(), o.as_str()) 1458 | } 1459 | } 1460 | 1461 | impl Eq for ArcStr {} 1462 | 1463 | macro_rules! impl_peq { 1464 | (@one $a:ty, $b:ty) => { 1465 | #[allow(clippy::extra_unused_lifetimes)] 1466 | impl<'a> PartialEq<$b> for $a { 1467 | #[inline] 1468 | fn eq(&self, s: &$b) -> bool { 1469 | PartialEq::eq(&self[..], &s[..]) 1470 | } 1471 | #[inline] 1472 | fn ne(&self, s: &$b) -> bool { 1473 | PartialEq::ne(&self[..], &s[..]) 1474 | } 1475 | } 1476 | }; 1477 | ($(($a:ty, $b:ty),)+) => {$( 1478 | impl_peq!(@one $a, $b); 1479 | impl_peq!(@one $b, $a); 1480 | )+}; 1481 | } 1482 | 1483 | impl_peq! { 1484 | (ArcStr, str), 1485 | (ArcStr, &'a str), 1486 | (ArcStr, String), 1487 | (ArcStr, Cow<'a, str>), 1488 | (ArcStr, Box), 1489 | (ArcStr, alloc::sync::Arc), 1490 | (ArcStr, alloc::rc::Rc), 1491 | (ArcStr, alloc::sync::Arc), 1492 | (ArcStr, alloc::rc::Rc), 1493 | } 1494 | 1495 | impl PartialOrd for ArcStr { 1496 | #[inline] 1497 | fn partial_cmp(&self, s: &Self) -> Option { 1498 | Some(self.as_str().cmp(s.as_str())) 1499 | } 1500 | } 1501 | 1502 | impl Ord for ArcStr { 1503 | #[inline] 1504 | fn cmp(&self, s: &Self) -> core::cmp::Ordering { 1505 | self.as_str().cmp(s.as_str()) 1506 | } 1507 | } 1508 | 1509 | impl core::hash::Hash for ArcStr { 1510 | #[inline] 1511 | fn hash(&self, h: &mut H) { 1512 | self.as_str().hash(h) 1513 | } 1514 | } 1515 | 1516 | macro_rules! impl_index { 1517 | ($($IdxT:ty,)*) => {$( 1518 | impl core::ops::Index<$IdxT> for ArcStr { 1519 | type Output = str; 1520 | #[inline] 1521 | fn index(&self, i: $IdxT) -> &Self::Output { 1522 | &self.as_str()[i] 1523 | } 1524 | } 1525 | )*}; 1526 | } 1527 | 1528 | impl_index! { 1529 | core::ops::RangeFull, 1530 | core::ops::Range, 1531 | core::ops::RangeFrom, 1532 | core::ops::RangeTo, 1533 | core::ops::RangeInclusive, 1534 | core::ops::RangeToInclusive, 1535 | } 1536 | 1537 | impl AsRef for ArcStr { 1538 | #[inline] 1539 | fn as_ref(&self) -> &str { 1540 | self 1541 | } 1542 | } 1543 | 1544 | impl AsRef<[u8]> for ArcStr { 1545 | #[inline] 1546 | fn as_ref(&self) -> &[u8] { 1547 | self.as_bytes() 1548 | } 1549 | } 1550 | 1551 | impl core::borrow::Borrow for ArcStr { 1552 | #[inline] 1553 | fn borrow(&self) -> &str { 1554 | self 1555 | } 1556 | } 1557 | 1558 | impl core::str::FromStr for ArcStr { 1559 | type Err = core::convert::Infallible; 1560 | #[inline] 1561 | fn from_str(s: &str) -> Result { 1562 | Ok(Self::from(s)) 1563 | } 1564 | } 1565 | 1566 | #[cfg(test)] 1567 | #[cfg(not(msrv))] // core::mem::offset_of! isn't stable in our MSRV 1568 | mod test { 1569 | use super::*; 1570 | 1571 | fn sasi_layout_check() { 1572 | assert!(align_of::>() >= 8); 1573 | assert_eq!( 1574 | core::mem::offset_of!(StaticArcStrInner, count_flag), 1575 | OFFSET_COUNTFLAGS 1576 | ); 1577 | assert_eq!( 1578 | core::mem::offset_of!(StaticArcStrInner, len_flag), 1579 | OFFSET_LENFLAGS 1580 | ); 1581 | assert_eq!( 1582 | core::mem::offset_of!(StaticArcStrInner, data), 1583 | OFFSET_DATA 1584 | ); 1585 | assert_eq!( 1586 | core::mem::offset_of!(ThinInner, count_flag), 1587 | core::mem::offset_of!(StaticArcStrInner::, count_flag), 1588 | ); 1589 | assert_eq!( 1590 | core::mem::offset_of!(ThinInner, len_flag), 1591 | core::mem::offset_of!(StaticArcStrInner::, len_flag), 1592 | ); 1593 | assert_eq!( 1594 | core::mem::offset_of!(ThinInner, data), 1595 | core::mem::offset_of!(StaticArcStrInner::, data), 1596 | ); 1597 | } 1598 | 1599 | #[test] 1600 | fn verify_type_pun_offsets_sasi_big_bufs() { 1601 | assert_eq!( 1602 | core::mem::offset_of!(ThinInner, count_flag), 1603 | OFFSET_COUNTFLAGS, 1604 | ); 1605 | assert_eq!(core::mem::offset_of!(ThinInner, len_flag), OFFSET_LENFLAGS); 1606 | assert_eq!(core::mem::offset_of!(ThinInner, data), OFFSET_DATA); 1607 | 1608 | assert!(align_of::() >= 8); 1609 | 1610 | sasi_layout_check::<[u8; 0]>(); 1611 | sasi_layout_check::<[u8; 1]>(); 1612 | sasi_layout_check::<[u8; 2]>(); 1613 | sasi_layout_check::<[u8; 3]>(); 1614 | sasi_layout_check::<[u8; 4]>(); 1615 | sasi_layout_check::<[u8; 5]>(); 1616 | sasi_layout_check::<[u8; 15]>(); 1617 | sasi_layout_check::<[u8; 16]>(); 1618 | sasi_layout_check::<[u8; 64]>(); 1619 | sasi_layout_check::<[u8; 128]>(); 1620 | sasi_layout_check::<[u8; 1024]>(); 1621 | sasi_layout_check::<[u8; 4095]>(); 1622 | sasi_layout_check::<[u8; 4096]>(); 1623 | } 1624 | } 1625 | 1626 | #[cfg(all(test, loom))] 1627 | mod loomtest { 1628 | use super::ArcStr; 1629 | use loom::sync::Arc; 1630 | use loom::thread; 1631 | #[test] 1632 | fn cloning_threads() { 1633 | loom::model(|| { 1634 | let a = ArcStr::from("abcdefgh"); 1635 | let addr = a.as_ptr() as usize; 1636 | 1637 | let a1 = Arc::new(a); 1638 | let a2 = a1.clone(); 1639 | 1640 | let t1 = thread::spawn(move || { 1641 | let b: ArcStr = (*a1).clone(); 1642 | assert_eq!(b.as_ptr() as usize, addr); 1643 | }); 1644 | let t2 = thread::spawn(move || { 1645 | let b: ArcStr = (*a2).clone(); 1646 | assert_eq!(b.as_ptr() as usize, addr); 1647 | }); 1648 | 1649 | t1.join().unwrap(); 1650 | t2.join().unwrap(); 1651 | }); 1652 | } 1653 | #[test] 1654 | fn drop_timing() { 1655 | loom::model(|| { 1656 | let a1 = alloc::vec![ 1657 | ArcStr::from("s1"), 1658 | ArcStr::from("s2"), 1659 | ArcStr::from("s3"), 1660 | ArcStr::from("s4"), 1661 | ]; 1662 | let a2 = a1.clone(); 1663 | 1664 | let t1 = thread::spawn(move || { 1665 | let mut a1 = a1; 1666 | while let Some(s) = a1.pop() { 1667 | assert!(s.starts_with("s")); 1668 | } 1669 | }); 1670 | let t2 = thread::spawn(move || { 1671 | let mut a2 = a2; 1672 | while let Some(s) = a2.pop() { 1673 | assert!(s.starts_with("s")); 1674 | } 1675 | }); 1676 | 1677 | t1.join().unwrap(); 1678 | t2.join().unwrap(); 1679 | }); 1680 | } 1681 | 1682 | #[test] 1683 | fn leak_drop() { 1684 | loom::model(|| { 1685 | let a1 = ArcStr::from("foo"); 1686 | let a2 = a1.clone(); 1687 | 1688 | let t1 = thread::spawn(move || { 1689 | drop(a1); 1690 | }); 1691 | let t2 = thread::spawn(move || a2.leak()); 1692 | t1.join().unwrap(); 1693 | let leaked: &'static str = t2.join().unwrap(); 1694 | assert_eq!(leaked, "foo"); 1695 | }); 1696 | } 1697 | } 1698 | -------------------------------------------------------------------------------- /src/impl_serde.rs: -------------------------------------------------------------------------------- 1 | use super::ArcStr; 2 | #[cfg(feature = "substr")] 3 | use super::Substr; 4 | 5 | use core::marker::PhantomData; 6 | use serde::{de, Deserialize, Deserializer, Serialize, Serializer}; 7 | 8 | impl Serialize for ArcStr { 9 | fn serialize(&self, ser: S) -> Result { 10 | ser.serialize_str(self) 11 | } 12 | } 13 | 14 | impl<'de> Deserialize<'de> for ArcStr { 15 | fn deserialize>(d: D) -> Result { 16 | d.deserialize_str(StrVisitor::(PhantomData)) 17 | } 18 | } 19 | 20 | #[cfg(feature = "substr")] 21 | impl Serialize for crate::Substr { 22 | fn serialize(&self, ser: S) -> Result { 23 | ser.serialize_str(self) 24 | } 25 | } 26 | 27 | #[cfg(feature = "substr")] 28 | impl<'de> Deserialize<'de> for Substr { 29 | fn deserialize>(d: D) -> Result { 30 | d.deserialize_str(StrVisitor::(PhantomData)) 31 | } 32 | } 33 | 34 | struct StrVisitor(PhantomData StrTy>); 35 | 36 | impl<'de, StrTy> de::Visitor<'de> for StrVisitor 37 | where 38 | for<'a> &'a str: Into, 39 | { 40 | type Value = StrTy; 41 | fn expecting(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { 42 | formatter.write_str("a string") 43 | } 44 | fn visit_str(self, v: &str) -> Result { 45 | Ok(v.into()) 46 | } 47 | fn visit_bytes(self, v: &[u8]) -> Result { 48 | match core::str::from_utf8(v) { 49 | Ok(s) => Ok(s.into()), 50 | Err(_) => Err(de::Error::invalid_value(de::Unexpected::Bytes(v), &self)), 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! # Better reference counted strings 2 | //! 3 | //! This crate defines [`ArcStr`], a type similar to `Arc`, but with a 4 | //! number of new features and functionality. There's a list of 5 | //! [benefits][benefits] in the `ArcStr` documentation comment which covers some 6 | //! of the reasons you might want to use it over other alternatives. 7 | //! 8 | //! Additionally, if the `substr` feature is enabled (and it is by default), we 9 | //! provide a [`Substr`] type which is essentially a `(ArcStr, Range)` 10 | //! with better ergonomics and more functionality, which represents a shared 11 | //! slice of a "parent" `ArcStr` (note that in reality, `u32` is used for the 12 | //! index type, but this is not exposed in the API, and can be transparently 13 | //! changed via a cargo feature). 14 | //! 15 | //! [benefits]: struct.ArcStr.html#benefits-of-arcstr-over-arcstr 16 | //! 17 | //! ## Feature overview 18 | //! 19 | //! A quick tour of the distinguishing features: 20 | //! 21 | //! ``` 22 | //! use arcstr::ArcStr; 23 | //! 24 | //! // Works in const: 25 | //! const MY_ARCSTR: ArcStr = arcstr::literal!("amazing constant"); 26 | //! assert_eq!(MY_ARCSTR, "amazing constant"); 27 | //! 28 | //! // `arcstr::literal!` input can come from `include_str!` too: 29 | //! # // We have to fake it here, but this has test coverage and such. 30 | //! # const _: &str = stringify!{ 31 | //! const MY_ARCSTR: ArcStr = arcstr::literal!(include_str!("my-best-files.txt")); 32 | //! # }; 33 | //! ``` 34 | //! 35 | //! Or, you can define the literals in normal expressions. Note that these 36 | //! literals are essentially ["Zero Cost"][zero-cost]. Specifically, below we 37 | //! not only avoid allocating any heap memory to instantiate `wow` or any of 38 | //! the clones, we also don't have to perform any atomic reads or writes. 39 | //! 40 | //! [zero-cost]: struct.ArcStr.html#what-does-zero-cost-literals-mean 41 | //! 42 | //! ``` 43 | //! use arcstr::ArcStr; 44 | //! 45 | //! let wow: ArcStr = arcstr::literal!("Wow!"); 46 | //! assert_eq!("Wow!", wow); 47 | //! // This line is probably not something you want to do regularly, 48 | //! // but causes no extra allocations, nor performs any atomic reads 49 | //! // nor writes. 50 | //! let wowzers = wow.clone().clone().clone().clone(); 51 | //! 52 | //! // At some point in the future, we can get a `&'static str` out of one 53 | //! // of the literal `ArcStr`s too. Note that this returns `None` for 54 | //! // a dynamically allocated `ArcStr`: 55 | //! let static_str: Option<&'static str> = ArcStr::as_static(&wowzers); 56 | //! assert_eq!(static_str, Some("Wow!")); 57 | //! ``` 58 | //! 59 | //! Of course, this is in addition to the typical functionality you might find in a 60 | //! non-borrowed string type (with the caveat that there is explicitly no way to 61 | //! mutate `ArcStr`). 62 | //! 63 | //! It's an open TODO to update this "feature tour" to include `Substr`. 64 | #![cfg_attr(not(feature = "std"), no_std)] 65 | #![deny(missing_docs)] 66 | #![allow(unknown_lints)] 67 | // for `cfg(loom)` and such -- I don't want to add a build.rs for this. 68 | #![allow(unexpected_cfgs)] 69 | 70 | #[doc(hidden)] 71 | pub extern crate alloc; 72 | 73 | #[doc(hidden)] 74 | pub use core; 75 | 76 | #[macro_use] 77 | mod mac; 78 | mod arc_str; 79 | #[cfg(feature = "serde")] 80 | mod impl_serde; 81 | pub use arc_str::ArcStr; 82 | 83 | #[cfg(feature = "substr")] 84 | mod substr; 85 | #[cfg(feature = "substr")] 86 | pub use substr::Substr; 87 | 88 | // Not public API, exists for macros 89 | #[doc(hidden)] 90 | pub mod _private { 91 | // Not part of public API. Transmutes a `*const u8` to a `&[u8; N]`. 92 | // 93 | // As of writing this, it's unstable to directly deref a raw pointer in 94 | // const code. We can get around this by transmuting (using the 95 | // const-transmute union trick) to transmute from `*const u8` to `&[u8; N]`, 96 | // and the dereferencing that. 97 | // 98 | // ... I'm a little surprised that this is allowed, but in general I do 99 | // remember a motivation behind stabilizing transmute in `const fn` was that 100 | // the union trick existed as a workaround. 101 | // 102 | // Anyway, this trick is courtesy of rodrimati1992 (that means you have to 103 | // blame them if it blows up :p). 104 | #[repr(C)] 105 | pub union ConstPtrDeref { 106 | pub p: *const u8, 107 | pub a: &'static Arr, 108 | } 109 | pub use crate::arc_str::StaticArcStrInner; 110 | pub use core::primitive::{str, u8}; 111 | } 112 | -------------------------------------------------------------------------------- /src/mac.rs: -------------------------------------------------------------------------------- 1 | /// Create a const [`ArcStr`](crate::ArcStr) from a string literal. The 2 | /// resulting `ArcStr` require no heap allocation, can be freely cloned and used 3 | /// interchangeably with `ArcStr`s from the heap, and are effectively "free". 4 | /// 5 | /// The main downside is that it's a macro. Eventually it may be doable as a 6 | /// `const fn`, which would be cleaner, but for now the drawbacks to this are 7 | /// not overwhelming, and the functionality it provides is very useful. 8 | /// 9 | /// # Usage 10 | /// 11 | /// ``` 12 | /// # use arcstr::ArcStr; 13 | /// // Works in const: 14 | /// const MY_ARCSTR: ArcStr = arcstr::literal!("testing testing"); 15 | /// assert_eq!(MY_ARCSTR, "testing testing"); 16 | /// 17 | /// // Or, just in normal expressions. 18 | /// assert_eq!("Wow!", arcstr::literal!("Wow!")); 19 | /// ``` 20 | /// 21 | /// Another motivating use case is bundled files: 22 | /// 23 | /// ```rust,ignore 24 | /// use arcstr::ArcStr; 25 | /// const VERY_IMPORTANT_FILE: ArcStr = 26 | /// arcstr::literal!(include_str!("./very-important.txt")); 27 | /// ``` 28 | #[macro_export] 29 | macro_rules! literal { 30 | ($text:expr $(,)?) => {{ 31 | // Note: extra scope to reduce the size of what's in `$text`'s scope 32 | // (note that consts in macros dont have hygene the way let does). 33 | const __TEXT: &$crate::_private::str = $text; 34 | { 35 | #[allow(clippy::declare_interior_mutable_const)] 36 | const SI: &$crate::_private::StaticArcStrInner<[$crate::_private::u8; __TEXT.len()]> = unsafe { 37 | &$crate::_private::StaticArcStrInner { 38 | len_flag: match $crate::_private::StaticArcStrInner::<[$crate::_private::u8; __TEXT.len()]>::encode_len(__TEXT.len()) { 39 | Some(len) => len, 40 | None => $crate::core::panic!("impossibly long length") 41 | }, 42 | count_flag: $crate::_private::StaticArcStrInner::<[$crate::_private::u8; __TEXT.len()]>::STATIC_COUNT_VALUE, 43 | // See comment for `_private::ConstPtrDeref` for what the hell's 44 | // going on here. 45 | data: *$crate::_private::ConstPtrDeref::<[$crate::_private::u8; __TEXT.len()]> { 46 | p: __TEXT.as_ptr(), 47 | } 48 | .a, 49 | // data: __TEXT.as_ptr().cast::<[$crate::_private::u8; __TEXT.len()]>().read(), 50 | } 51 | }; 52 | #[allow(clippy::declare_interior_mutable_const)] 53 | const S: $crate::ArcStr = unsafe { $crate::ArcStr::_private_new_from_static_data(SI) }; 54 | S 55 | } 56 | }}; 57 | } 58 | 59 | /// Conceptually equivalent to `ArcStr::from(format!("...", args...))`. 60 | /// 61 | /// In the future, this will be implemented in such a way to avoid an additional 62 | /// string copy which is required by the `from` operation. 63 | /// 64 | /// # Example 65 | /// 66 | /// ``` 67 | /// let arcstr = arcstr::format!("testing {}", 123); 68 | /// assert_eq!(arcstr, "testing 123"); 69 | /// ``` 70 | #[macro_export] 71 | macro_rules! format { 72 | ($($toks:tt)*) => { 73 | $crate::ArcStr::from($crate::alloc::fmt::format($crate::core::format_args!($($toks)*))) 74 | }; 75 | } 76 | 77 | /// `feature = "substr"`: Create a `const` [`Substr`][crate::Substr]. 78 | /// 79 | /// This is a wrapper that initializes a `Substr` over the entire contents of a 80 | /// `const` [`ArcStr`](crate::ArcStr) made using [arcstr::literal!](crate::literal). 81 | /// 82 | /// As with `arcstr::literal`, these require no heap allocation, can be freely 83 | /// cloned and used interchangeably with `ArcStr`s from the heap, and are 84 | /// effectively "free". 85 | /// 86 | /// The main use case here is in applications where `Substr` is a much more 87 | /// common string type than `ArcStr`. 88 | /// 89 | /// # Examples 90 | /// 91 | /// ``` 92 | /// use arcstr::{Substr, literal_substr}; 93 | /// // Works in const: 94 | /// const EXAMPLE_SUBSTR: Substr = literal_substr!("testing testing"); 95 | /// assert_eq!(EXAMPLE_SUBSTR, "testing testing"); 96 | /// 97 | /// // Or, just in normal expressions. 98 | /// assert_eq!("Wow!", literal_substr!("Wow!")); 99 | /// ``` 100 | #[macro_export] 101 | #[cfg(feature = "substr")] 102 | macro_rules! literal_substr { 103 | ($text:expr $(,)?) => {{ 104 | const __S: &$crate::_private::str = $text; 105 | { 106 | const PARENT: $crate::ArcStr = $crate::literal!(__S); 107 | const SUBSTR: $crate::Substr = 108 | unsafe { $crate::Substr::from_parts_unchecked(PARENT, 0..__S.len()) }; 109 | SUBSTR 110 | } 111 | }}; 112 | } 113 | 114 | #[cfg(test)] 115 | mod test { 116 | #[test] 117 | fn ensure_no_import() { 118 | let v = literal!("foo"); 119 | assert_eq!(v, "foo"); 120 | #[cfg(feature = "substr")] 121 | { 122 | let substr = literal_substr!("bar"); 123 | assert_eq!(substr, "bar"); 124 | } 125 | // Loom doesn't like it if you do things outside `loom::model`, AFAICT. 126 | // These calls produce error messages from inside `libstd` about 127 | // accessing thread_locals that haven't been initialized. 128 | #[cfg(not(loom))] 129 | { 130 | let test = crate::format!("foo"); 131 | assert_eq!(test, "foo"); 132 | let test2 = crate::format!("foo {}", 123); 133 | assert_eq!(test2, "foo 123"); 134 | #[cfg(not(msrv))] 135 | { 136 | let foo = "abc"; 137 | let test3 = crate::format!("foo {foo}"); 138 | assert_eq!(test3, "foo abc"); 139 | } 140 | } 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /src/substr.rs: -------------------------------------------------------------------------------- 1 | #![allow( 2 | // We follow libstd's lead and prefer to define both. 3 | clippy::partialeq_ne_impl, 4 | // This is a really annoying clippy lint, since it's required for so many cases... 5 | clippy::cast_ptr_alignment, 6 | // For macros 7 | clippy::redundant_slicing, 8 | )] 9 | #![cfg_attr(feature = "substr-usize-indices", allow(clippy::unnecessary_cast))] 10 | use crate::ArcStr; 11 | use core::ops::{Range, RangeBounds}; 12 | 13 | #[cfg(feature = "substr-usize-indices")] 14 | type Idx = usize; 15 | 16 | #[cfg(not(feature = "substr-usize-indices"))] 17 | type Idx = u32; 18 | 19 | #[cfg(not(any(target_pointer_width = "64", target_pointer_width = "32")))] 20 | compile_error!( 21 | "Non-32/64-bit pointers not supported right now due to insufficient \ 22 | testing on a platform like that. Please file a issue with the \ 23 | `arcstr` crate so we can talk about your use case if this is \ 24 | important to you." 25 | ); 26 | 27 | /// A low-cost string type representing a view into an [`ArcStr`]. 28 | /// 29 | /// Conceptually this is `(ArcStr, Range)` with ergonomic helpers. In 30 | /// implementation, the only difference between it and that is that the index 31 | /// type is `u32` unless the `substr-usize-indices` feature is enabled, which 32 | /// makes them use `usize`. 33 | /// 34 | /// # Examples 35 | /// 36 | /// ``` 37 | /// use arcstr::{ArcStr, Substr}; 38 | /// let parent = ArcStr::from("foo bar"); 39 | /// // The main way to create a Substr is with `ArcStr::substr`. 40 | /// let substr: Substr = parent.substr(3..); 41 | /// assert_eq!(substr, " bar"); 42 | /// // You can use `substr_using` to turn a function which is 43 | /// // `&str => &str` into a function over `Substr => Substr`. 44 | /// // See also `substr_from`, `try_substr_{from,using}`, and 45 | /// // the functions with the same name on `ArcStr`. 46 | /// let trimmed = substr.substr_using(str::trim); 47 | /// assert_eq!(trimmed, "bar"); 48 | /// ``` 49 | /// 50 | /// # Caveats 51 | /// 52 | /// The main caveat is the bit about index types. The index type is u32 by 53 | /// default. You can turn on `substr-usize-indices` if you desire though. The 54 | /// feature doesn't change the public API at all, just makes it able to handle 55 | /// enormous strings without panicking. This seems very niche to me, though. 56 | #[derive(Clone)] 57 | #[repr(C)] // We mentioned ArcStr being good at FFI at some point so why not 58 | pub struct Substr(ArcStr, Idx, Idx); 59 | 60 | #[inline] 61 | #[cfg(all(target_pointer_width = "64", not(feature = "substr-usize-indices")))] 62 | #[allow(clippy::let_unit_value)] 63 | const fn to_idx_const(i: usize) -> Idx { 64 | const DUMMY: [(); 1] = [()]; 65 | let _ = DUMMY[i >> 32]; 66 | i as Idx 67 | } 68 | #[inline] 69 | #[cfg(any(not(target_pointer_width = "64"), feature = "substr-usize-indices"))] 70 | const fn to_idx_const(i: usize) -> Idx { 71 | i as Idx 72 | } 73 | 74 | #[inline] 75 | #[cfg(all(target_pointer_width = "64", not(feature = "substr-usize-indices")))] 76 | fn to_idx(i: usize) -> Idx { 77 | if i > 0xffff_ffff { 78 | index_overflow(i); 79 | } 80 | i as Idx 81 | } 82 | 83 | #[inline] 84 | #[cfg(any(not(target_pointer_width = "64"), feature = "substr-usize-indices"))] 85 | fn to_idx(i: usize) -> Idx { 86 | i as Idx 87 | } 88 | 89 | #[cold] 90 | #[inline(never)] 91 | #[cfg(all(target_pointer_width = "64", not(feature = "substr-usize-indices")))] 92 | fn index_overflow(i: usize) -> ! { 93 | panic!("The index {} is too large for arcstr::Substr (enable the `substr-usize-indices` feature in `arcstr` if you need this)", i); 94 | } 95 | #[cold] 96 | #[inline(never)] 97 | fn bad_substr_idx(s: &ArcStr, i: usize, e: usize) -> ! { 98 | assert!(i <= e, "Bad substr range: start {} must be <= end {}", i, e); 99 | let max = if cfg!(all( 100 | target_pointer_width = "64", 101 | not(feature = "substr-usize-indices") 102 | )) { 103 | u32::MAX as usize 104 | } else { 105 | usize::MAX 106 | }; 107 | let len = s.len().min(max); 108 | assert!( 109 | e <= len, 110 | "Bad substr range: end {} must be <= string length/index max size {}", 111 | e, 112 | len 113 | ); 114 | assert!( 115 | s.is_char_boundary(i) && s.is_char_boundary(e), 116 | "Bad substr range: start and end must be on char boundaries" 117 | ); 118 | unreachable!( 119 | "[arcstr bug]: should have failed one of the above tests: \ 120 | please report me. debugging info: b={}, e={}, l={}, max={:#x}", 121 | i, 122 | e, 123 | s.len(), 124 | max 125 | ); 126 | } 127 | 128 | impl Substr { 129 | /// Construct an empty substr. 130 | /// 131 | /// # Examples 132 | /// ``` 133 | /// # use arcstr::Substr; 134 | /// let s = Substr::new(); 135 | /// assert_eq!(s, ""); 136 | /// ``` 137 | #[inline] 138 | pub const fn new() -> Self { 139 | Substr(ArcStr::new(), 0, 0) 140 | } 141 | 142 | /// Construct a Substr over the entire ArcStr. 143 | /// 144 | /// This is also provided as `Substr::from(some_arcstr)`, and can be 145 | /// accomplished with `a.substr(..)`, `a.into_substr(..)`, ... 146 | /// 147 | /// # Examples 148 | /// ``` 149 | /// # use arcstr::{Substr, ArcStr}; 150 | /// let s = Substr::full(ArcStr::from("foo")); 151 | /// assert_eq!(s, "foo"); 152 | /// assert_eq!(s.range(), 0..3); 153 | /// ``` 154 | #[inline] 155 | pub fn full(a: ArcStr) -> Self { 156 | let l = to_idx(a.len()); 157 | Substr(a, 0, l) 158 | } 159 | 160 | #[inline] 161 | pub(crate) fn from_parts(a: &ArcStr, range: impl RangeBounds) -> Self { 162 | use core::ops::Bound; 163 | let begin = match range.start_bound() { 164 | Bound::Included(&n) => n, 165 | Bound::Excluded(&n) => n + 1, 166 | Bound::Unbounded => 0, 167 | }; 168 | 169 | let end = match range.end_bound() { 170 | Bound::Included(&n) => n + 1, 171 | Bound::Excluded(&n) => n, 172 | Bound::Unbounded => a.len(), 173 | }; 174 | let _ = &a.as_str()[begin..end]; 175 | 176 | Self(ArcStr::clone(a), to_idx(begin), to_idx(end)) 177 | } 178 | 179 | /// Extract a substr of this substr. 180 | /// 181 | /// If the result would be empty, a new strong reference to our parent is 182 | /// not created. 183 | /// 184 | /// # Examples 185 | /// ``` 186 | /// # use arcstr::Substr; 187 | /// let s: Substr = arcstr::literal!("foobarbaz").substr(3..); 188 | /// assert_eq!(s.as_str(), "barbaz"); 189 | /// 190 | /// let s2 = s.substr(1..5); 191 | /// assert_eq!(s2, "arba"); 192 | /// ``` 193 | /// # Panics 194 | /// If any of the following are untrue, we panic 195 | /// - `range.start() <= range.end()` 196 | /// - `range.end() <= self.len()` 197 | /// - `self.is_char_boundary(start) && self.is_char_boundary(end)` 198 | /// - These can be conveniently verified in advance using 199 | /// `self.get(start..end).is_some()` if needed. 200 | #[inline] 201 | pub fn substr(&self, range: impl RangeBounds) -> Self { 202 | use core::ops::Bound; 203 | let my_end = self.2 as usize; 204 | 205 | let begin = match range.start_bound() { 206 | Bound::Included(&n) => n, 207 | Bound::Excluded(&n) => n + 1, 208 | Bound::Unbounded => 0, 209 | }; 210 | 211 | let end = match range.end_bound() { 212 | Bound::Included(&n) => n + 1, 213 | Bound::Excluded(&n) => n, 214 | Bound::Unbounded => self.len(), 215 | }; 216 | let new_begin = self.1 as usize + begin; 217 | let new_end = self.1 as usize + end; 218 | // let _ = &self.0.as_str()[new_begin..new_end]; 219 | if begin > end 220 | || end > my_end 221 | || !self.0.is_char_boundary(new_begin) 222 | || !self.0.is_char_boundary(new_end) 223 | { 224 | bad_substr_idx(&self.0, new_begin, new_end); 225 | } 226 | debug_assert!(self.0.get(new_begin..new_end).is_some()); 227 | 228 | debug_assert!(new_begin <= (Idx::MAX as usize) && new_end <= (Idx::MAX as usize)); 229 | 230 | Self(ArcStr::clone(&self.0), new_begin as Idx, new_end as Idx) 231 | } 232 | 233 | /// Extract a string slice containing our data. 234 | /// 235 | /// Note: This is an equivalent to our `Deref` implementation, but can be 236 | /// more readable than `&*s` in the cases where a manual invocation of 237 | /// `Deref` would be required. 238 | /// 239 | /// # Examples 240 | /// ``` 241 | /// # use arcstr::Substr; 242 | /// let s: Substr = arcstr::literal!("foobar").substr(3..); 243 | /// assert_eq!(s.as_str(), "bar"); 244 | /// ``` 245 | #[inline] 246 | pub fn as_str(&self) -> &str { 247 | self 248 | } 249 | 250 | /// Returns the length of this `Substr` in bytes. 251 | /// 252 | /// # Examples 253 | /// 254 | /// ``` 255 | /// # use arcstr::{ArcStr, Substr}; 256 | /// let a: Substr = ArcStr::from("foo").substr(1..); 257 | /// assert_eq!(a.len(), 2); 258 | /// ``` 259 | #[inline] 260 | pub fn len(&self) -> usize { 261 | debug_assert!(self.2 >= self.1); 262 | (self.2 - self.1) as usize 263 | } 264 | 265 | /// Returns true if this `Substr` is empty. 266 | /// 267 | /// # Examples 268 | /// 269 | /// ``` 270 | /// # use arcstr::Substr; 271 | /// assert!(arcstr::literal!("abc").substr(3..).is_empty()); 272 | /// assert!(!arcstr::literal!("abc").substr(2..).is_empty()); 273 | /// assert!(Substr::new().is_empty()); 274 | /// ``` 275 | #[inline] 276 | pub fn is_empty(&self) -> bool { 277 | self.2 == self.1 278 | } 279 | 280 | /// Convert us to a `std::string::String`. 281 | /// 282 | /// This is provided as an inherent method to avoid needing to route through 283 | /// the `Display` machinery, but is equivalent to `ToString::to_string`. 284 | /// 285 | /// # Examples 286 | /// 287 | /// ``` 288 | /// # use arcstr::Substr; 289 | /// let s: Substr = arcstr::literal!("12345").substr(1..4); 290 | /// assert_eq!(s.to_string(), "234"); 291 | /// ``` 292 | #[inline] 293 | #[allow(clippy::inherent_to_string_shadow_display)] 294 | pub fn to_string(&self) -> alloc::string::String { 295 | #[cfg(not(feature = "std"))] 296 | use alloc::borrow::ToOwned; 297 | self.as_str().to_owned() 298 | } 299 | 300 | /// Unchecked function to construct a [`Substr`] from an [`ArcStr`] and a 301 | /// byte range. Direct usage of this function is largely discouraged in 302 | /// favor of [`ArcStr::substr`][crate::ArcStr::substr], or the 303 | /// [`literal_substr!`](crate::literal_substr) macro, which currently is 304 | /// implemented using a call to this function (however, can guarantee safe 305 | /// usage). 306 | /// 307 | /// This is unsafe because currently `ArcStr` cannot provide a `&str` in a 308 | /// `const fn`. If that changes then we will likely deprecate this function, 309 | /// and provide a `pub const fn from_parts` with equivalent functionality. 310 | /// 311 | /// In the distant future, it would be nice if this accepted other kinds of 312 | /// ranges too. 313 | /// 314 | /// # Examples 315 | /// 316 | /// ``` 317 | /// use arcstr::{ArcStr, Substr}; 318 | /// const FOOBAR: ArcStr = arcstr::literal!("foobar"); 319 | /// const OBA: Substr = unsafe { Substr::from_parts_unchecked(FOOBAR, 2..5) }; 320 | /// assert_eq!(OBA, "oba"); 321 | /// ``` 322 | // TODO: can I do a compile_fail test that only is a failure under a certain feature? 323 | /// 324 | /// # Safety 325 | /// You promise that `range` is in bounds for `s`, and that the start and 326 | /// end are both on character boundaries. Note that we do check that the 327 | /// `usize` indices fit into `u32` if thats our configured index type, so 328 | /// `_unchecked` is not *entirely* a lie. 329 | /// 330 | /// # Panics 331 | /// If the `substr-usize-indices` is not enabled, and the target arch is 332 | /// 64-bit, and the usizes do not fit in 32 bits, then we panic with a 333 | /// (possibly strange-looking) index-out-of-bounds error in order to force 334 | /// compilation failure. 335 | #[inline] 336 | pub const unsafe fn from_parts_unchecked(s: ArcStr, range: Range) -> Self { 337 | Self(s, to_idx_const(range.start), to_idx_const(range.end)) 338 | } 339 | 340 | /// Returns `true` if the two `Substr`s have identical parents, and are 341 | /// covering the same range. 342 | /// 343 | /// Note that the "identical"ness of parents is determined by 344 | /// [`ArcStr::ptr_eq`], which can have surprising/nondeterministic results 345 | /// when used on `const` `ArcStr`s. It is guaranteed that `Substr::clone()`s 346 | /// will be `shallow_eq` eachother, however. 347 | /// 348 | /// This should generally only be used as an optimization, or a debugging 349 | /// aide. Additionally, it is already used in the implementation of 350 | /// `PartialEq`, so optimizing a comparison by performing it first is 351 | /// generally unnecessary. 352 | /// 353 | /// # Examples 354 | /// ``` 355 | /// # use arcstr::{ArcStr, Substr}; 356 | /// let parent = ArcStr::from("foooo"); 357 | /// let sub1 = parent.substr(1..3); 358 | /// let sub2 = parent.substr(1..3); 359 | /// assert!(Substr::shallow_eq(&sub1, &sub2)); 360 | /// // Same parent *and* contents, but over a different range: not `shallow_eq`. 361 | /// let not_same = parent.substr(3..); 362 | /// assert!(!Substr::shallow_eq(&sub1, ¬_same)); 363 | /// ``` 364 | #[inline] 365 | pub fn shallow_eq(this: &Self, o: &Self) -> bool { 366 | ArcStr::ptr_eq(&this.0, &o.0) && (this.1 == o.1) && (this.2 == o.2) 367 | } 368 | 369 | /// Returns the ArcStr this is a substring of. 370 | /// 371 | /// Note that the exact pointer value of this can be somewhat 372 | /// nondeterministic when used with `const` `ArcStr`s. For example 373 | /// 374 | /// ```rust,ignore 375 | /// const FOO: ArcStr = arcstr::literal!("foo"); 376 | /// // This is non-deterministic, as all references to a given 377 | /// // const are not required to point to the same value. 378 | /// ArcStr::ptr_eq(FOO.substr(..).parent(), &FOO); 379 | /// ``` 380 | /// 381 | /// # Examples 382 | /// 383 | /// ``` 384 | /// # use arcstr::ArcStr; 385 | /// let parent = ArcStr::from("abc def"); 386 | /// let child = parent.substr(2..5); 387 | /// assert!(ArcStr::ptr_eq(&parent, child.parent())); 388 | /// 389 | /// let child = parent.substr(..); 390 | /// assert_eq!(child.range(), 0..7); 391 | /// ``` 392 | #[inline] 393 | pub fn parent(&self) -> &ArcStr { 394 | &self.0 395 | } 396 | 397 | /// Returns the range of bytes we occupy inside our parent. 398 | /// 399 | /// This range is always guaranteed to: 400 | /// 401 | /// - Have an end >= start. 402 | /// - Have both start and end be less than or equal to `self.parent().len()` 403 | /// - Have both start and end be on meet `self.parent().is_char_boundary(b)` 404 | /// 405 | /// To put another way, it's always sound to do 406 | /// `s.parent().get_unchecked(s.range())`. 407 | /// 408 | /// ``` 409 | /// # use arcstr::ArcStr; 410 | /// let parent = ArcStr::from("abc def"); 411 | /// let child = parent.substr(2..5); 412 | /// assert_eq!(child.range(), 2..5); 413 | /// 414 | /// let child = parent.substr(..); 415 | /// assert_eq!(child.range(), 0..7); 416 | /// ``` 417 | #[inline] 418 | pub fn range(&self) -> Range { 419 | (self.1 as usize)..(self.2 as usize) 420 | } 421 | 422 | /// Returns a [`Substr`] of self over the given `&str`, or panics. 423 | /// 424 | /// It is not rare to end up with a `&str` which holds a view into a 425 | /// `Substr`'s backing data. A common case is when using functionality that 426 | /// takes and returns `&str` and are entirely unaware of `arcstr`, for 427 | /// example: `str::trim()`. 428 | /// 429 | /// This function allows you to reconstruct a [`Substr`] from a `&str` which 430 | /// is a view into this `Substr`'s backing string. 431 | /// 432 | /// See [`Substr::try_substr_from`] for a version that returns an option 433 | /// rather than panicking. 434 | /// 435 | /// # Examples 436 | /// 437 | /// ``` 438 | /// use arcstr::Substr; 439 | /// let text = Substr::from(" abc"); 440 | /// let trimmed = text.trim(); 441 | /// let substr: Substr = text.substr_from(trimmed); 442 | /// assert_eq!(substr, "abc"); 443 | /// ``` 444 | /// 445 | /// # Panics 446 | /// 447 | /// Panics if `substr` isn't a view into our memory. 448 | /// 449 | /// Also panics if `substr` is a view into our memory but is >= `u32::MAX` 450 | /// bytes away from our start, if we're a 64-bit machine and 451 | /// `substr-usize-indices` is not enabled. 452 | pub fn substr_from(&self, substr: &str) -> Substr { 453 | // TODO: should outline `expect` call to avoid fmt bloat and let us 454 | // provide better error message like we do for ArcStr 455 | self.try_substr_from(substr) 456 | .expect("non-substring passed to Substr::substr_from") 457 | } 458 | 459 | /// If possible, returns a [`Substr`] of self over the given `&str`. 460 | /// 461 | /// This is a fallible version of [`Substr::substr_from`]. 462 | /// 463 | /// It is not rare to end up with a `&str` which holds a view into a 464 | /// `ArcStr`'s backing data. A common case is when using functionality that 465 | /// takes and returns `&str` and are entirely unaware of `arcstr`, for 466 | /// example: `str::trim()`. 467 | /// 468 | /// This function allows you to reconstruct a [`Substr`] from a `&str` which 469 | /// is a view into this [`Substr`]'s backing string. Note that we accept the 470 | /// empty string as input, in which case we return the same value as 471 | /// [`Substr::new`] (For clarity, this no longer holds a reference to 472 | /// `self.parent()`). 473 | /// 474 | /// # Examples 475 | /// 476 | /// ``` 477 | /// use arcstr::Substr; 478 | /// let text = Substr::from(" abc"); 479 | /// let trimmed = text.trim(); 480 | /// let substr: Option = text.try_substr_from(trimmed); 481 | /// assert_eq!(substr.unwrap(), "abc"); 482 | /// // `&str`s not derived from `self` will return None. 483 | /// let not_substr = text.try_substr_from("abc"); 484 | /// assert!(not_substr.is_none()); 485 | /// ``` 486 | /// 487 | /// # Panics 488 | /// 489 | /// Panics if `substr` is a view into our memory but is >= `u32::MAX` bytes 490 | /// away from our start, on a 64-bit machine, when `substr-usize-indices` is 491 | /// not enabled. 492 | pub fn try_substr_from(&self, substr: &str) -> Option { 493 | if substr.is_empty() { 494 | return Some(Substr::new()); 495 | } 496 | let parent_ptr = self.0.as_ptr() as usize; 497 | let self_start = parent_ptr + (self.1 as usize); 498 | let self_end = parent_ptr + (self.2 as usize); 499 | 500 | let substr_start = substr.as_ptr() as usize; 501 | let substr_end = substr_start + substr.len(); 502 | if substr_start < self_start || substr_end > self_end { 503 | return None; 504 | } 505 | 506 | let index = substr_start - self_start; 507 | let end = index + substr.len(); 508 | Some(self.substr(index..end)) 509 | } 510 | /// Compute a derived `&str` a function of `&str` => `&str`, and produce a 511 | /// Substr of the result if possible. 512 | /// 513 | /// The function may return either a derived string, or any empty string. 514 | /// 515 | /// This function is mainly a wrapper around [`Substr::try_substr_from`]. If 516 | /// you're coming to `arcstr` from the `shared_string` crate, this is the 517 | /// moral equivalent of the `slice_with` function. 518 | /// 519 | /// # Examples 520 | /// 521 | /// ``` 522 | /// use arcstr::Substr; 523 | /// let text = Substr::from(" abc"); 524 | /// let trimmed: Option = text.try_substr_using(str::trim); 525 | /// assert_eq!(trimmed.unwrap(), "abc"); 526 | /// let other = text.try_substr_using(|_s| "different string!"); 527 | /// assert_eq!(other, None); 528 | /// // As a special case, this is allowed. 529 | /// let empty = text.try_substr_using(|_s| ""); 530 | /// assert_eq!(empty.unwrap(), ""); 531 | /// ``` 532 | pub fn try_substr_using(&self, f: impl FnOnce(&str) -> &str) -> Option { 533 | self.try_substr_from(f(self.as_str())) 534 | } 535 | /// Compute a derived `&str` a function of `&str` => `&str`, and produce a 536 | /// Substr of the result. 537 | /// 538 | /// The function may return either a derived string, or any empty string. 539 | /// Returning anything else will result in a panic. 540 | /// 541 | /// This function is mainly a wrapper around [`Substr::try_substr_from`]. If 542 | /// you're coming to `arcstr` from the `shared_string` crate, this is the 543 | /// likely closest to the `slice_with_unchecked` function, but this panics 544 | /// instead of UB on dodginess. 545 | /// 546 | /// # Examples 547 | /// 548 | /// ``` 549 | /// use arcstr::Substr; 550 | /// let text = Substr::from(" abc"); 551 | /// let trimmed: Substr = text.substr_using(str::trim); 552 | /// assert_eq!(trimmed, "abc"); 553 | /// // As a special case, this is allowed. 554 | /// let empty = text.substr_using(|_s| ""); 555 | /// assert_eq!(empty, ""); 556 | /// ``` 557 | pub fn substr_using(&self, f: impl FnOnce(&str) -> &str) -> Self { 558 | self.substr_from(f(self.as_str())) 559 | } 560 | } 561 | 562 | impl From for Substr { 563 | #[inline] 564 | fn from(a: ArcStr) -> Self { 565 | Self::full(a) 566 | } 567 | } 568 | 569 | impl From<&ArcStr> for Substr { 570 | #[inline] 571 | fn from(a: &ArcStr) -> Self { 572 | Self::full(a.clone()) 573 | } 574 | } 575 | 576 | impl core::ops::Deref for Substr { 577 | type Target = str; 578 | #[inline] 579 | fn deref(&self) -> &str { 580 | debug_assert!(self.0.get((self.1 as usize)..(self.2 as usize)).is_some()); 581 | unsafe { self.0.get_unchecked((self.1 as usize)..(self.2 as usize)) } 582 | } 583 | } 584 | 585 | impl PartialEq for Substr { 586 | #[inline] 587 | fn eq(&self, o: &Self) -> bool { 588 | Substr::shallow_eq(self, o) || PartialEq::eq(self.as_str(), o.as_str()) 589 | } 590 | #[inline] 591 | fn ne(&self, o: &Self) -> bool { 592 | !Substr::shallow_eq(self, o) && PartialEq::ne(self.as_str(), o.as_str()) 593 | } 594 | } 595 | 596 | impl PartialEq for Substr { 597 | #[inline] 598 | fn eq(&self, o: &ArcStr) -> bool { 599 | (ArcStr::ptr_eq(&self.0, o) && (self.1 == 0) && (self.2 as usize == o.len())) 600 | || PartialEq::eq(self.as_str(), o.as_str()) 601 | } 602 | #[inline] 603 | fn ne(&self, o: &ArcStr) -> bool { 604 | (!ArcStr::ptr_eq(&self.0, o) || (self.1 != 0) || (self.2 as usize != o.len())) 605 | && PartialEq::ne(self.as_str(), o.as_str()) 606 | } 607 | } 608 | impl PartialEq for ArcStr { 609 | #[inline] 610 | fn eq(&self, o: &Substr) -> bool { 611 | PartialEq::eq(o, self) 612 | } 613 | #[inline] 614 | fn ne(&self, o: &Substr) -> bool { 615 | PartialEq::ne(o, self) 616 | } 617 | } 618 | 619 | impl Eq for Substr {} 620 | 621 | impl PartialOrd for Substr { 622 | #[inline] 623 | fn partial_cmp(&self, s: &Self) -> Option { 624 | Some(self.as_str().cmp(s.as_str())) 625 | } 626 | } 627 | 628 | impl Ord for Substr { 629 | #[inline] 630 | fn cmp(&self, s: &Self) -> core::cmp::Ordering { 631 | self.as_str().cmp(s.as_str()) 632 | } 633 | } 634 | 635 | impl core::hash::Hash for Substr { 636 | #[inline] 637 | fn hash(&self, h: &mut H) { 638 | self.as_str().hash(h) 639 | } 640 | } 641 | 642 | impl core::fmt::Debug for Substr { 643 | #[inline] 644 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { 645 | core::fmt::Debug::fmt(self.as_str(), f) 646 | } 647 | } 648 | 649 | impl core::fmt::Display for Substr { 650 | #[inline] 651 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { 652 | core::fmt::Display::fmt(self.as_str(), f) 653 | } 654 | } 655 | 656 | impl Default for Substr { 657 | #[inline] 658 | fn default() -> Self { 659 | Self::new() 660 | } 661 | } 662 | 663 | macro_rules! impl_from_via_arcstr { 664 | ($($SrcTy:ty),+) => {$( 665 | impl From<$SrcTy> for Substr { 666 | #[inline] 667 | fn from(v: $SrcTy) -> Self { 668 | Self::full(ArcStr::from(v)) 669 | } 670 | } 671 | )+}; 672 | } 673 | impl_from_via_arcstr![ 674 | &str, 675 | &mut str, 676 | alloc::string::String, 677 | &alloc::string::String, 678 | alloc::boxed::Box, 679 | alloc::rc::Rc, 680 | alloc::sync::Arc, 681 | alloc::borrow::Cow<'_, str> 682 | ]; 683 | 684 | impl<'a> From<&'a Substr> for alloc::borrow::Cow<'a, str> { 685 | #[inline] 686 | fn from(s: &'a Substr) -> Self { 687 | alloc::borrow::Cow::Borrowed(s) 688 | } 689 | } 690 | 691 | impl<'a> From for alloc::borrow::Cow<'a, str> { 692 | #[inline] 693 | fn from(s: Substr) -> Self { 694 | if let Some(st) = ArcStr::as_static(&s.0) { 695 | debug_assert!(st.get(s.range()).is_some()); 696 | alloc::borrow::Cow::Borrowed(unsafe { st.get_unchecked(s.range()) }) 697 | } else { 698 | alloc::borrow::Cow::Owned(s.to_string()) 699 | } 700 | } 701 | } 702 | 703 | macro_rules! impl_peq { 704 | (@one $a:ty, $b:ty) => { 705 | #[allow(clippy::extra_unused_lifetimes)] 706 | impl<'a> PartialEq<$b> for $a { 707 | #[inline] 708 | fn eq(&self, s: &$b) -> bool { 709 | PartialEq::eq(&self[..], &s[..]) 710 | } 711 | #[inline] 712 | fn ne(&self, s: &$b) -> bool { 713 | PartialEq::ne(&self[..], &s[..]) 714 | } 715 | } 716 | }; 717 | ($(($a:ty, $b:ty),)+) => {$( 718 | impl_peq!(@one $a, $b); 719 | impl_peq!(@one $b, $a); 720 | )+}; 721 | } 722 | 723 | impl_peq! { 724 | (Substr, str), 725 | (Substr, &'a str), 726 | (Substr, alloc::string::String), 727 | (Substr, alloc::borrow::Cow<'a, str>), 728 | (Substr, alloc::boxed::Box), 729 | (Substr, alloc::sync::Arc), 730 | (Substr, alloc::rc::Rc), 731 | } 732 | 733 | macro_rules! impl_index { 734 | ($($IdxT:ty,)*) => {$( 735 | impl core::ops::Index<$IdxT> for Substr { 736 | type Output = str; 737 | #[inline] 738 | fn index(&self, i: $IdxT) -> &Self::Output { 739 | &self.as_str()[i] 740 | } 741 | } 742 | )*}; 743 | } 744 | 745 | impl_index! { 746 | core::ops::RangeFull, 747 | core::ops::Range, 748 | core::ops::RangeFrom, 749 | core::ops::RangeTo, 750 | core::ops::RangeInclusive, 751 | core::ops::RangeToInclusive, 752 | } 753 | 754 | impl AsRef for Substr { 755 | #[inline] 756 | fn as_ref(&self) -> &str { 757 | self 758 | } 759 | } 760 | 761 | impl AsRef<[u8]> for Substr { 762 | #[inline] 763 | fn as_ref(&self) -> &[u8] { 764 | self.as_bytes() 765 | } 766 | } 767 | 768 | impl core::borrow::Borrow for Substr { 769 | #[inline] 770 | fn borrow(&self) -> &str { 771 | self 772 | } 773 | } 774 | 775 | impl core::str::FromStr for Substr { 776 | type Err = core::convert::Infallible; 777 | #[inline] 778 | fn from_str(s: &str) -> Result { 779 | Ok(Self::from(ArcStr::from(s))) 780 | } 781 | } 782 | 783 | #[cfg(test)] 784 | mod test { 785 | use super::*; 786 | #[test] 787 | #[should_panic] 788 | #[cfg(not(miri))] // XXX does miri still hate unwinding? 789 | #[cfg(all(target_pointer_width = "64", not(feature = "substr-usize-indices")))] 790 | fn test_from_parts_unchecked_err() { 791 | let s = crate::literal!("foo"); 792 | // Note: this is actually a violation of the safety requirement of 793 | // from_parts_unchecked (the indices are illegal), but I can't get an 794 | // ArcStr that's big enough, and I'm the author so I know it's fine 795 | // because we hit the panic case. 796 | let _u = unsafe { Substr::from_parts_unchecked(s, 0x1_0000_0000usize..0x1_0000_0001) }; 797 | } 798 | #[test] 799 | fn test_from_parts_unchecked_valid() { 800 | let s = crate::literal!("foobar"); 801 | let u = unsafe { Substr::from_parts_unchecked(s, 2..5) }; 802 | assert_eq!(&*u, "oba"); 803 | } 804 | } 805 | -------------------------------------------------------------------------------- /tests/arc_str.rs: -------------------------------------------------------------------------------- 1 | #![allow( 2 | // need to test cloning, these are deliberate. 3 | clippy::redundant_clone, 4 | // yep, we create owned instance just for comparison, to test comparison 5 | // with owned instacnces. 6 | clippy::cmp_owned, 7 | unexpected_cfgs, 8 | )] 9 | use arcstr::ArcStr; 10 | 11 | #[test] 12 | fn test_various_partial_eq() { 13 | macro_rules! check_partial_eq { 14 | (@eq1; $a:expr, $b:expr) => {{ 15 | // Note: intentionally not assert_eq. 16 | assert!($a == $b); 17 | assert!(!($a != $b)); 18 | assert!($b == $a); 19 | assert!(!($b != $a)); 20 | }}; 21 | (@ne1; $a:expr, $b:expr) => { 22 | assert!($a != $b); 23 | assert!(!($a == $b)); 24 | assert!($b != $a); 25 | assert!(!($b == $a)); 26 | }; 27 | (@eq; $a:expr, $b:expr) => {{ 28 | check_partial_eq!(@eq1; $a, $b); 29 | check_partial_eq!(@eq1; $a.clone(), $b); 30 | check_partial_eq!(@eq1; $a.clone(), $a); 31 | }}; 32 | (@ne; $a:expr, $b:expr) => {{ 33 | check_partial_eq!(@ne1; $a, $b); 34 | check_partial_eq!(@ne1; $a.clone(), $b); 35 | }}; 36 | } 37 | 38 | check_partial_eq!(@eq; ArcStr::from("123"), "123"); 39 | check_partial_eq!(@eq; ArcStr::from("foobar"), *"foobar"); 40 | check_partial_eq!(@eq; ArcStr::from("🏳️‍🌈"), String::from("🏳️‍🌈")); 41 | check_partial_eq!(@eq; ArcStr::from("🏳️‍⚧️"), std::borrow::Cow::Borrowed("🏳️‍⚧️")); 42 | check_partial_eq!(@eq; ArcStr::from("🏴‍☠️"), std::borrow::Cow::Owned("🏴‍☠️".into())); 43 | check_partial_eq!(@eq; ArcStr::from(":o"), std::rc::Rc::::from(":o")); 44 | check_partial_eq!(@eq; ArcStr::from("!!!"), std::sync::Arc::::from("!!!")); 45 | 46 | check_partial_eq!(@eq; ArcStr::from(""), ""); 47 | check_partial_eq!(@eq; ArcStr::from(""), ArcStr::from("")); 48 | 49 | check_partial_eq!(@ne; ArcStr::from("123"), "124"); 50 | check_partial_eq!(@ne; ArcStr::from("Foobar"), *"FoobarFoobar"); 51 | 52 | check_partial_eq!(@ne; ArcStr::from("①"), String::from("1")); 53 | check_partial_eq!(@ne; ArcStr::from(""), String::from("1")); 54 | check_partial_eq!(@ne; ArcStr::from("abc"), String::from("")); 55 | 56 | check_partial_eq!(@ne; ArcStr::from("butts"), std::borrow::Cow::Borrowed("boots")); 57 | check_partial_eq!(@ne; ArcStr::from("bots"), std::borrow::Cow::Owned("🤖".into())); 58 | check_partial_eq!(@ne; ArcStr::from("put"), std::rc::Rc::::from("⛳️")); 59 | check_partial_eq!(@ne; ArcStr::from("pots"), std::sync::Arc::::from("🍲")); 60 | } 61 | 62 | #[test] 63 | fn test_indexing() { 64 | let a = ArcStr::from("12345"); 65 | assert_eq!(&a[..], "12345"); 66 | assert_eq!(&a[1..], "2345"); 67 | assert_eq!(&a[..4], "1234"); 68 | assert_eq!(&a[1..4], "234"); 69 | assert_eq!(&a[1..=3], "234"); 70 | assert_eq!(&a[..=3], "1234"); 71 | } 72 | 73 | #[test] 74 | fn test_fmt() { 75 | assert_eq!(format!("{}", ArcStr::from("test")), "test"); 76 | assert_eq!(format!("{:?}", ArcStr::from("test")), "\"test\""); 77 | 78 | // make sure we forward formatting to the real impl... 79 | let s = ArcStr::from("uwu"); 80 | assert_eq!(format!("{:.<6}", s), "uwu..."); 81 | assert_eq!(format!("{:.>6}", s), "...uwu"); 82 | assert_eq!(format!("{:.^9}", s), r#"...uwu..."#); 83 | } 84 | 85 | #[test] 86 | fn test_ord() { 87 | let mut arr = [ArcStr::from("foo"), "bar".into(), "baz".into()]; 88 | arr.sort(); 89 | assert_eq!(&arr, &["bar", "baz", "foo"]); 90 | } 91 | 92 | #[test] 93 | fn smoke_test_clone() { 94 | let count = if cfg!(miri) { 20 } else { 100 }; 95 | for _ in 0..count { 96 | drop(vec![ArcStr::from("foobar"); count]); 97 | drop(vec![ArcStr::from("baz quux"); count]); 98 | let lit = { arcstr::literal!("test 999") }; 99 | drop(vec![lit; count]); 100 | } 101 | drop(vec![ArcStr::default(); count]); 102 | } 103 | 104 | #[test] 105 | fn test_btreemap() { 106 | let mut m = std::collections::BTreeMap::new(); 107 | 108 | for i in 0..100 { 109 | let prev = m.insert(ArcStr::from(format!("key {}", i)), i); 110 | assert_eq!(prev, None); 111 | } 112 | 113 | for i in 0..100 { 114 | let s = format!("key {}", i); 115 | assert_eq!(m.remove(s.as_str()), Some(i)); 116 | } 117 | } 118 | #[test] 119 | fn test_hashmap() { 120 | let mut m = std::collections::HashMap::new(); 121 | for i in 0..100 { 122 | let prev = m.insert(ArcStr::from(format!("key {}", i)), i); 123 | assert_eq!(prev, None); 124 | } 125 | for i in 0..100 { 126 | let key = format!("key {}", i); 127 | let search = key.as_str(); 128 | assert_eq!(m[search], i); 129 | assert_eq!(m.remove(search), Some(i)); 130 | } 131 | } 132 | 133 | #[cfg(feature = "serde")] 134 | #[test] 135 | fn test_serde() { 136 | use serde_test::{assert_de_tokens, assert_tokens, Token}; 137 | let teststr = ArcStr::from("test test 123 456"); 138 | assert_tokens(&teststr, &[Token::BorrowedStr("test test 123 456")]); 139 | assert_tokens(&teststr.clone(), &[Token::BorrowedStr("test test 123 456")]); 140 | assert_tokens(&ArcStr::default(), &[Token::BorrowedStr("")]); 141 | 142 | let checks = &[ 143 | [Token::Str("123")], 144 | [Token::BorrowedStr("123")], 145 | [Token::String("123")], 146 | [Token::Bytes(b"123")], 147 | [Token::BorrowedBytes(b"123")], 148 | [Token::ByteBuf(b"123")], 149 | ]; 150 | for check in checks { 151 | eprintln!("checking {:?}", check); 152 | assert_de_tokens(&ArcStr::from("123"), check); 153 | } 154 | } 155 | 156 | #[test] 157 | fn test_loose_ends() { 158 | assert_eq!(ArcStr::default(), ""); 159 | assert_eq!("abc".parse::().unwrap(), "abc"); 160 | let abc_arc = ArcStr::from("abc"); 161 | let abc_str: &str = abc_arc.as_ref(); 162 | let abc_bytes: &[u8] = abc_arc.as_ref(); 163 | assert_eq!(abc_str, "abc"); 164 | assert_eq!(abc_bytes, b"abc"); 165 | } 166 | 167 | #[test] 168 | fn test_from_into_raw() { 169 | let a = vec![ 170 | ArcStr::default(), 171 | ArcStr::from("1234"), 172 | ArcStr::from(format!("test {}", 1)), 173 | ]; 174 | let v = a.into_iter().cycle().take(100).collect::>(); 175 | let v2 = v 176 | .iter() 177 | .map(|s| ArcStr::into_raw(s.clone())) 178 | .collect::>(); 179 | drop(v); 180 | let back = v2 181 | .iter() 182 | .map(|s| unsafe { ArcStr::from_raw(*s) }) 183 | .collect::>(); 184 | 185 | let end = [ 186 | ArcStr::default(), 187 | ArcStr::from("1234"), 188 | ArcStr::from(format!("test {}", 1)), 189 | ] 190 | .iter() 191 | .cloned() 192 | .cycle() 193 | .take(100) 194 | .collect::>(); 195 | assert_eq!(back, end); 196 | drop(back); 197 | } 198 | 199 | #[test] 200 | fn test_strong_count() { 201 | let foobar = ArcStr::from("foobar"); 202 | assert_eq!(Some(1), ArcStr::strong_count(&foobar)); 203 | let also_foobar = ArcStr::clone(&foobar); 204 | assert_eq!(Some(2), ArcStr::strong_count(&foobar)); 205 | assert_eq!(Some(2), ArcStr::strong_count(&also_foobar)); 206 | 207 | let astr = arcstr::literal!("baz"); 208 | assert_eq!(None, ArcStr::strong_count(&astr)); 209 | assert_eq!(None, ArcStr::strong_count(&ArcStr::default())); 210 | } 211 | 212 | #[test] 213 | fn test_ptr_eq() { 214 | let foobar = ArcStr::from("foobar"); 215 | let same_foobar = foobar.clone(); 216 | let other_foobar = ArcStr::from("foobar"); 217 | assert!(ArcStr::ptr_eq(&foobar, &same_foobar)); 218 | assert!(!ArcStr::ptr_eq(&foobar, &other_foobar)); 219 | 220 | const YET_AGAIN_A_DIFFERENT_FOOBAR: ArcStr = arcstr::literal!("foobar"); 221 | let strange_new_foobar = YET_AGAIN_A_DIFFERENT_FOOBAR.clone(); 222 | let wild_blue_foobar = strange_new_foobar.clone(); 223 | assert!(ArcStr::ptr_eq(&strange_new_foobar, &wild_blue_foobar)); 224 | } 225 | 226 | #[test] 227 | fn test_statics() { 228 | const STATIC: ArcStr = arcstr::literal!("Electricity!"); 229 | assert!(ArcStr::is_static(&STATIC)); 230 | assert_eq!(ArcStr::as_static(&STATIC), Some("Electricity!")); 231 | 232 | assert!(ArcStr::is_static(&ArcStr::new())); 233 | assert_eq!(ArcStr::as_static(&ArcStr::new()), Some("")); 234 | let st = { 235 | // Note that they don't have to be consts, just made using `literal!`: 236 | let still_static = { arcstr::literal!("Shocking!") }; 237 | assert!(ArcStr::is_static(&still_static)); 238 | assert_eq!(ArcStr::as_static(&still_static), Some("Shocking!")); 239 | assert_eq!(ArcStr::as_static(&still_static.clone()), Some("Shocking!")); 240 | // clones are still static 241 | assert_eq!( 242 | ArcStr::as_static(&still_static.clone().clone()), 243 | Some("Shocking!") 244 | ); 245 | ArcStr::as_static(&still_static).unwrap() 246 | }; 247 | assert_eq!(st, "Shocking!"); 248 | 249 | // But it won't work for other strings. 250 | let nonstatic = ArcStr::from("Grounded..."); 251 | assert_eq!(ArcStr::as_static(&nonstatic), None); 252 | } 253 | 254 | #[test] 255 | fn test_static_arcstr_include_bytes() { 256 | const APACHE: ArcStr = arcstr::literal!(include_str!("../LICENSE-APACHE")); 257 | assert!(APACHE.len() > 10000); 258 | assert!(APACHE.trim_start().starts_with("Apache License")); 259 | assert!(APACHE 260 | .trim_end() 261 | .ends_with("limitations under the License.")); 262 | } 263 | 264 | #[test] 265 | fn test_inherent_overrides() { 266 | let s = ArcStr::from("abc"); 267 | assert_eq!(s.as_str(), "abc"); 268 | let a = ArcStr::from("foo"); 269 | assert_eq!(a.len(), 3); 270 | assert!(!ArcStr::from("foo").is_empty()); 271 | assert!(ArcStr::new().is_empty()); 272 | } 273 | 274 | #[test] 275 | fn test_froms_more() { 276 | let mut s = "asdf".to_string(); 277 | { 278 | let s2: &mut str = &mut s; 279 | // Make sure we go through the right From 280 | let arc = >::from(s2); 281 | assert_eq!(arc, "asdf"); 282 | } 283 | let arc = >::from(&s); 284 | assert_eq!(arc, "asdf"); 285 | 286 | // This is a slightly more natural way to check, as it's when the "you a 287 | // weird From" situation comes up more often. 288 | 289 | let b: Option> = Some("abc".into()); 290 | assert_eq!(b.map(ArcStr::from), Some(ArcStr::from("abc"))); 291 | 292 | let b: Option> = Some("abc".into()); 293 | assert_eq!(b.map(ArcStr::from), Some(ArcStr::from("abc"))); 294 | 295 | let b: Option> = Some("abc".into()); 296 | assert_eq!(b.map(ArcStr::from), Some(ArcStr::from("abc"))); 297 | 298 | let bs: Box = ArcStr::from("123").into(); 299 | assert_eq!(&bs[..], "123"); 300 | let rcs: std::rc::Rc = ArcStr::from("123").into(); 301 | assert_eq!(&rcs[..], "123"); 302 | let arcs: std::sync::Arc = ArcStr::from("123").into(); 303 | assert_eq!(&arcs[..], "123"); 304 | use std::borrow::Cow::{self, Borrowed, Owned}; 305 | let cow: Cow<'_, str> = Borrowed("abcd"); 306 | assert_eq!(ArcStr::from(cow), "abcd"); 307 | 308 | let cow: Cow<'_, str> = Owned("abcd".into()); 309 | assert_eq!(ArcStr::from(cow), "abcd"); 310 | 311 | let cow: Option> = Some(Cow::from(&arc)); 312 | assert_eq!(cow.as_deref(), Some("asdf")); 313 | 314 | let cow: Option> = Some(Cow::from(arc)); 315 | assert!(matches!(cow, Some(Cow::Owned(_)))); 316 | assert_eq!(cow.as_deref(), Some("asdf")); 317 | 318 | let st = { arcstr::literal!("static should borrow") }; 319 | { 320 | let cow: Option> = Some(Cow::from(st.clone())); 321 | assert!(matches!(cow, Some(Cow::Borrowed(_)))); 322 | assert_eq!(cow.as_deref(), Some("static should borrow")); 323 | } 324 | // works with any lifetime 325 | { 326 | let cow: Option> = Some(Cow::from(st.clone())); 327 | assert!(matches!(cow, Some(Cow::Borrowed(_)))); 328 | assert_eq!(cow.as_deref(), Some("static should borrow")); 329 | } 330 | 331 | let astr = ArcStr::from(&st); 332 | assert!(ArcStr::ptr_eq(&st, &astr)); 333 | // Check non-statics 334 | let astr2 = ArcStr::from("foobar"); 335 | assert!(ArcStr::ptr_eq(&astr2, &ArcStr::from(&astr2))) 336 | } 337 | 338 | #[test] 339 | fn try_allocate() { 340 | assert_eq!(ArcStr::try_alloc("foo").as_deref(), Some("foo")); 341 | // TODO: how to test the error cases here? 342 | } 343 | 344 | #[test] 345 | fn repeat_string() { 346 | assert_eq!(ArcStr::repeat("", 1000), ""); 347 | assert_eq!(ArcStr::repeat("AAA", 0), ""); 348 | assert_eq!(ArcStr::repeat("AAA", 1000), "AAA".repeat(1000)); 349 | assert_eq!(ArcStr::try_repeat("AAA", usize::MAX), None); 350 | } 351 | 352 | #[test] 353 | #[should_panic = "capacity overflow"] 354 | fn repeat_string_panics() { 355 | ArcStr::repeat("AAA", usize::MAX); 356 | } 357 | 358 | #[test] 359 | #[allow(unknown_lints)] 360 | #[cfg_attr(asan, ignore)] // Leaks memory intentionally 361 | fn test_leaking() { 362 | let s = ArcStr::from("foobar"); 363 | assert!(!ArcStr::is_static(&s)); 364 | assert!(ArcStr::as_static(&s).is_none()); 365 | 366 | let leaked: &'static str = s.leak(); 367 | assert_eq!(leaked, s); 368 | assert!(ArcStr::is_static(&s)); 369 | assert_eq!(ArcStr::as_static(&s), Some("foobar")); 370 | } 371 | -------------------------------------------------------------------------------- /tests/substr.rs: -------------------------------------------------------------------------------- 1 | #![allow( 2 | // need to test cloning, these are deliberate. 3 | clippy::redundant_clone, 4 | // yep, we create owned instance just for comparison, to test comparison 5 | // with owned instacnces. 6 | clippy::cmp_owned, 7 | // Deliberate 8 | clippy::redundant_slicing, 9 | )] 10 | #![cfg(feature = "substr")] 11 | use arcstr::{ArcStr, Substr}; 12 | 13 | #[test] 14 | fn double_substr() { 15 | let s: Substr = arcstr::literal!("foobarbaz").substr(3..); 16 | assert_eq!(s.as_str(), "barbaz"); 17 | assert_eq!(s.substr(1..5), "arba"); 18 | assert_eq!(s.substr(..5), "barba"); 19 | assert_eq!(s.substr(1..), "arbaz"); 20 | assert_eq!(s.substr(..), "barbaz"); 21 | assert_eq!(s.substr(1..=4), "arba"); 22 | assert_eq!(s.substr(..=4), "barba"); 23 | assert_eq!(s.substr(1..=5), "arbaz"); 24 | assert_eq!(s.substr(..=5), "barbaz"); 25 | assert_eq!( 26 | s.substr((core::ops::Bound::Excluded(1), core::ops::Bound::Unbounded)), 27 | "rbaz" 28 | ); 29 | } 30 | 31 | #[test] 32 | fn single_substr() { 33 | let s = ArcStr::from("barbaz"); 34 | assert_eq!(s.substr(1..5), "arba"); 35 | assert_eq!(s.substr(..5), "barba"); 36 | assert_eq!(s.substr(1..), "arbaz"); 37 | assert_eq!(s.substr(..), "barbaz"); 38 | assert_eq!(s.substr(1..=4), "arba"); 39 | assert_eq!(s.substr(..=4), "barba"); 40 | assert_eq!(s.substr(1..=5), "arbaz"); 41 | assert_eq!(s.substr(..=5), "barbaz"); 42 | assert_eq!( 43 | s.substr((core::ops::Bound::Excluded(1), core::ops::Bound::Unbounded)), 44 | "rbaz" 45 | ); 46 | } 47 | 48 | #[test] 49 | fn substr_index() { 50 | let s = ArcStr::from("_barbaz_").substr(1..7); 51 | assert_eq!(&s[1..5], "arba"); 52 | assert_eq!(&s[..5], "barba"); 53 | assert_eq!(&s[1..], "arbaz"); 54 | assert_eq!(&s[..], "barbaz"); 55 | assert_eq!(&s[1..=4], "arba"); 56 | assert_eq!(&s[..=4], "barba"); 57 | assert_eq!(&s[1..=5], "arbaz"); 58 | assert_eq!(&s[..=5], "barbaz"); 59 | } 60 | 61 | #[test] 62 | #[should_panic] 63 | fn substr_panic() { 64 | let s = ArcStr::from("abc"); 65 | let _v = &s[1..4]; 66 | } 67 | #[test] 68 | #[should_panic] 69 | fn substr_panic1() { 70 | let s = ArcStr::from("abc").substr(..2); 71 | let _v = &s.substr(1..3); 72 | } 73 | #[test] 74 | #[should_panic] 75 | fn substr_panic2() { 76 | let s = ArcStr::from("🙀"); 77 | let _v = &s.substr(1..); 78 | } 79 | #[test] 80 | #[should_panic] 81 | fn substr_panic3() { 82 | let s = ArcStr::from(" 🙀").substr(1..); 83 | let _v = &s.substr(1..); 84 | } 85 | #[test] 86 | #[should_panic] 87 | fn substr_panic4() { 88 | let s = ArcStr::from("abc").substr(..); 89 | let _v = &s.substr(1..4); 90 | } 91 | 92 | #[test] 93 | fn test_various_partial_eq() { 94 | macro_rules! check_partial_eq { 95 | (@eq1; $a:expr, $b:expr) => {{ 96 | // Note: intentionally not assert_eq. 97 | assert!($a == $b); 98 | assert!(!($a != $b)); 99 | assert!($b == $a); 100 | assert!(!($b != $a)); 101 | }}; 102 | (@ne1; $a:expr, $b:expr) => { 103 | assert!($a != $b); 104 | assert!(!($a == $b)); 105 | assert!($b != $a); 106 | assert!(!($b == $a)); 107 | }; 108 | (@eq; $a:expr, $b:expr) => {{ 109 | check_partial_eq!(@eq1; $a, $b); 110 | check_partial_eq!(@eq1; $a.clone(), $b); 111 | check_partial_eq!(@eq1; $a.clone(), $a); 112 | }}; 113 | (@ne; $a:expr, $b:expr) => {{ 114 | check_partial_eq!(@ne1; $a, $b); 115 | check_partial_eq!(@ne1; $a.clone(), $b); 116 | }}; 117 | } 118 | // really lazy to reuse Arcstr checks here.. 119 | 120 | // could just use Substr::from but lets make sure we have substrs that are 121 | // nontrivial 122 | fn substr(s: &str) -> Substr { 123 | ArcStr::from(format!("xx{}xx", s)).substr(2..s.len() + 2) 124 | } 125 | check_partial_eq!(@eq; substr("123"), "123"); 126 | check_partial_eq!(@eq; substr("foobar"), *"foobar"); 127 | check_partial_eq!(@eq; substr("🏳️‍🌈"), String::from("🏳️‍🌈")); 128 | check_partial_eq!(@eq; substr("🏳️‍⚧️"), std::borrow::Cow::Borrowed("🏳️‍⚧️")); 129 | check_partial_eq!(@eq; substr("🏴‍☠️"), std::borrow::Cow::Owned("🏴‍☠️".into())); 130 | check_partial_eq!(@eq; substr(":o"), std::rc::Rc::::from(":o")); 131 | check_partial_eq!(@eq; substr("!!!"), std::sync::Arc::::from("!!!")); 132 | check_partial_eq!(@eq; substr("examples"), ArcStr::from("examples")); 133 | 134 | check_partial_eq!(@eq; substr(""), ""); 135 | check_partial_eq!(@eq; substr(""), ArcStr::from("abc").substr(3..)); 136 | let twin = substr("1 2 3"); 137 | let twin2 = twin.clone(); 138 | check_partial_eq!(@eq; twin, twin2); 139 | 140 | check_partial_eq!(@ne; substr("123"), "124"); 141 | check_partial_eq!(@ne; substr("Foobar"), *"FoobarFoobar"); 142 | 143 | check_partial_eq!(@ne; substr("①"), String::from("1")); 144 | check_partial_eq!(@ne; substr(""), String::from("1")); 145 | check_partial_eq!(@ne; substr("abc"), String::from("")); 146 | 147 | check_partial_eq!(@ne; substr("butts"), std::borrow::Cow::Borrowed("boots")); 148 | check_partial_eq!(@ne; substr("bots"), std::borrow::Cow::Owned("🤖".into())); 149 | check_partial_eq!(@ne; substr("put"), std::rc::Rc::::from("⛳️")); 150 | check_partial_eq!(@ne; substr("pots"), std::sync::Arc::::from("🍲")); 151 | check_partial_eq!(@ne; substr("lots"), ArcStr::from("auctions")); 152 | } 153 | 154 | #[test] 155 | fn test_fmt() { 156 | assert_eq!(format!("{}", ArcStr::from("__test__").substr(2..6)), "test"); 157 | assert_eq!( 158 | format!("{:?}", ArcStr::from("__test__").substr(2..6)), 159 | "\"test\"" 160 | ); 161 | assert_eq!(arcstr::format!("{:?}", "__test__"), "\"__test__\""); 162 | assert_eq!(arcstr::format!("test2"), "test2"); 163 | } 164 | #[test] 165 | fn test_parts_shallow_eq() { 166 | let parent = ArcStr::from("12345"); 167 | let sub = parent.substr(1..); 168 | assert!(ArcStr::ptr_eq(&parent, sub.parent())); 169 | assert_eq!(sub.range(), 1..5); 170 | assert!(Substr::shallow_eq(&sub.clone(), &sub)); 171 | assert!(Substr::shallow_eq(&sub.clone(), &parent.substr(1..))); 172 | assert!(Substr::shallow_eq(&sub, &parent.clone().substr(1..))); 173 | assert!(Substr::shallow_eq(&sub, &parent.substr(1..5))); 174 | assert!(!Substr::shallow_eq( 175 | &sub, 176 | &ArcStr::from("12345").substr(1..) 177 | )); 178 | assert!(!Substr::shallow_eq(&sub, &parent.substr(1..3))); 179 | assert!(!Substr::shallow_eq(&sub, &parent.substr(2..))); 180 | assert!(!Substr::shallow_eq(&sub, &parent.substr(..5))); 181 | } 182 | 183 | #[test] 184 | fn test_ord() { 185 | let mut arr = [ 186 | ArcStr::from("_foo").substr(1..), 187 | ArcStr::from("AAAbar").substr(3..), 188 | ArcStr::from("zzzbaz").substr(3..), 189 | ]; 190 | arr.sort(); 191 | assert_eq!(&arr, &["bar", "baz", "foo"]); 192 | } 193 | 194 | #[test] 195 | fn test_btreemap() { 196 | let mut m = std::collections::BTreeMap::new(); 197 | 198 | for i in 0..100 { 199 | let prev = m.insert(ArcStr::from(format!("key {}", i)).substr(1..), i); 200 | assert_eq!(prev, None); 201 | } 202 | 203 | for i in 0..100 { 204 | let s = format!("ey {}", i); 205 | assert_eq!(m.remove(s.as_str()), Some(i)); 206 | } 207 | } 208 | #[test] 209 | fn test_hashmap() { 210 | let mut m = std::collections::HashMap::new(); 211 | for i in 0..100 { 212 | let prev = m.insert(ArcStr::from(format!("key {}", i)).substr(1..), i); 213 | assert_eq!(prev, None); 214 | } 215 | for i in 0..100 { 216 | let key = format!("ey {}", i); 217 | let search = key.as_str(); 218 | assert_eq!(m[search], i); 219 | assert_eq!(m.remove(search), Some(i)); 220 | } 221 | } 222 | 223 | #[cfg(feature = "serde")] 224 | #[test] 225 | fn test_serde() { 226 | use serde_test::{assert_de_tokens, assert_tokens, Token}; 227 | let teststr = ArcStr::from(" test test 123 456").substr(2..); 228 | assert_tokens(&teststr, &[Token::BorrowedStr("test test 123 456")]); 229 | assert_tokens(&teststr.clone(), &[Token::BorrowedStr("test test 123 456")]); 230 | assert_tokens(&ArcStr::default(), &[Token::BorrowedStr("")]); 231 | 232 | let checks = &[ 233 | [Token::Str("123")], 234 | [Token::BorrowedStr("123")], 235 | [Token::String("123")], 236 | [Token::Bytes(b"123")], 237 | [Token::BorrowedBytes(b"123")], 238 | [Token::ByteBuf(b"123")], 239 | ]; 240 | for check in checks { 241 | eprintln!("checking {:?}", check); 242 | assert_de_tokens(&Substr::from("123"), check); 243 | } 244 | } 245 | 246 | #[test] 247 | fn test_loose_ends() { 248 | assert_eq!(Substr::default(), ""); 249 | assert_eq!("abc".parse::().unwrap(), "abc"); 250 | let abc_sub = Substr::from(" abc ").substr(1..4); 251 | let abc_str: &str = abc_sub.as_ref(); 252 | let abc_bytes: &[u8] = abc_sub.as_ref(); 253 | assert_eq!(abc_str, "abc"); 254 | assert_eq!(abc_bytes, b"abc"); 255 | let full_src = ArcStr::from("123"); 256 | let sub = Substr::full(full_src.clone()); 257 | assert!(ArcStr::ptr_eq(&full_src, sub.parent())); 258 | assert_eq!(sub.range(), 0..3); 259 | let sub2 = Substr::from(&full_src); 260 | assert!(Substr::shallow_eq(&sub2, &sub)); 261 | } 262 | 263 | #[test] 264 | fn test_cow() { 265 | use std::borrow::Cow::{self, Borrowed, Owned}; 266 | let cow: Cow<'_, str> = Borrowed("abcd"); 267 | assert_eq!(Substr::from(cow), "abcd"); 268 | 269 | let cow: Cow<'_, str> = Owned("abcd".into()); 270 | assert_eq!(Substr::from(cow), "abcd"); 271 | let sub = ArcStr::from("XXasdfYY").substr(2..6); 272 | let cow: Option> = Some(Cow::from(&sub)); 273 | assert_eq!(cow.as_deref(), Some("asdf")); 274 | 275 | let cow: Option> = Some(Cow::from(sub)); 276 | assert!(matches!(cow, Some(Cow::Owned(_)))); 277 | assert_eq!(cow.as_deref(), Some("asdf")); 278 | 279 | let st = { arcstr::literal!("_static should borrow_") }; 280 | let ss = st.substr(1..st.len() - 1); 281 | { 282 | let cow: Option> = Some(Cow::from(ss.clone())); 283 | assert!(matches!(cow, Some(Cow::Borrowed(_)))); 284 | assert_eq!(cow.as_deref(), Some("static should borrow")); 285 | } 286 | // works with any lifetime 287 | { 288 | let cow: Option> = Some(Cow::from(ss.clone())); 289 | assert!(matches!(cow, Some(Cow::Borrowed(_)))); 290 | assert_eq!(cow.as_deref(), Some("static should borrow")); 291 | } 292 | } 293 | 294 | #[test] 295 | fn test_inherent_overrides() { 296 | let s = ArcStr::from(" abc ").substr(2..5); 297 | assert_eq!(s.as_str(), "abc"); 298 | // let a = ArcStr::from("foo"); 299 | assert_eq!(s.len(), 3); 300 | assert!(!s.is_empty()); 301 | assert!(s.substr(3..).is_empty()); 302 | assert_eq!(s.to_string(), "abc"); 303 | } 304 | 305 | #[test] 306 | fn test_substr_from() { 307 | let a = ArcStr::from(" abcdefg "); 308 | let ss = a.substr_from(&a.as_str()[2..]); 309 | assert_eq!(ss, "abcdefg "); 310 | assert!(Substr::shallow_eq(&ss, &a.substr(2..))); 311 | 312 | let ss = a.substr_from(&a.as_str()[..9]); 313 | assert_eq!(ss, " abcdefg"); 314 | assert!(Substr::shallow_eq(&ss, &a.substr(..9))); 315 | 316 | let ss = a.substr_from(a.trim()); 317 | assert_eq!(ss, "abcdefg"); 318 | assert!(Substr::shallow_eq(&ss, &a.substr(2..9))); 319 | } 320 | 321 | #[test] 322 | fn test_try_substr_from_using() { 323 | let orig = arcstr::literal!(" bcdef "); 324 | let a = orig.substr(1..10); 325 | let ss = a.try_substr_from(&a.as_str()[1..8]).unwrap(); 326 | assert_eq!(ss, " bcdef "); 327 | assert!(Substr::shallow_eq(&ss, &orig.substr(2..9))); 328 | let ss2 = orig.try_substr_using(str::trim); 329 | assert_eq!(ss2.unwrap(), "bcdef"); 330 | 331 | let nil = orig.try_substr_using(|s| s.get(5..100).unwrap_or("")); 332 | assert_eq!(nil.unwrap(), ""); 333 | let nil = a.try_substr_using(|s| s.get(5..100).unwrap_or("")); 334 | assert_eq!(nil.unwrap(), ""); 335 | // lifetimes make it pretty hard to misuse this — I do wonder if generative 336 | // lifetimes would make it even harder... But for now, we keep the checks. 337 | let outside = a.try_substr_using(|_| &ArcStr::as_static(&orig).unwrap()[..]); 338 | assert_eq!(outside, None); 339 | let outside_l = a.try_substr_using(|_| &ArcStr::as_static(&orig).unwrap()[1..]); 340 | assert_eq!(outside_l, None); 341 | let outside_r = a.try_substr_using(|_| &ArcStr::as_static(&orig).unwrap()[..10]); 342 | assert_eq!(outside_r, None); 343 | let outside_lr = a.try_substr_using(|_| &ArcStr::as_static(&orig).unwrap()[1..10]); 344 | assert_eq!(outside_lr.as_deref(), Some(" bcdef ")); 345 | } 346 | #[test] 347 | #[should_panic] 348 | fn test_substr_using_panic0() { 349 | let orig = arcstr::literal!(" bcdef "); 350 | let a = orig.substr(1..10); 351 | let _s = a.substr_using(|_| &ArcStr::as_static(&orig).unwrap()[..]); 352 | } 353 | #[test] 354 | #[should_panic] 355 | fn test_substr_using_panic1() { 356 | let orig = arcstr::literal!(" bcdef "); 357 | let a = orig.substr(1..10); 358 | let _s = a.substr_using(|_| &ArcStr::as_static(&orig).unwrap()[1..]); 359 | } 360 | 361 | #[test] 362 | #[should_panic] 363 | fn test_substr_using_panic2() { 364 | let orig = arcstr::literal!(" bcdef "); 365 | let a = orig.substr(1..10); 366 | let _s = a.substr_using(|_| &ArcStr::as_static(&orig).unwrap()[..10]); 367 | } 368 | 369 | #[test] 370 | fn test_substr_from_using() { 371 | let orig = ArcStr::from(" bcdef "); 372 | let a = orig.substr(1..10); 373 | let ss = a.substr_from(&a.as_str()[1..8]); 374 | assert_eq!(ss, " bcdef "); 375 | assert!(Substr::shallow_eq(&ss, &orig.substr(2..9))); 376 | let ss2 = orig.substr_using(str::trim); 377 | assert_eq!(ss2, "bcdef"); 378 | 379 | let nil = orig.substr_using(|s| s.get(5..100).unwrap_or("")); 380 | assert_eq!(nil, ""); 381 | let nil = a.substr_using(|s| s.get(5..100).unwrap_or("")); 382 | assert_eq!(nil, ""); 383 | } 384 | 385 | #[test] 386 | #[should_panic] 387 | fn test_substr_from_panic() { 388 | let a = ArcStr::from(" abcdefg "); 389 | let _s = a.substr_from("abcdefg"); 390 | } 391 | 392 | #[test] 393 | #[should_panic] 394 | fn test_substr_using_arc_panic() { 395 | let a = ArcStr::from(" abcdefg "); 396 | let _s = a.substr_using(|_| "abcdefg"); 397 | } 398 | 399 | #[test] 400 | fn test_try_substr_from() { 401 | let a = ArcStr::from(" abcdefg "); 402 | assert!(a.try_substr_from("abcdefg").is_none()); 403 | let ss = a.try_substr_from(&a.as_str()[2..]); 404 | assert_eq!(ss.as_deref(), Some("abcdefg ")); 405 | assert!(Substr::shallow_eq(&ss.unwrap(), &a.substr(2..))); 406 | 407 | let ss = a.try_substr_from(&a.as_str()[..9]); 408 | assert_eq!(ss.as_deref(), Some(" abcdefg")); 409 | assert!(Substr::shallow_eq(&ss.unwrap(), &a.substr(..9))); 410 | 411 | let ss = a.try_substr_from(a.trim()); 412 | assert_eq!(ss.as_deref(), Some("abcdefg")); 413 | assert!(Substr::shallow_eq(&ss.unwrap(), &a.substr(2..9))); 414 | } 415 | 416 | #[test] 417 | fn test_try_substr_from_substr() { 418 | let subs = arcstr::literal_substr!(" abcdefg "); 419 | assert!(subs.try_substr_from("abcdefg").is_none()); 420 | let ss = subs.try_substr_from(&subs.as_str()[2..]); 421 | assert_eq!(ss.as_deref(), Some("abcdefg ")); 422 | assert!(Substr::shallow_eq(&ss.unwrap(), &subs.substr(2..))); 423 | 424 | let ss = subs.try_substr_from(&subs.as_str()[..9]); 425 | assert_eq!(ss.as_deref(), Some(" abcdefg")); 426 | assert!(Substr::shallow_eq(&ss.unwrap(), &subs.substr(..9))); 427 | 428 | let ss = subs.try_substr_from(subs.trim()); 429 | assert_eq!(ss.as_deref(), Some("abcdefg")); 430 | assert!(Substr::shallow_eq(&ss.unwrap(), &subs.substr(2..9))); 431 | } 432 | --------------------------------------------------------------------------------