ecow-0.2.6/.cargo_vcs_info.json0000644000000001360000000000100117760ustar { "git": { "sha1": "72875f7d1ee363c1d68c9717b4d6ca303e5119f9" }, "path_in_vcs": "" }ecow-0.2.6/.github/.well-known/funding-manifest-urls000064400000000000000000000000371046102023000204450ustar 00000000000000https://typst.app/funding.json ecow-0.2.6/.github/FUNDING.yml000064400000000000000000000000201046102023000137330ustar 00000000000000github: [typst] ecow-0.2.6/.github/workflows/ci.yml000064400000000000000000000031321046102023000153000ustar 00000000000000name: CI on: push: pull_request: schedule: # Run weekly to keep Rust toolchain changes fresh - cron: '0 0 * * 1' jobs: multiple_toolchains: name: Stable and Beta tasks runs-on: ubuntu-latest strategy: matrix: rust: - 1.73 - beta steps: - uses: actions/checkout@v3 - name: Install ${{ matrix.rust }} toolchain uses: dtolnay/rust-toolchain@master with: toolchain: ${{ matrix.rust }} components: rustfmt - name: Check formatting run: cargo fmt --all --check - name: Check check env: RUSTFLAGS: -D warnings run: cargo check - name: Run test suite run: cargo test - name: Check docs env: RUSTDOCFLAGS: -D warnings run: cargo doc --no-deps --document-private-items - name: Run loom tests env: RUSTFLAGS: --cfg loom run: cargo test --release loom --features loom # We use a healthy amount of unsafe, so run tests with Miri to check for UB nightly_only: name: Nightly tasks runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Install nightly toolchain uses: dtolnay/rust-toolchain@master with: toolchain: nightly components: miri - name: Miri 64-bit LE run: cargo miri test --target x86_64-unknown-linux-gnu - name: Miri 64-bit BE run: cargo miri test --target sparc64-unknown-linux-gnu - name: Miri 32-bit LE run: cargo miri test --target i686-unknown-linux-gnu ecow-0.2.6/.github/workflows/coverage.yml000064400000000000000000000010651046102023000165030ustar 00000000000000name: Coverage on: [pull_request, push] jobs: coverage: runs-on: ubuntu-latest env: CARGO_TERM_COLOR: always steps: - uses: actions/checkout@v3 - name: Install stable toolchain uses: dtolnay/rust-toolchain@master with: toolchain: stable - name: Install cargo-llvm-cov uses: taiki-e/install-action@cargo-llvm-cov - name: Generate code coverage run: cargo llvm-cov --lcov --output-path lcov.info - name: Upload coverage to Codecov uses: codecov/codecov-action@v3 ecow-0.2.6/.gitignore000064400000000000000000000000361046102023000125550ustar 00000000000000/target /Cargo.lock .DS_Store ecow-0.2.6/Cargo.lock0000644000000232770000000000100077640ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 3 [[package]] name = "cc" version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "ecow" version = "0.2.6" dependencies = [ "loom", "serde", ] [[package]] name = "generator" version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33a20a288a94683f5f4da0adecdbe095c94a77c295e514cc6484e9394dd8376e" dependencies = [ "cc", "libc", "log", "rustversion", "windows", ] [[package]] name = "lazy_static" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" version = "0.2.154" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae743338b92ff9146ce83992f766a31066a91a8c84a45e0e9f21e7cf6de6d346" [[package]] name = "log" version = "0.4.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" dependencies = [ "cfg-if", ] [[package]] name = "loom" version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7e045d70ddfbc984eacfa964ded019534e8f6cbf36f6410aee0ed5cefa5a9175" dependencies = [ "cfg-if", "generator", "scoped-tls", "tracing", "tracing-subscriber", ] [[package]] name = "matchers" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" dependencies = [ "regex-automata", ] [[package]] name = "nu-ansi-term" version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" dependencies = [ "overload", "winapi", ] [[package]] name = "once_cell" version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] name = "overload" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" [[package]] name = "pin-project-lite" version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" [[package]] name = "proc-macro2" version = "1.0.81" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d1597b0c024618f09a9c3b8655b7e430397a36d23fdafec26d6965e9eec3eba" dependencies = [ "unicode-ident", ] [[package]] name = "quote" version = "1.0.36" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" dependencies = [ "proc-macro2", ] [[package]] name = "regex" version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48aaa5748ba571fb95cd2c85c09f629215d3a6ece942baa100950af03a34f733" dependencies = [ "regex-syntax", ] [[package]] name = "regex-automata" version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" dependencies = [ "regex-syntax", ] [[package]] name = "regex-syntax" version = "0.6.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" [[package]] name = "rustversion" version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5583e89e108996506031660fe09baa5011b9dd0341b89029313006d1fb508d70" [[package]] name = "scoped-tls" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" [[package]] name = "serde" version = "1.0.160" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bb2f3770c8bce3bcda7e149193a069a0f4365bda1fa5cd88e03bca26afc1216c" [[package]] name = "sharded-slab" version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31" dependencies = [ "lazy_static", ] [[package]] name = "smallvec" version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" [[package]] name = "syn" version = "1.0.109" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "thread_local" version = "1.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152" dependencies = [ "cfg-if", "once_cell", ] [[package]] name = "tracing" version = "0.1.37" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" dependencies = [ "cfg-if", "pin-project-lite", "tracing-attributes", "tracing-core", ] [[package]] name = "tracing-attributes" version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4017f8f45139870ca7e672686113917c71c7a6e02d4924eda67186083c03081a" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "tracing-core" version = "0.1.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a" dependencies = [ "once_cell", "valuable", ] [[package]] name = "tracing-log" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ddad33d2d10b1ed7eb9d1f518a5674713876e97e5bb9b7345a7984fbb4f922" dependencies = [ "lazy_static", "log", "tracing-core", ] [[package]] name = "tracing-subscriber" version = "0.3.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a6176eae26dd70d0c919749377897b54a9276bd7061339665dd68777926b5a70" dependencies = [ "matchers", "nu-ansi-term", "once_cell", "regex", "sharded-slab", "smallvec", "thread_local", "tracing", "tracing-core", "tracing-log", ] [[package]] name = "unicode-ident" version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc" [[package]] name = "valuable" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" [[package]] name = "winapi" version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" dependencies = [ "winapi-i686-pc-windows-gnu", "winapi-x86_64-pc-windows-gnu", ] [[package]] name = "winapi-i686-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows" version = "0.44.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e745dab35a0c4c77aa3ce42d595e13d2003d6902d6b08c9ef5fc326d08da12b" dependencies = [ "windows-targets", ] [[package]] name = "windows-targets" version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e2522491fbfcd58cc84d47aeb2958948c4b8982e9a2d8a2a35bbaed431390e7" dependencies = [ "windows_aarch64_gnullvm", "windows_aarch64_msvc", "windows_i686_gnu", "windows_i686_msvc", "windows_x86_64_gnu", "windows_x86_64_gnullvm", "windows_x86_64_msvc", ] [[package]] name = "windows_aarch64_gnullvm" version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c9864e83243fdec7fc9c5444389dcbbfd258f745e7853198f365e3c4968a608" [[package]] name = "windows_aarch64_msvc" version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c8b1b673ffc16c47a9ff48570a9d85e25d265735c503681332589af6253c6c7" [[package]] name = "windows_i686_gnu" version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "de3887528ad530ba7bdbb1faa8275ec7a1155a45ffa57c37993960277145d640" [[package]] name = "windows_i686_msvc" version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf4d1122317eddd6ff351aa852118a2418ad4214e6613a50e0191f7004372605" [[package]] name = "windows_x86_64_gnu" version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1040f221285e17ebccbc2591ffdc2d44ee1f9186324dd3e84e99ac68d699c45" [[package]] name = "windows_x86_64_gnullvm" version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "628bfdf232daa22b0d64fdb62b09fcc36bb01f05a3939e20ab73aaf9470d0463" [[package]] name = "windows_x86_64_msvc" version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd" ecow-0.2.6/Cargo.toml0000644000000025150000000000100077770ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2021" rust-version = "1.73" name = "ecow" version = "0.2.6" authors = ["Laurenz "] build = false autolib = false autobins = false autoexamples = false autotests = false autobenches = false description = "Compact, clone-on-write vector and string." readme = "README.md" keywords = [ "string", "vector", "sso", "cow", ] categories = [ "data-structures", "no-std", ] license = "MIT OR Apache-2.0" repository = "https://github.com/typst/ecow" [features] default = ["std"] std = [] [lib] name = "ecow" path = "src/lib.rs" [[test]] name = "loom" path = "tests/loom.rs" [[test]] name = "tests" path = "tests/tests.rs" [[test]] name = "ub_guards" path = "tests/ub_guards.rs" [dependencies.serde] version = "1.0" optional = true default-features = false [target."cfg(loom)".dependencies.loom] version = "0.7" optional = true ecow-0.2.6/Cargo.toml.orig0000644000000011270000000000100107340ustar [package] name = "ecow" version = "0.2.6" rust-version = "1.73" # also change in ci.yml authors = ["Laurenz "] edition = "2021" description = "Compact, clone-on-write vector and string." repository = "https://github.com/typst/ecow" readme = "README.md" license = "MIT OR Apache-2.0" categories = ["data-structures", "no-std"] keywords = ["string", "vector", "sso", "cow"] [features] default = ["std"] std = [] [dependencies] serde = { version = "1.0", optional = true, default-features = false } [target.'cfg(loom)'.dependencies] loom = { version = "0.7", optional = true } ecow-0.2.6/Cargo.toml.orig000064400000000000000000000011271046102023000134560ustar 00000000000000[package] name = "ecow" version = "0.2.6" rust-version = "1.73" # also change in ci.yml authors = ["Laurenz "] edition = "2021" description = "Compact, clone-on-write vector and string." repository = "https://github.com/typst/ecow" readme = "README.md" license = "MIT OR Apache-2.0" categories = ["data-structures", "no-std"] keywords = ["string", "vector", "sso", "cow"] [features] default = ["std"] std = [] [dependencies] serde = { version = "1.0", optional = true, default-features = false } [target.'cfg(loom)'.dependencies] loom = { version = "0.7", optional = true } ecow-0.2.6/LICENSE-APACHE000064400000000000000000000227731046102023000125250ustar 00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS ecow-0.2.6/LICENSE-MIT000064400000000000000000000020141046102023000122170ustar 00000000000000MIT License Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ecow-0.2.6/README.md000064400000000000000000000047521046102023000120550ustar 00000000000000# ecow [![Crates.io](https://img.shields.io/crates/v/ecow.svg)](https://crates.io/crates/ecow) [![Documentation](https://docs.rs/ecow/badge.svg)](https://docs.rs/ecow) Compact, clone-on-write vector and string. ## Types - An `EcoVec` is a reference-counted clone-on-write vector. It takes up two words of space (= 2 usize) and has the same memory layout as a `&[T]` slice. Within its allocation, it stores a reference count, its capacity and its elements. - An `EcoString` is a reference-counted clone-on-write string with inline storage. It takes up 16 bytes of space. It has 15 bytes of inline storage and starting from 16 bytes it becomes an `EcoVec`. ## Example ```rust // This is stored inline. let small = ecow::EcoString::from("Welcome"); // This spills to the heap, but only once: `big` and `third` share the // same underlying allocation. Vectors and spilled strings are only // really cloned upon mutation. let big = small + " to earth! 🌱"; let mut third = big.clone(); // This allocates again to mutate `third` without affecting `big`. assert_eq!(third.pop(), Some('🌱')); assert_eq!(third, "Welcome to earth! "); ``` ## Why should I use this instead of ... | Type | Details | |:--------------------------------------------|:--------| | [`Vec`][vec] / [`String`][string] | Normal vectors are a great general purpose data structure. But they have a quite big footprint (3 machine words) and are expensive to clone. The `EcoVec` has a bit of overhead for mutation, but is cheap to clone and only takes two words. | | [`Arc>`][arc] / [`Arc`][arc] | These require two allocations instead of one and are less convenient to mutate. | | [`Arc<[T]>`][arc] / [`Arc`][arc] | While these require only one allocation, they aren't mutable. | | Small vector | Different trade-off. Great when there are few, small `T`s, but expensive to clone when spilled to the heap. | | Small string | The `EcoString` combines different small string qualities into a very practical package: It has inline storage, a smaller footprint than a normal `String`, is efficient to clone even when spilled, and at the same time mutable. | [vec]: https://doc.rust-lang.org/std/vec/struct.Vec.html [string]: https://doc.rust-lang.org/std/string/struct.String.html [arc]: https://doc.rust-lang.org/std/sync/struct.Arc.html ## License This crate is dual-licensed under the MIT and Apache 2.0 licenses. ecow-0.2.6/rustfmt.toml000064400000000000000000000002111046102023000131610ustar 00000000000000use_small_heuristics = "Max" max_width = 90 chain_width = 70 struct_lit_width = 50 use_field_init_shorthand = true merge_derives = false ecow-0.2.6/src/dynamic.rs000064400000000000000000000252221046102023000133520ustar 00000000000000use core::mem::{self, ManuallyDrop}; use core::ptr; use super::EcoVec; /// A byte vector that can hold up to 15 bytes inline and then spills to an /// `EcoVec`. pub(crate) struct DynamicVec(Repr); /// The internal representation. /// /// On 64-bit little endian, this assumes that no valid EcoVec exists in which /// the highest-order bit of the InlineVec's `tagged_len` would be set. This is /// true because EcoVec is repr(C) and its second field `len` is bounded by /// `isize::MAX`. On 32-bit, it's no problem and for 64-bit big endian, we /// have an increased limit to prevent the overlap. #[repr(C)] union Repr { inline: InlineVec, spilled: ManuallyDrop>, } /// This is never stored in memory, it's just an abstraction for safe access. #[derive(Debug)] enum Variant<'a> { Inline(&'a InlineVec), Spilled(&'a EcoVec), } /// This is never stored in memory, it's just an abstraction for safe access. #[derive(Debug)] enum VariantMut<'a> { Inline(&'a mut InlineVec), Spilled(&'a mut EcoVec), } /// The maximum amount of inline storage. Typically, this is 15 bytes. /// /// However, in the rare exotic system, we still want things to be safe. /// Therefore, the following special cases: /// - For big endian, we increase the limit such that the tagged length of the /// inline variants doesn't overlap with the EcoVec. For little endian, it's /// fine since the highest order bit is never set for a valid EcoVec. /// - In case somehow EcoVec is very big (128-bit pointers woah), increase the /// limit too. pub(crate) const LIMIT: usize = { let mut limit = 15; if limit < mem::size_of::>() - 1 { limit = mem::size_of::>() - 1; } if cfg!(target_endian = "big") { limit += mem::size_of::(); } limit }; /// This bit is used to check whether we are inline or not. On 64-bit little /// endian, it coincides with the highest-order bit of an EcoVec's length, which /// can't be set because the EcoVec's length never exceeds `isize::MAX`. const LEN_TAG: u8 = 0b1000_0000; /// This is used to mask off the tag to get the inline variant's length. const LEN_MASK: u8 = 0b0111_1111; impl DynamicVec { #[inline] pub const fn new() -> Self { Self::from_inline(InlineVec::new()) } #[inline] pub const fn from_inline(inline: InlineVec) -> Self { Self(Repr { inline }) } #[inline] pub const fn from_eco(vec: EcoVec) -> Self { // Safety: // Explicitly set `tagged_len` to 0 to mark this as a spilled variant. // Just initializing with `Repr { spilled: ... }` would leave // `tagged_len` uninitialized, leading to undefined behaviour on access. let mut repr = Repr { inline: InlineVec { buf: [0; LIMIT], tagged_len: 0 }, }; repr.spilled = ManuallyDrop::new(vec); Self(repr) } #[inline] pub fn from_slice(bytes: &[u8]) -> Self { match InlineVec::from_slice(bytes) { Ok(inline) => Self::from_inline(inline), _ => Self::from_eco(EcoVec::from(bytes)), } } #[inline] pub fn with_capacity(capacity: usize) -> Self { if capacity <= LIMIT { Self::new() } else { Self::from_eco(EcoVec::with_capacity(capacity)) } } #[inline] pub fn len(&self) -> usize { match self.variant() { Variant::Inline(inline) => inline.len(), Variant::Spilled(spilled) => spilled.len(), } } #[inline] pub fn as_slice(&self) -> &[u8] { match self.variant() { Variant::Inline(inline) => inline.as_slice(), Variant::Spilled(spilled) => spilled.as_slice(), } } #[inline] pub fn make_mut(&mut self) -> &mut [u8] { match self.variant_mut() { VariantMut::Inline(inline) => inline.as_mut_slice(), VariantMut::Spilled(spilled) => spilled.make_mut(), } } #[inline] pub fn push(&mut self, byte: u8) { match self.variant_mut() { VariantMut::Inline(inline) => { if inline.push(byte).is_err() { let mut eco = EcoVec::with_capacity(LIMIT * 2); eco.extend_from_byte_slice(self.as_slice()); eco.push(byte); *self = Self::from_eco(eco); } } VariantMut::Spilled(spilled) => { spilled.push(byte); } } } #[inline] pub fn extend_from_slice(&mut self, bytes: &[u8]) { match self.variant_mut() { VariantMut::Inline(inline) => { if inline.extend_from_slice(bytes).is_err() { let needed = inline.len() + bytes.len(); let mut eco = EcoVec::with_capacity(needed.next_power_of_two()); eco.extend_from_byte_slice(self.as_slice()); eco.extend_from_byte_slice(bytes); *self = Self::from_eco(eco); } } VariantMut::Spilled(spilled) => { spilled.extend_from_byte_slice(bytes); } } } #[inline] pub fn clear(&mut self) { match self.variant_mut() { VariantMut::Inline(inline) => inline.clear(), VariantMut::Spilled(spilled) => spilled.clear(), } } #[inline] pub fn truncate(&mut self, target: usize) { match self.variant_mut() { VariantMut::Inline(inline) => inline.truncate(target), VariantMut::Spilled(spilled) => spilled.truncate(target), } } } impl DynamicVec { // If this returns true, guarantees that `self.0.inline` is initialized. // Otherwise, guarantees that `self.0.spilled` is initialized. #[inline] fn is_inline(&self) -> bool { // Safety: // We always initialize tagged_len, even for the `EcoVec` variant. For // the inline variant the highest-order bit is always `1`. For the // spilled variant, it is initialized with `0` and cannot deviate from // that because the EcoVec's `len` field is bounded by `isize::MAX`. (At // least on 64-bit little endian; on 32-bit or big-endian the EcoVec // and tagged_len fields don't even overlap, meaning tagged_len stays at // its initial value.) unsafe { self.0.inline.tagged_len & LEN_TAG != 0 } } #[inline] fn variant(&self) -> Variant<'_> { unsafe { // Safety: // We access the respective variant only if the check passes. if self.is_inline() { Variant::Inline(&self.0.inline) } else { Variant::Spilled(&self.0.spilled) } } } #[inline] fn variant_mut(&mut self) -> VariantMut<'_> { unsafe { // Safety: // We access the respective variant only if the check passes. if self.is_inline() { VariantMut::Inline(&mut self.0.inline) } else { VariantMut::Spilled(&mut self.0.spilled) } } } } impl Clone for DynamicVec { #[inline] fn clone(&self) -> Self { match self.variant() { Variant::Inline(inline) => Self::from_inline(*inline), Variant::Spilled(spilled) => Self::from_eco(spilled.clone()), } } } impl Drop for DynamicVec { #[inline] fn drop(&mut self) { if let VariantMut::Spilled(spilled) = self.variant_mut() { unsafe { // Safety: We are guaranteed to have a valid `EcoVec`. ptr::drop_in_place(spilled); } } } } #[repr(C)] #[derive(Debug, Copy, Clone)] pub(crate) struct InlineVec { /// Storage! buf: [u8; LIMIT], /// Invariant: After masking off the tag, never exceeds LIMIT. tagged_len: u8, } impl InlineVec { #[inline] pub const fn new() -> Self { // Safety: Trivially, 0 <= LIMIT unsafe { Self::from_buf([0; LIMIT], 0) } } #[inline] pub const fn from_slice(bytes: &[u8]) -> Result { let len = bytes.len(); if len > LIMIT { return Err(()); } let mut buf = [0; LIMIT]; let mut i = 0; while i < len { buf[i] = bytes[i]; i += 1; } // Safety: If len > LIMIT, Err was returned earlier. unsafe { Ok(Self::from_buf(buf, len)) } } /// The given length may not exceed LIMIT. #[inline] pub const unsafe fn from_buf(buf: [u8; LIMIT], len: usize) -> Self { debug_assert!(len <= LIMIT); Self { buf, tagged_len: len as u8 | LEN_TAG } } #[inline] pub fn len(&self) -> usize { usize::from(self.tagged_len & LEN_MASK) } /// The given length may not exceed LIMIT. #[inline] unsafe fn set_len(&mut self, len: usize) { debug_assert!(len <= LIMIT); self.tagged_len = len as u8 | LEN_TAG; } #[inline] pub fn as_slice(&self) -> &[u8] { // Safety: We have the invariant `len <= LIMIT`. unsafe { self.buf.get_unchecked(..self.len()) } } #[inline] pub fn as_mut_slice(&mut self) -> &mut [u8] { // Safety: We have the invariant `len <= LIMIT`. let len = self.len(); unsafe { self.buf.get_unchecked_mut(..len) } } #[inline] pub fn clear(&mut self) { unsafe { // Safety: Trivially, `0 <= LIMIT`. self.set_len(0); } } #[inline] pub fn push(&mut self, byte: u8) -> Result<(), ()> { let len = self.len(); if let Some(slot) = self.buf.get_mut(len) { *slot = byte; unsafe { // Safety: The `get_mut` call guarantees that `len < LIMIT`. self.set_len(len + 1); } Ok(()) } else { Err(()) } } #[inline] pub fn extend_from_slice(&mut self, bytes: &[u8]) -> Result<(), ()> { let len = self.len(); let grown = len + bytes.len(); if let Some(segment) = self.buf.get_mut(len..grown) { segment.copy_from_slice(bytes); unsafe { // Safety: The `get_mut` call guarantees that `grown <= LIMIT`. self.set_len(grown); } Ok(()) } else { Err(()) } } #[inline] pub fn truncate(&mut self, target: usize) { if target < self.len() { unsafe { // Safety: Checked that it's smaller than the current length, // which cannot exceed LIMIT itself. self.set_len(target); } } } } ecow-0.2.6/src/lib.rs000064400000000000000000000053431046102023000124760ustar 00000000000000/*! Compact, clone-on-write vector and string. ## Types - An [`EcoVec`] is a reference-counted clone-on-write vector. It takes up two words of space (= 2 usize) and has the same memory layout as a `&[T]` slice. Within its allocation, it stores a reference count, its capacity and its elements. - An [`EcoString`] is a reference-counted clone-on-write string with inline storage. It takes up 16 bytes of space. It has 15 bytes of inline storage and starting from 16 bytes it becomes an [`EcoVec`]. ## Example ``` // This is stored inline. let small = ecow::EcoString::from("Welcome"); // This spills to the heap, but only once: `big` and `third` share the // same underlying allocation. Vectors and spilled strings are only // really cloned upon mutation. let big = small + " to earth! 🌱"; let mut third = big.clone(); // This allocates again to mutate `third` without affecting `big`. assert_eq!(third.pop(), Some('🌱')); assert_eq!(third, "Welcome to earth! "); ``` ## Why should I use this instead of ... | Type | Details | |:----------------------------------|:--------| | [`Vec`]/ [`String`] | Normal vectors are a great general purpose data structure. But they have a quite big footprint (3 machine words) and are expensive to clone. The [`EcoVec`] has a bit of overhead for mutation, but is cheap to clone and only takes two words. | | [`Arc>`] / [`Arc`] | These require two allocations instead of one and are less convenient to mutate. | | [`Arc<[T]>`] / [`Arc`] | While these require only one allocation, they aren't mutable. | | Small vector | Different trade-off. Great when there are few, small `T`s, but expensive to clone when spilled to the heap. | | Small string | The [`EcoString`] combines different small string qualities into a very practical package: It has inline storage, a smaller footprint than a normal [`String`][string], is efficient to clone even when spilled, and at the same time mutable. | */ #![cfg_attr(not(feature = "std"), no_std)] #![deny(missing_docs)] // See https://github.com/tokio-rs/loom/issues/352 #![allow(unknown_lints, unexpected_cfgs)] extern crate alloc; pub mod string; pub mod vec; mod dynamic; pub use self::string::EcoString; pub use self::vec::EcoVec; #[cfg(doc)] use alloc::{string::String, sync::Arc, vec::Vec}; // Run doctests on the README too #[doc = include_str!("../README.md")] #[cfg(doctest)] pub struct ReadmeDoctests; /// Loom needs its own synchronization types to be used in order to work mod sync { /// Atomics stub pub mod atomic { #[cfg(not(loom))] pub use core::sync::atomic::*; #[cfg(loom)] pub use loom::sync::atomic::*; } } ecow-0.2.6/src/string.rs000064400000000000000000000353051046102023000132370ustar 00000000000000//! A clone-on-write, small-string-optimized alternative to [`String`]. use alloc::borrow::Cow; use core::borrow::Borrow; use core::cmp::Ordering; use core::fmt::{self, Debug, Display, Formatter, Write}; use core::hash::{Hash, Hasher}; use core::ops::{Add, AddAssign, Deref}; use core::str::FromStr; #[cfg(not(feature = "std"))] use alloc::string::String; use crate::dynamic::{DynamicVec, InlineVec}; /// Create a new [`EcoString`] from a format string. /// ``` /// # use ecow::eco_format; /// assert_eq!(eco_format!("Hello, {}!", 123), "Hello, 123!"); /// ``` #[macro_export] macro_rules! eco_format { ($($tts:tt)*) => {{ use ::std::fmt::Write; let mut s = $crate::EcoString::new(); ::std::write!(s, $($tts)*).unwrap(); s }}; } /// An economical string with inline storage and clone-on-write semantics. /// /// This type has a size of 16 bytes. It has 15 bytes of inline storage and /// starting from 16 bytes it becomes an [`EcoVec`](super::EcoVec). The /// internal reference counter of the heap variant is atomic, making this type /// [`Sync`] and [`Send`]. /// /// # Example /// ``` /// use ecow::EcoString; /// /// // This is stored inline. /// let small = EcoString::from("Welcome"); /// /// // This spills to the heap only once: `big` and `third` share the same /// // underlying allocation. Just like vectors, heap strings are only really /// // cloned upon mutation. /// let big = small + " to earth! 🌱"; /// let mut third = big.clone(); /// assert_eq!(big, "Welcome to earth! 🌱"); /// assert_eq!(third, big); /// /// // This allocates again to mutate `third` without affecting `big`. /// assert_eq!(third.pop(), Some('🌱')); /// assert_eq!(third, "Welcome to earth! "); /// assert_eq!(big, "Welcome to earth! 🌱"); /// ``` /// /// # Note /// The above holds true for normal 32-bit or 64-bit little endian systems. On /// 64-bit big-endian systems, the type's size increases to 24 bytes and the /// amount of inline storage to 23 bytes. #[derive(Clone)] pub struct EcoString(DynamicVec); impl EcoString { /// Maximum number of bytes for an inline `EcoString` before spilling on /// the heap. /// /// The exact value for this is architecture dependent. /// /// # Note /// This value is semver exempt and can be changed with any update. pub const INLINE_LIMIT: usize = crate::dynamic::LIMIT; /// Create a new, empty string. #[inline] pub const fn new() -> Self { Self(DynamicVec::new()) } /// Create a new, inline string. /// /// Panics if the string's length exceeds the capacity of the inline /// storage. #[inline] pub const fn inline(string: &str) -> Self { let Ok(inline) = InlineVec::from_slice(string.as_bytes()) else { exceeded_inline_capacity(); }; Self(DynamicVec::from_inline(inline)) } /// Create a new, empty string with the given `capacity`. #[inline] pub fn with_capacity(capacity: usize) -> Self { Self(DynamicVec::with_capacity(capacity)) } /// Create an instance from a string slice. #[inline] fn from_str(string: &str) -> Self { Self(DynamicVec::from_slice(string.as_bytes())) } /// Whether the string is empty. #[inline] pub fn is_empty(&self) -> bool { self.len() == 0 } /// The length of the string in bytes. #[inline] pub fn len(&self) -> usize { self.0.len() } /// A string slice containing the entire string. #[inline] pub fn as_str(&self) -> &str { // Safety: // The buffer contents stem from correct UTF-8 sources: // - Valid ASCII characters // - Other string slices // - Chars that were encoded with char::encode_utf8 unsafe { core::str::from_utf8_unchecked(self.0.as_slice()) } } /// Produce a mutable slice containing the entire string. /// /// Clones the string if its reference count is larger than 1. #[inline] pub fn make_mut(&mut self) -> &mut str { // Safety: // The buffer contents stem from correct UTF-8 sources: // - Valid ASCII characters // - Other string slices // - Chars that were encoded with char::encode_utf8 unsafe { core::str::from_utf8_unchecked_mut(self.0.make_mut()) } } /// Append the given character at the end. #[inline] pub fn push(&mut self, c: char) { if c.len_utf8() == 1 { self.0.push(c as u8); } else { self.push_str(c.encode_utf8(&mut [0; 4])); } } /// Append the given string slice at the end. pub fn push_str(&mut self, string: &str) { self.0.extend_from_slice(string.as_bytes()); } /// Remove the last character from the string. #[inline] pub fn pop(&mut self) -> Option { let slice = self.as_str(); let c = slice.chars().next_back()?; self.0.truncate(slice.len() - c.len_utf8()); Some(c) } /// Clear the string. #[inline] pub fn clear(&mut self) { self.0.clear(); } /// Shortens the string to the specified length. /// /// If `new_len` is greater than or equal to the string's current length, /// this has no effect. /// /// Panics if `new_len` does not lie on a [`char`] boundary. #[inline] pub fn truncate(&mut self, new_len: usize) { if new_len <= self.len() { assert!(self.is_char_boundary(new_len)); self.0.truncate(new_len); } } /// Replaces all matches of a string with another string. /// /// This is a bit less general that [`str::replace`] because the `Pattern` /// trait is unstable. In return, it can produce an `EcoString` without /// any intermediate [`String`] allocation. pub fn replace(&self, pat: &str, to: &str) -> Self { self.replacen(pat, to, usize::MAX) } /// Replaces the first N matches of a string with another string. /// /// This is a bit less general that [`str::replacen`] because the `Pattern` /// trait is unstable. In return, it can produce an `EcoString` without /// any intermediate [`String`] allocation. pub fn replacen(&self, pat: &str, to: &str, count: usize) -> Self { // Copied from the standard library: https://github.com/rust-lang/rust let mut result = Self::new(); let mut last_end = 0; for (start, part) in self.match_indices(pat).take(count) { // Safety: Copied from std. result.push_str(unsafe { self.get_unchecked(last_end..start) }); result.push_str(to); last_end = start + part.len(); } // Safety: Copied from std. result.push_str(unsafe { self.get_unchecked(last_end..self.len()) }); result } /// Returns the lowercase equivalent of this string. pub fn to_lowercase(&self) -> Self { let str = self.as_str(); let mut lower = Self::with_capacity(str.len()); for c in str.chars() { // Let std handle the special case. if c == 'Σ' { return str.to_lowercase().into(); } for v in c.to_lowercase() { lower.push(v); } } lower } /// Returns the uppercase equivalent of this string. pub fn to_uppercase(&self) -> Self { let str = self.as_str(); let mut upper = Self::with_capacity(str.len()); for c in str.chars() { for v in c.to_uppercase() { upper.push(v); } } upper } /// Returns a copy of this string where each character is mapped to its /// ASCII uppercase equivalent. pub fn to_ascii_lowercase(&self) -> Self { let mut s = self.clone(); s.make_mut().make_ascii_lowercase(); s } /// Returns a copy of this string where each character is mapped to its /// ASCII uppercase equivalent. pub fn to_ascii_uppercase(&self) -> Self { let mut s = self.clone(); s.make_mut().make_ascii_uppercase(); s } /// Repeat this string `n` times. pub fn repeat(&self, n: usize) -> Self { let slice = self.as_bytes(); let capacity = slice.len().saturating_mul(n); let mut vec = DynamicVec::with_capacity(capacity); for _ in 0..n { vec.extend_from_slice(slice); } Self(vec) } } impl Deref for EcoString { type Target = str; #[inline] fn deref(&self) -> &str { self.as_str() } } impl Default for EcoString { #[inline] fn default() -> Self { Self::new() } } impl Debug for EcoString { #[inline] fn fmt(&self, f: &mut Formatter) -> fmt::Result { Debug::fmt(self.as_str(), f) } } impl Display for EcoString { #[inline] fn fmt(&self, f: &mut Formatter) -> fmt::Result { Display::fmt(self.as_str(), f) } } impl Eq for EcoString {} impl PartialEq for EcoString { #[inline] fn eq(&self, other: &Self) -> bool { self.as_str().eq(other.as_str()) } } impl PartialEq for EcoString { #[inline] fn eq(&self, other: &str) -> bool { self.as_str().eq(other) } } impl PartialEq<&str> for EcoString { #[inline] fn eq(&self, other: &&str) -> bool { self.as_str().eq(*other) } } impl PartialEq for EcoString { #[inline] fn eq(&self, other: &String) -> bool { self.as_str().eq(other) } } impl PartialEq for str { #[inline] fn eq(&self, other: &EcoString) -> bool { self.eq(other.as_str()) } } impl PartialEq for &str { #[inline] fn eq(&self, other: &EcoString) -> bool { (*self).eq(other.as_str()) } } impl PartialEq for String { #[inline] fn eq(&self, other: &EcoString) -> bool { self.eq(other.as_str()) } } impl Ord for EcoString { #[inline] fn cmp(&self, other: &Self) -> Ordering { self.as_str().cmp(other.as_str()) } } impl PartialOrd for EcoString { #[inline] fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) } } impl Hash for EcoString { #[inline] fn hash(&self, state: &mut H) { self.as_str().hash(state); } } impl Write for EcoString { #[inline] fn write_str(&mut self, s: &str) -> fmt::Result { self.push_str(s); Ok(()) } #[inline] fn write_char(&mut self, c: char) -> fmt::Result { self.push(c); Ok(()) } } impl Add for EcoString { type Output = Self; #[inline] fn add(mut self, rhs: Self) -> Self::Output { self += rhs; self } } impl AddAssign for EcoString { #[inline] fn add_assign(&mut self, rhs: Self) { self.push_str(rhs.as_str()); } } impl Add<&str> for EcoString { type Output = Self; #[inline] fn add(mut self, rhs: &str) -> Self::Output { self += rhs; self } } impl AddAssign<&str> for EcoString { #[inline] fn add_assign(&mut self, rhs: &str) { self.push_str(rhs); } } impl AsRef for EcoString { #[inline] fn as_ref(&self) -> &str { self } } impl Borrow for EcoString { #[inline] fn borrow(&self) -> &str { self } } impl From for EcoString { #[inline] fn from(c: char) -> Self { Self::inline(c.encode_utf8(&mut [0; 4])) } } impl From<&str> for EcoString { #[inline] fn from(s: &str) -> Self { Self::from_str(s) } } impl From for EcoString { /// When the string does not fit inline, this needs to allocate to change /// the layout. #[inline] fn from(s: String) -> Self { Self::from_str(&s) } } impl From<&String> for EcoString { #[inline] fn from(s: &String) -> Self { Self::from_str(s.as_str()) } } impl From<&EcoString> for EcoString { #[inline] fn from(s: &EcoString) -> Self { s.clone() } } impl From> for EcoString { #[inline] fn from(s: Cow) -> Self { Self::from_str(&s) } } impl FromIterator for EcoString { #[inline] fn from_iter>(iter: T) -> Self { let mut s = Self::new(); for c in iter { s.push(c); } s } } impl FromIterator for EcoString { #[inline] fn from_iter>(iter: T) -> Self { let mut s = Self::new(); for piece in iter { s.push_str(&piece); } s } } impl Extend for EcoString { #[inline] fn extend>(&mut self, iter: T) { for c in iter { self.push(c); } } } impl From for String { /// This needs to allocate to change the layout. #[inline] fn from(s: EcoString) -> Self { s.as_str().into() } } impl From<&EcoString> for String { #[inline] fn from(s: &EcoString) -> Self { s.as_str().into() } } impl FromStr for EcoString { type Err = core::convert::Infallible; #[inline] fn from_str(s: &str) -> Result { Ok(Self::from_str(s)) } } #[cold] const fn exceeded_inline_capacity() -> ! { panic!("exceeded inline capacity"); } #[cfg(feature = "serde")] mod serde { use crate::EcoString; use core::fmt; use serde::de::{Deserializer, Error, Unexpected, Visitor}; impl serde::Serialize for EcoString { fn serialize(&self, serializer: S) -> Result where S: serde::Serializer, { self.as_str().serialize(serializer) } } impl<'de> serde::Deserialize<'de> for EcoString { fn deserialize(deserializer: D) -> Result where D: Deserializer<'de>, { struct EcoStringVisitor; impl Visitor<'_> for EcoStringVisitor { type Value = EcoString; fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { formatter.write_str("a string") } fn visit_str(self, v: &str) -> Result where E: Error, { Ok(EcoString::from(v)) } fn visit_bytes(self, v: &[u8]) -> Result where E: Error, { if let Ok(utf8) = core::str::from_utf8(v) { return Ok(EcoString::from(utf8)); } Err(Error::invalid_value(Unexpected::Bytes(v), &self)) } } deserializer.deserialize_str(EcoStringVisitor) } } } ecow-0.2.6/src/vec.rs000064400000000000000000001222441046102023000125050ustar 00000000000000//! A clone-on-write alternative to [`Vec`]. use core::alloc::Layout; use core::array; use core::borrow::Borrow; use core::cmp::Ordering; use core::fmt::{self, Debug, Formatter}; use core::hash::{Hash, Hasher}; use core::marker::PhantomData; use core::mem; use core::ops::Deref; use core::ptr::{self, NonNull}; #[cfg(not(feature = "std"))] use alloc::vec::Vec; use crate::sync::atomic::{self, AtomicUsize, Ordering::*}; /// Create a new [`EcoVec`] with the given elements. /// ``` /// # use ecow::eco_vec; /// assert_eq!(eco_vec![1; 4], [1; 4]); /// assert_eq!(eco_vec![1, 2, 3], [1, 2, 3]); /// ``` #[macro_export] macro_rules! eco_vec { () => { $crate::EcoVec::new() }; ($elem:expr; $n:expr) => { $crate::EcoVec::from_elem($elem, $n) }; ($($value:expr),+ $(,)?) => { $crate::EcoVec::from([$($value),+]) }; } /// An economical vector with clone-on-write semantics. /// /// This type has the same layout as a slice `&[T]`: It consists of a pointer /// and a length. The pointer is null-pointer optimized (meaning that /// [`Option>`] has the same size as `EcoVec`). Dereferencing an /// `EcoVec` to a slice is a no-op. /// /// Within its allocation, an `EcoVec` stores a reference count and its /// capacity. In contrast to an [`Arc>`](alloc::sync::Arc), it only /// requires a single allocation for both the reference count and the elements. /// The internal reference counter is atomic, making this type [`Sync`] and /// [`Send`]. /// /// Note that most mutating methods require [`T: Clone`](Clone) due to /// clone-on-write semantics. /// /// # Example /// ``` /// use ecow::EcoVec; /// /// // Empty vector does not allocate, but first push does. /// let mut first = EcoVec::new(); /// first.push(1); /// first.push(2); /// assert_eq!(first, [1, 2]); /// /// // This clone is cheap, it references the same allocation. /// let mut second = first.clone(); /// /// // This makes a real copy (clone-on-write). /// second.push(3); /// assert_eq!(second, [1, 2, 3]); /// /// // As `second` was cloned upon mutation, this iterator can /// // move the elements. If the allocation was still shared with /// // `first`, this would clone lazily. /// assert_eq!(second.into_iter().collect::>(), vec![1, 2, 3]); /// ``` #[repr(C)] pub struct EcoVec { /// Is `Self::dangling()` when the vector is unallocated. /// /// Otherwise, points `Self::offset()` bytes after a valid allocation and /// header, to the start of the vector's elements. It is then aligned to the /// maximum of the header's alignment and T's alignment. The pointer is /// valid for `len` reads and `capacity` writes of T. The elements may only /// be accessed mutably if the reference-count is `1`. ptr: NonNull, /// The number of elements in the vector. /// /// Invariant: `len <= capacity`. len: usize, /// See Vec's impl for more details. phantom: PhantomData, } /// The start of the backing allocation. /// /// This is followed by padding, if necessary, and then the actual data. #[derive(Debug)] struct Header { /// The vector's reference count. Starts at 1 and only drops to zero /// when the last vector is dropped. /// /// Invariant: `refs <= isize::MAX`. refs: AtomicUsize, /// The number of elements the backing allocation can hold. Zero if there /// is no backing allocation. /// /// May only be mutated if `refs == 1`. /// /// Invariant: `capacity <= isize::MAX`. capacity: usize, } impl EcoVec { /// Create a new, empty vector. /// /// This does not allocate. #[inline] pub const fn new() -> Self { Self { ptr: Self::dangling(), len: 0, phantom: PhantomData, } } /// Create a new, empty vector with at least the specified capacity. #[inline] pub fn with_capacity(capacity: usize) -> Self { let mut vec = Self::new(); if capacity > 0 { unsafe { // Safety: // - The reference count starts at 1. // - The capacity starts at 0 and the target capacity is checked // to be `> 0`. vec.grow(capacity); } } vec } /// Returns `true` if the vector contains no elements. #[inline] pub const fn is_empty(&self) -> bool { self.len == 0 } /// The number of elements in the vector. #[inline] pub const fn len(&self) -> usize { self.len } /// How many elements the vector's backing allocation can hold. /// /// Even if `len < capacity`, pushing into the vector may still /// allocate if the reference count is larger than one. #[inline] pub fn capacity(&self) -> usize { self.header().map_or(0, |header| header.capacity) } /// Extracts a slice containing the entire vector. #[inline] pub fn as_slice(&self) -> &[T] { // Safety: // - The pointer returned by `data()` is non-null, well-aligned, and // valid for `len` reads of `T`. // - We have the invariant `len <= capacity <= isize::MAX`. // - The memory referenced by the slice isn't mutated for the returned // slice's lifetime, because `self` becomes borrowed and even if there // are other vectors referencing the same backing allocation, they are // now allowed to mutate the slice since then the ref-count is larger // than one. unsafe { core::slice::from_raw_parts(self.data(), self.len) } } /// Removes all values from the vector. pub fn clear(&mut self) { // Nothing to do if it's empty. if self.is_empty() { return; } // If there are other vectors that reference the same backing // allocation, we just create a new, empty vector. if !self.is_unique() { // If another vector was dropped in the meantime, this vector could // have become unique, but we don't care, creating a new one // is safe nonetheless. Note that this runs the vector's drop // impl and reduces the ref-count. *self = Self::new(); return; } unsafe { let prev = self.len; self.len = 0; // Safety: // - We set the length to zero first in case a drop panics, so we // leak rather than double dropping. // - We have unique ownership of the backing allocation, so we can // keep it and clear it. In particular, no other vector can have // gained shared ownership in the meantime since `is_unique()`, // as this is the only live vector available for cloning and we // hold a mutable reference to it. // - The pointer returned by `data_mut()` is valid for `capacity` // writes, we have the invariant `prev <= capacity` and thus, // `data_mut()` is valid for `prev` writes. ptr::drop_in_place(ptr::slice_from_raw_parts_mut(self.data_mut(), prev)); } } } impl EcoVec { /// Create a new vector with `n` copies of `value`. pub fn from_elem(value: T, n: usize) -> Self { let mut vec = Self::with_capacity(n); for _ in 0..n { // Safety: we just called `EcoVec::with_capacity()` unsafe { vec.push_unchecked(value.clone()) } } vec } /// Produce a mutable slice containing the entire vector. /// /// Clones the vector if its reference count is larger than 1. pub fn make_mut(&mut self) -> &mut [T] { // To provide mutable access, we must have unique ownership over the // backing allocation. self.make_unique(); // Safety: // The reference count is `1` because of `make_unique`. // For more details, see `Self::as_slice()`. unsafe { core::slice::from_raw_parts_mut(self.data_mut(), self.len) } } /// Add a value at the end of the vector. /// /// Clones the vector if its reference count is larger than 1. #[inline] pub fn push(&mut self, value: T) { // Ensure unique ownership and grow the vector if necessary. self.reserve((self.len == self.capacity()) as usize); // Safety: we just called `EcoVec::reserve()` unsafe { self.push_unchecked(value); } } /// Add a value at the end of the vector, without reallocating. /// /// You must ensure that `self.is_unique()` and `self.len < self.capacity()` /// hold, by calling `EcoVec::with_capacity()` or `EcoVec::reserve()`. #[inline] unsafe fn push_unchecked(&mut self, value: T) { debug_assert!(self.is_unique()); debug_assert!(self.len < self.capacity()); unsafe { // Safety: // - The caller must ensure that the reference count is `1`. // - The pointer returned by `data_mut()` is valid for `capacity` // writes. // - The caller must ensure that `len < capacity`. // - Thus, `data_mut() + len` is valid for one write. ptr::write(self.data_mut().add(self.len), value); // Safety: // Since we reserved space, we maintain `len <= capacity`. self.len += 1; } } /// Removes the last element from a vector and returns it, or `None` if the /// vector is empty. /// /// Clones the vector if its reference count is larger than 1. #[inline] pub fn pop(&mut self) -> Option { if self.is_empty() { return None; } self.make_unique(); unsafe { // Safety: // Cannot underflow because `is_empty` returned `false`. self.len -= 1; // Safety: // - The reference count is `1` because of `make_unique`. // - The pointer returned by `data()` is valid for `len` reads and // thus `data() + new_len` is valid for one read. Some(ptr::read(self.data().add(self.len))) } } /// Inserts an element at an index within the vector, shifting all elements /// after it to the right. /// /// Clones the vector if its reference count is larger than 1. /// /// Panics if `index > len`. pub fn insert(&mut self, index: usize, value: T) { if index > self.len { out_of_bounds(index, self.len); } // Ensure unique ownership and grow the vector if necessary. self.reserve((self.len == self.capacity()) as usize); unsafe { // Safety: // - The reference count is `1` because of `reserve`. // - The pointer returned by `data_mut()` is valid for `len` // reads and `capacity` writes of `T`. // - Thus, `at` is valid for `len - index` reads of `T` // - And `at` is valid for `capacity - index` writes of `T`. // Because of the `reserve` call, we have `len < capacity` and // thus `at + 1` is valid for `len - index` writes of `T`. let at = self.data_mut().add(index); ptr::copy(at, at.add(1), self.len - index); // Safety: // - The pointer returned by `data_mut()` is valid for `capacity` // writes. // - Due to the bounds check above, `index <= len` // - Due to the reserve check, `len < capacity`. // - Thus, `data() + index` is valid for one write. ptr::write(at, value); // Safety: // Since we reserved space, we maintain `len <= capacity`. self.len += 1; } } /// Removes and returns the element at position index within the vector, /// shifting all elements after it to the left. /// /// Clones the vector if its reference count is larger than 1. /// /// Panics if `index >= len`. pub fn remove(&mut self, index: usize) -> T { if index >= self.len { out_of_bounds(index, self.len); } self.make_unique(); unsafe { // Safety: // - The reference count is `1` because of `make_unique`. // - The pointer returned by `data()` is valid for `len` reads. // - Due to the check above, `index < len`. // - Thus, `at` is valid for one read. let at = self.data_mut().add(index); let value = ptr::read(at); // Safety: // - The pointer returned by `data()` is valid for `len` reads and // `capacity` writes. // - Thus, `at + 1` is valid for `len - index - 1` reads. // - Thus, `at` is valid for `capacity - index` writes. // - Due to the invariant `len <= capacity`, `at` is also valid // for `len - index - 1` writes. ptr::copy(at.add(1), at, self.len - index - 1); // Safety: // Cannot underflow because `index < len` and thus `len > 0`. self.len -= 1; value } } /// Retains only the elements specified by the predicate. /// /// Clones the vector if its reference count is larger than 1. /// /// Note that this clones the vector even if `f` always returns `false`. To /// prevent that, you can first iterate over the vector yourself and then /// only call `retain` if your condition is `false` for some element. pub fn retain(&mut self, mut f: F) where F: FnMut(&mut T) -> bool, { // Modified from: https://github.com/servo/rust-smallvec // Copyright (c) 2018 The Servo Project Developers let len = self.len; let values = self.make_mut(); let mut del = 0; for i in 0..len { if !f(&mut values[i]) { del += 1; } else if del > 0 { values.swap(i - del, i); } } if del > 0 { self.truncate(len - del); } } /// Shortens the vector, keeping the first `target` elements and dropping /// the rest. /// /// Clones the vector if its reference count is larger than 1 and /// `target < len`. pub fn truncate(&mut self, target: usize) { if target >= self.len { return; } if !self.is_unique() { // Safety: Just checked bounds. *self = Self::from(unsafe { self.get_unchecked(..target) }); return; } let rest = self.len - target; unsafe { // Safety: // - Since `target < len`, we maintain `len <= capacity`. self.len = target; // Safety: // The reference count is `1` because of `make_unique`. // - The pointer returned by `data_mut()` is valid for `capacity` // writes. // - We have the invariant `len <= capacity`. // - Thus, `data_mut() + target` is valid for `len - target` writes. ptr::drop_in_place(ptr::slice_from_raw_parts_mut( self.data_mut().add(target), rest, )); } } /// Reserve space for at least `additional` more elements. /// /// Guarantees that the resulting vector has reference count `1` and space /// for `additional` more elements. pub fn reserve(&mut self, additional: usize) { let capacity = self.capacity(); let mut target = capacity; if additional > capacity - self.len { // Reserve at least the `additional` capacity, but also at least // double the capacity to ensure exponential growth and finally // jump directly to a minimum capacity to prevent frequent // reallocation for small vectors. target = self .len .checked_add(additional) .unwrap_or_else(|| capacity_overflow()) .max(2 * capacity) .max(Self::min_cap()); } if !self.is_unique() { let mut vec = Self::with_capacity(target); vec.extend(self.iter().cloned()); *self = vec; } else if target > capacity { unsafe { // Safety: // - The reference count is `1` because of `make_unique`. // - The `target` capacity is greater than the current capacity // because `additional > 0`. self.grow(target); } } } /// Clones and pushes all elements in a slice to the vector. pub fn extend_from_slice(&mut self, slice: &[T]) { if slice.is_empty() { return; } self.reserve(slice.len()); for value in slice { // Safety: // - The reference count is `1` because of `reserve`. // - `self.len < self.capacity()` because we reserved space for // `slice.len()` more elements. unsafe { self.push_unchecked(value.clone()); } } } /// Pushes all elements in a trusted-len iterator to the vector. /// /// # Safety /// We can't use `TrustedLen` because it is unstable. Still, the /// `ExactSizeIterator::len` must return the exact length of the iterator /// for this to be safe. pub unsafe fn extend_from_trusted(&mut self, iter: I) where I: IntoIterator, I::IntoIter: ExactSizeIterator, { let iter = iter.into_iter(); let count = iter.len(); if count == 0 { return; } self.reserve(count); for value in iter { // Safety: // - The reference count is `1` because of `reserve`. // - `self.len < self.capacity()` because we reserved space for // `iter.len()` more elements. unsafe { self.push_unchecked(value); } } } } impl EcoVec { /// Grow the capacity to at least the `target` size. /// /// May only be called if: /// - the reference count is `1`, and /// - `target > capacity` (i.e., this methods grows, it doesn't shrink). unsafe fn grow(&mut self, mut target: usize) { debug_assert!(self.is_unique()); debug_assert!(target > self.capacity()); // Maintain the `capacity <= isize::MAX` invariant. if target > isize::MAX as usize { capacity_overflow(); } // Directly go to maximum capacity for ZSTs. if mem::size_of::() == 0 { target = isize::MAX as usize; } let layout = Self::layout(target); let allocation = if !self.is_allocated() { // Safety: // The layout has non-zero size because `target > 0`. alloc::alloc::alloc(layout) } else { // Safety: // - `self.ptr` was allocated before (just checked) // - the old block was allocated with the current capacity // - `Self::size()` guarantees to return a value that is `> 0` // and rounded up to the nearest multiple of `Self::align()` // does not overflow `isize::MAX`. alloc::alloc::realloc( self.allocation_mut(), Self::layout(self.capacity()), Self::size(target), ) }; if allocation.is_null() { alloc::alloc::handle_alloc_error(layout); } // Construct data pointer by offsetting. // // Safety: // Just checked for null and adding only increases the size. Can't // overflow because the `allocation` is a valid pointer to // `Self::size(target)` bytes and `Self::offset() < Self::size(target)`. self.ptr = NonNull::new_unchecked(allocation.add(Self::offset()).cast()); debug_assert_ne!(self.ptr, Self::dangling()); // Safety: // The freshly allocated pointer is valid for a write of the header. ptr::write( allocation.cast::
(), Header { refs: AtomicUsize::new(1), capacity: target }, ); } /// Whether this vector has a backing allocation. #[inline] fn is_allocated(&self) -> bool { !ptr::eq(self.ptr.as_ptr(), Self::dangling().as_ptr()) } /// An immutable pointer to the backing allocation. /// /// May only be called if `is_allocated` returns `true`. #[inline] unsafe fn allocation(&self) -> *const u8 { debug_assert!(self.is_allocated()); self.ptr.as_ptr().cast::().sub(Self::offset()) } /// A mutable pointer to the backing allocation. /// /// May only be called if `is_allocated` returns `true`. #[inline] unsafe fn allocation_mut(&mut self) -> *mut u8 { debug_assert!(self.is_allocated()); self.ptr.as_ptr().cast::().sub(Self::offset()) } /// A reference to the header. #[inline] fn header(&self) -> Option<&Header> { // Safety: // If the vector is allocated, there is always a valid header. self.is_allocated() .then(|| unsafe { &*self.allocation().cast::
() }) } /// The data pointer. /// /// Returns a pointer that is non-null, well-aligned, and valid for `len` /// reads of `T`. #[inline] fn data(&self) -> *const T { self.ptr.as_ptr() } /// The data pointer, mutably. /// /// Returns a pointer that is non-null, well-aligned, and valid for /// `capacity` writes of `T`. /// /// May only be called if the reference count is 1. #[inline] unsafe fn data_mut(&mut self) -> *mut T { self.ptr.as_ptr() } /// The layout of a backing allocation for the given capacity. #[inline] fn layout(capacity: usize) -> Layout { // Safety: // - `Self::size(capacity)` guarantees that it rounded up the alignment // does not overflow `isize::MAX`. // - Since `Self::align()` is the header's alignment or T's alignment, // it fulfills the requirements of a valid alignment. unsafe { Layout::from_size_align_unchecked(Self::size(capacity), Self::align()) } } /// The size of a backing allocation for the given capacity. /// /// Always `> 0`. When rounded up to the next multiple of `Self::align()` is /// guaranteed to be `<= isize::MAX`. #[inline] fn size(capacity: usize) -> usize { mem::size_of::() .checked_mul(capacity) .and_then(|size| Self::offset().checked_add(size)) .filter(|&size| { // See `Layout::max_size_for_align` for details. size < isize::MAX as usize - Self::align() }) .unwrap_or_else(|| capacity_overflow()) } /// The alignment of the backing allocation. #[inline] const fn align() -> usize { max(mem::align_of::
(), mem::align_of::()) } /// The offset of the data in the backing allocation. /// /// Always `> 0`. `self.ptr` points to the data and `self.ptr - offset` to /// the header. #[inline] const fn offset() -> usize { max(mem::size_of::
(), Self::align()) } /// The sentinel value of `self.ptr`, used to indicate an uninitialized, /// unallocated vector. It is dangling (does not point to valid memory) and /// has no provenance. As such, it must not be used to read/write/offset. /// However, it is well-aligned, so it can be used to create 0-length /// slices. /// /// All pointers to allocated vector elements will be distinct from this /// value, because allocated vector elements start `Self::offset()` bytes /// into a heap allocation and heap allocations cannot start at 0 (null). #[inline] const fn dangling() -> NonNull { unsafe { // Safety: This is the stable equivalent of `core::ptr::invalid_mut`. // The pointer we create has no provenance and may not be // read/write/offset. #[allow(clippy::useless_transmute)] let ptr = mem::transmute::(Self::offset()); // Safety: `Self::offset()` is never 0. NonNull::new_unchecked(ptr) } } /// The minimum non-zero capacity. #[inline] const fn min_cap() -> usize { // In the spirit of the `EcoVec`, we choose the cutoff size of T from // which 1 is the minimum capacity a bit lower than a standard `Vec`. if mem::size_of::() == 1 { 8 } else if mem::size_of::() <= 32 { 4 } else { 1 } } } impl EcoVec { /// Ensure that this vector has a unique backing allocation. /// /// May change the capacity. fn make_unique(&mut self) { if !self.is_unique() { *self = Self::from(self.as_slice()); } } } impl EcoVec { /// Copies from a byte slice. #[inline] pub(crate) fn extend_from_byte_slice(&mut self, bytes: &[u8]) { if bytes.is_empty() { return; } self.reserve(bytes.len()); unsafe { // Safety: // - The source slice is valid for `bytes.len()` reads. // - The destination is valid for `bytes.len()` more writes due to // the `reserve` call. // - The two ranges are non-overlapping because we hold a mutable // reference to `self` and an immutable one to `bytes`. ptr::copy_nonoverlapping( bytes.as_ptr(), self.data_mut().add(self.len), bytes.len(), ); } self.len += bytes.len(); } } // Safety: Works like `Arc`. unsafe impl Sync for EcoVec {} unsafe impl Send for EcoVec {} impl EcoVec { /// Whether no other vector is pointing to the same backing allocation. /// /// This takes a mutable reference because only callers with ownership or a /// mutable reference can ensure that the result stays relevant. Potential /// callers with a shared reference could read `true` while another shared /// reference is cloned on a different thread, bumping the ref-count. By /// restricting this callers with mutable access, we ensure that no /// uncontrolled cloning is happening in the time between the `is_unique` /// call and any subsequent mutation. #[inline] pub fn is_unique(&mut self) -> bool { // See Arc's is_unique() method. self.header().map_or(true, |header| header.refs.load(Acquire) == 1) } } impl Clone for EcoVec { #[inline] fn clone(&self) -> Self { // If the vector has a backing allocation, bump the ref-count. if let Some(header) = self.header() { // See Arc's clone impl for details about memory ordering. let prev = header.refs.fetch_add(1, Relaxed); // See Arc's clone impl details about guarding against incredibly degenerate programs if prev > isize::MAX as usize { ref_count_overflow(self.ptr, self.len); } } Self { ptr: self.ptr, len: self.len, phantom: PhantomData } } } impl Drop for EcoVec { fn drop(&mut self) { // Drop our ref-count. If there was more than one vector before // (including this one), we shouldn't deallocate. Nothing to do if there // is no header and thus no backing allocation. See Arc's drop impl for // details about memory ordering. if self .header() .map_or(true, |header| header.refs.fetch_sub(1, Release) != 1) { return; } // See Arc's drop impl for details. atomic::fence(Acquire); // Ensures that the backing storage is deallocated even if one of the // element drops panics. struct Dealloc(*mut u8, Layout); impl Drop for Dealloc { fn drop(&mut self) { // Safety: See below. unsafe { alloc::alloc::dealloc(self.0, self.1); } } } // Safety: // The vector has a header, so `self.allocation()` points to an // allocation with the layout of current capacity. let _dealloc = unsafe { Dealloc(self.allocation_mut(), Self::layout(self.capacity())) }; unsafe { // Safety: // No other vector references the backing allocation (just checked). // For more details, see `Self::as_slice()`. ptr::drop_in_place(ptr::slice_from_raw_parts_mut(self.data_mut(), self.len)); } } } impl Deref for EcoVec { type Target = [T]; #[inline] fn deref(&self) -> &Self::Target { self.as_slice() } } impl Borrow<[T]> for EcoVec { #[inline] fn borrow(&self) -> &[T] { self.as_slice() } } impl AsRef<[T]> for EcoVec { #[inline] fn as_ref(&self) -> &[T] { self.as_slice() } } impl Default for EcoVec { #[inline] fn default() -> Self { Self::new() } } impl Debug for EcoVec { #[inline] fn fmt(&self, f: &mut Formatter) -> fmt::Result { self.as_slice().fmt(f) } } impl Hash for EcoVec { #[inline] fn hash(&self, state: &mut H) { self.as_slice().hash(state); } } impl Eq for EcoVec {} impl PartialEq for EcoVec { #[inline] fn eq(&self, other: &Self) -> bool { self.as_slice() == other.as_slice() } } impl PartialEq<[T]> for EcoVec { #[inline] fn eq(&self, other: &[T]) -> bool { self.as_slice() == other } } impl PartialEq<&[T]> for EcoVec { #[inline] fn eq(&self, other: &&[T]) -> bool { self.as_slice() == *other } } impl PartialEq<[T; N]> for EcoVec { #[inline] fn eq(&self, other: &[T; N]) -> bool { self.as_slice() == other } } impl PartialEq<&[T; N]> for EcoVec { #[inline] fn eq(&self, other: &&[T; N]) -> bool { self.as_slice() == *other } } impl PartialEq> for EcoVec { #[inline] fn eq(&self, other: &Vec) -> bool { self.as_slice() == other } } impl PartialEq> for [T] { #[inline] fn eq(&self, other: &EcoVec) -> bool { self == other.as_slice() } } impl PartialEq> for [T; N] { #[inline] fn eq(&self, other: &EcoVec) -> bool { self == other.as_slice() } } impl PartialEq> for Vec { #[inline] fn eq(&self, other: &EcoVec) -> bool { self == other.as_slice() } } impl Ord for EcoVec { #[inline] fn cmp(&self, other: &Self) -> Ordering { self.as_slice().cmp(other.as_slice()) } } impl PartialOrd for EcoVec { #[inline] fn partial_cmp(&self, other: &Self) -> Option { self.as_slice().partial_cmp(other.as_slice()) } } impl From<&[T]> for EcoVec { fn from(slice: &[T]) -> Self { let mut vec = Self::new(); vec.extend_from_slice(slice); vec } } impl From<[T; N]> for EcoVec { fn from(array: [T; N]) -> Self { let mut vec = Self::new(); unsafe { // Safety: Array's IntoIter implements `TrustedLen`. vec.extend_from_trusted(array); } vec } } impl From> for EcoVec { /// Allocates a new EcoVec, and moves other's items into it. fn from(mut other: Vec) -> Self { let len = other.len(); let mut vec = Self::with_capacity(len); unsafe { // Disables dropping of individual `Vec` items that will be moved // into the `EcoVec`. // // Safety: 0 is less than or equal to capacity. other.set_len(0); // Safety: // - The source vector is valid for `len` reads. // - The destination is valid for `len` writes due to the // `Self::with_capacity(len)` call. // - The source and destination are non-overlapping because we just // allocated the destination. ptr::copy_nonoverlapping(other.as_ptr(), vec.data_mut(), len); // Sets the correct length, and thereby also enables dropping of the // individual items that have been moved into the `EcoVec`. // There is no possiblity of double dropping because we've already // set the length of the original `Vec` to 0 before copying. vec.len = len; } vec } } impl TryFrom> for [T; N] { type Error = EcoVec; fn try_from(mut vec: EcoVec) -> Result { if vec.len() != N { return Err(vec); } Ok(if vec.is_unique() { // Set the length to zero to prevent double drop. vec.len = 0; // Safety: // - We have unique ownership over the underlying allocation. // - The pointer returned by `data()` is valid for `N` reads. // - We take ownership of the value and don't drop it again // in our drop impl. unsafe { ptr::read(vec.data() as *const [T; N]) } } else { // Safety: We know that the length is correct. unsafe { array::from_fn(|i| vec.get_unchecked(i).clone()) } }) } } impl FromIterator for EcoVec { fn from_iter>(iter: I) -> Self { let iter = iter.into_iter(); let hint = iter.size_hint().0; let mut vec = Self::with_capacity(hint); vec.extend(iter); vec } } impl Extend for EcoVec { fn extend(&mut self, iter: I) where I: IntoIterator, { let iter = iter.into_iter(); let hint = iter.size_hint().0; if hint > 0 { self.reserve(hint); } for value in iter { self.push(value); } } } impl<'a, T> IntoIterator for &'a EcoVec { type IntoIter = core::slice::Iter<'a, T>; type Item = &'a T; #[inline] fn into_iter(self) -> Self::IntoIter { self.as_slice().iter() } } impl IntoIterator for EcoVec { type IntoIter = IntoIter; type Item = T; #[inline] fn into_iter(mut self) -> Self::IntoIter { IntoIter { unique: self.is_unique(), front: 0, back: self.len, vec: self, } } } /// An owned iterator over an [`EcoVec`]. /// /// If the vector had a reference count of 1, this moves out of the vector, /// otherwise it lazily clones. pub struct IntoIter { /// The underlying vector. vec: EcoVec, /// Whether we have unique ownership over the underlying allocation. unique: bool, /// How many elements we have already read from the front. /// If `unique` is true, these must not be dropped in our drop impl! /// /// Invariant: `0 <= front <= back`. front: usize, /// How many elements we have already read from the back. /// If `unique` is true, these must not be dropped in our drop impl! /// /// Invariant: `0 <= back <= len`. back: usize, } impl IntoIter { /// Returns the remaining items of this iterator as a slice. #[inline] pub fn as_slice(&self) -> &[T] { unsafe { // Safety: // - The pointer returned by `data()` is valid for `len` reads. // - Since `front <= back <= len`, `data() + front` is valid for // `back - front` reads. // - For more details, see `EcoVec::as_slice`. core::slice::from_raw_parts( self.vec.data().add(self.front), self.back - self.front, ) } } } impl Iterator for IntoIter { type Item = T; #[inline] fn next(&mut self) -> Option { (self.front < self.back).then(|| { let prev = self.front; self.front += 1; if self.unique { // Safety: // - We have unique ownership over the underlying allocation. // - The pointer returned by `data()` is valid for `len` reads. // - We know that `prev < self.back <= len`. // - We take ownership of the value and don't drop it again // in our drop impl. unsafe { ptr::read(self.vec.data().add(prev)) } } else { // Safety: // - We know that `prev < self.back <= len`. unsafe { self.vec.get_unchecked(prev).clone() } } }) } #[inline] fn size_hint(&self) -> (usize, Option) { let len = self.back - self.front; (len, Some(len)) } #[inline] fn count(self) -> usize { self.len() } } impl DoubleEndedIterator for IntoIter { #[inline] fn next_back(&mut self) -> Option { (self.back > self.front).then(|| { self.back -= 1; if self.unique { // Safety: // - We have unique ownership over the underlying allocation. // - The pointer returned by `data()` is valid for `len` reads. // - We know that `self.back < len` at this point. // - We take ownership of the value and don't drop it again // in our drop impl. unsafe { ptr::read(self.vec.data().add(self.back)) } } else { // Safety: // - Due to the subtraction, `self.back < len` at this point. unsafe { self.vec.get_unchecked(self.back).clone() } } }) } } impl ExactSizeIterator for IntoIter {} impl Drop for IntoIter { fn drop(&mut self) { if !self.unique || !self.vec.is_allocated() { return; } // Safety: // We have unique ownership over the underlying allocation. unsafe { // Safety: // Set len to zero before dropping to prevent double dropping in // EcoVec's drop impl in case of panic. self.vec.len = 0; // Safety: // - The elements in `..self.front` and `self.back..` have // already been moved out of the vector. Thus, we only drop // the elements that remain in the middle. // - For details about the slicing, see `Self::as_slice()`. ptr::drop_in_place(ptr::slice_from_raw_parts_mut( self.vec.data_mut().add(self.front), self.back - self.front, )); } } } impl Debug for IntoIter { #[inline] fn fmt(&self, f: &mut Formatter) -> fmt::Result { f.debug_tuple("IntoIter").field(&self.as_slice()).finish() } } #[cold] fn capacity_overflow() -> ! { panic!("capacity overflow"); } #[cold] fn ref_count_overflow(ptr: NonNull, len: usize) -> ! { // Drop to decrement the ref count to counter the increment in `clone()` drop(EcoVec { ptr, len, phantom: PhantomData }); panic!("reference count overflow"); } #[cold] fn out_of_bounds(index: usize, len: usize) -> ! { panic!("index is out bounds (index: {index}, len: {len})"); } // Copy of `std::cmp::max::()` that is callable in `const` contexts #[inline] const fn max(x: usize, y: usize) -> usize { if x > y { x } else { y } } #[cfg(feature = "std")] impl std::io::Write for EcoVec { #[inline] fn write(&mut self, buf: &[u8]) -> std::io::Result { self.extend_from_byte_slice(buf); Ok(buf.len()) } #[inline] fn flush(&mut self) -> std::io::Result<()> { Ok(()) } } #[cfg(feature = "serde")] mod serde { use crate::EcoVec; use core::{fmt, marker::PhantomData}; use serde::de::{Deserializer, Visitor}; impl serde::Serialize for EcoVec { fn serialize(&self, serializer: S) -> Result where S: serde::Serializer, { self.as_slice().serialize(serializer) } } struct EcoVecVisitor(PhantomData); impl<'a, T: serde::Deserialize<'a> + Clone> Visitor<'a> for EcoVecVisitor { type Value = EcoVec; fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { formatter.write_str("a sequence") } fn visit_seq(self, mut seq: A) -> Result where A: serde::de::SeqAccess<'a>, { let len = seq.size_hint().unwrap_or(0); let mut values = EcoVec::with_capacity(len); while let Some(value) = seq.next_element()? { values.push(value) } Ok(values) } } impl<'de, T: serde::Deserialize<'de> + Clone> serde::Deserialize<'de> for EcoVec { fn deserialize(deserializer: D) -> Result where D: Deserializer<'de>, { deserializer.deserialize_seq(EcoVecVisitor(PhantomData)) } } } ecow-0.2.6/tests/tests.rs000064400000000000000000000332511046102023000134440ustar 00000000000000// Test with `cargo +nightly miri test` to check sanity! #![allow(clippy::redundant_clone)] #![allow(clippy::disallowed_names)] use std::borrow::Cow; use std::collections::HashMap; use std::fmt::Write; use std::mem; use std::sync::atomic::{AtomicUsize, Ordering::*}; use ecow::{eco_vec, EcoString, EcoVec}; const ALPH: &str = "abcdefghijklmnopqrstuvwxyz"; const LIMIT: usize = EcoString::INLINE_LIMIT; fn v(value: T) -> Box { Box::new(value) } #[test] fn test_mem_size() { let word = mem::size_of::(); assert_eq!(mem::size_of::>(), 2 * word); assert_eq!(mem::size_of::>>(), 2 * word); if cfg!(target_endian = "little") { if cfg!(target_pointer_width = "32") { // Inline length still should be reasonable on 32-bit systems assert_eq!(mem::size_of::(), 4 * word); // No niche :( assert_eq!(mem::size_of::>(), 5 * word); } else if cfg!(target_pointer_width = "64") { assert_eq!(mem::size_of::(), 2 * word); // No niche :( assert_eq!(mem::size_of::>(), 3 * word); } } } #[test] fn test_vec_macro() { assert_eq!(eco_vec![Box::new(1); 3], vec![v(1); 3]); } #[test] fn test_vec_construction() { assert_eq!(EcoVec::<()>::default(), &[]); assert_eq!(EcoVec::from(vec![(); 100]), vec![(); 100]); } #[test] fn test_from_vec_rc() { use std::rc::Rc; let x = Rc::new(()); let v = vec![x.clone()]; assert_eq!(Rc::strong_count(&x), 2); let vec = EcoVec::from(v); assert_eq!(Rc::strong_count(&x), 2, "Rc count should not change"); assert_eq!(vec.len(), 1); assert!(Rc::ptr_eq(&x, &vec[0])); std::mem::drop(vec); assert_eq!(Rc::strong_count(&x), 1); } #[test] fn test_vec_with_capacity() { let mut vec = EcoVec::with_capacity(3); assert_eq!(vec.capacity(), 3); let ptr = vec.as_ptr(); vec.push(1); vec.push(2); vec.push(3); assert_eq!(ptr, vec.as_ptr()); vec.push(4); assert_eq!(vec, [1, 2, 3, 4]); } #[test] #[should_panic(expected = "capacity overflow")] fn test_vec_with_capacity_fail() { EcoVec::::with_capacity(usize::MAX); } #[test] fn test_vec_empty() { let mut first = EcoVec::with_capacity(3); assert!(first.is_empty()); assert_eq!(first.len(), 0); first.push("hi".to_string()); assert!(!first.is_empty()); assert_eq!(first.len(), 1); let second = first.clone(); first.clear(); assert!(first.is_empty()); first.clear(); assert!(first.is_empty()); assert_eq!(second.len(), 1); assert_eq!(second, ["hi".to_string()]); } #[test] fn test_vec_make_mut() { let mut first = eco_vec![4, -3, 11, 6, 10]; let ptr = first.as_ptr(); first.make_mut()[1] -= 1; assert_eq!(ptr, first.as_ptr()); let second = first.clone(); first.make_mut().sort(); assert_eq!(first, [-4, 4, 6, 10, 11]); assert_eq!(second, [4, -4, 11, 6, 10]); } #[test] fn test_vec_push() { let mut first = EcoVec::new(); first.push(1); first.push(2); first.push(3); assert_eq!(first.len(), 3); let mut second = first.clone(); let third = second.clone(); let _ = third.clone(); second.push(4); assert_eq!(second.len(), 4); assert_eq!(first, [1, 2, 3]); assert_eq!(second, [1, 2, 3, 4]); assert_eq!(third, [1, 2, 3]); assert_ne!(first.as_ptr(), second.as_ptr()); assert_eq!(first.as_ptr(), third.as_ptr()); } #[test] fn test_vec_pop() { let mut first = EcoVec::new(); assert_eq!(first.pop(), None); first.push(v("a")); let ptr = first.as_ptr(); assert_eq!(first.pop(), Some(v("a"))); first.push(v("b")); assert_eq!(ptr, first.as_ptr()); let second = first.clone(); assert_eq!(first[0], v("b")); assert_eq!(ptr, first.as_ptr()); assert_eq!(first.pop(), Some(v("b"))); assert_eq!(first, []); assert_eq!(second, [v("b")]); } #[test] fn test_vec_insert() { let mut first = EcoVec::new(); first.insert(0, "okay"); let ptr = first.as_ptr(); first.insert(0, "reverse"); let mut second = first.clone(); first.insert(2, "a"); first.insert(1, "b"); second.insert(2, "last"); assert_eq!(first, ["reverse", "b", "okay", "a"]); assert_eq!(second, ["reverse", "okay", "last"]); assert_ne!(ptr, first.as_ptr()); assert_eq!(ptr, second.as_ptr()); } #[test] #[should_panic(expected = "index is out bounds (index: 4, len: 3)")] fn test_vec_insert_fail() { EcoVec::from([1, 2, 3]).insert(4, 0); } #[test] fn test_vec_remove() { let mut first = EcoVec::with_capacity(4); let ptr = first.as_ptr(); first.extend_from_slice(&[v(2), v(4), v(1)]); let second = first.clone(); assert_eq!(first.remove(1), v(4)); assert_eq!(first, [v(2), v(1)]); assert_eq!(second, [v(2), v(4), v(1)]); assert_ne!(ptr, first.as_ptr()); assert_eq!(ptr, second.as_ptr()); } #[test] #[should_panic(expected = "index is out bounds (index: 4, len: 3)")] fn test_vec_remove_fail() { EcoVec::from([1, 2, 3]).remove(4); } #[test] fn test_vec_truncate() { let mut vec = eco_vec!["ok"; 10]; vec.truncate(13); vec.truncate(3); assert_eq!(vec, ["ok"; 3]); let mut cloned = vec.clone(); cloned.truncate(2); assert_eq!(cloned, ["ok"; 2]); } #[test] fn test_vec_extend() { let mut vec = EcoVec::new(); vec.extend_from_slice(&[]); vec.extend_from_slice(&[2, 3, 4]); assert_eq!(vec, [2, 3, 4]); } #[test] fn test_vec_into_iter() { let first = eco_vec![v(2), v(4), v(5)]; let mut second = first.clone(); assert_eq!(first.clone().into_iter().count(), 3); assert_eq!(second.clone().into_iter().rev().collect::>(), [v(5), v(4), v(2)]); second.clear(); assert_eq!(second.into_iter().collect::>(), []); assert_eq!(first.clone().into_iter().collect::>(), [v(2), v(4), v(5)]); let mut iter = first.into_iter(); assert_eq!(iter.len(), 3); assert_eq!(iter.next(), Some(v(2))); assert_eq!(iter.next_back(), Some(v(5))); assert_eq!(iter.as_slice(), [v(4)]); drop(iter); } #[test] fn test_vec_zst() { static COUNTER: AtomicUsize = AtomicUsize::new(0); #[derive(Clone)] struct Potato; impl Drop for Potato { fn drop(&mut self) { COUNTER.fetch_add(1, SeqCst); } } let mut vec = EcoVec::new(); for _ in 0..1000 { vec.push(Potato); } assert_eq!(vec.len(), 1000); drop(vec); assert_eq!(COUNTER.load(SeqCst), 1000); } #[test] fn test_vec_huge_alignment() { #[derive(Debug, PartialEq, Clone)] #[repr(align(128))] struct B(&'static str); let mut vec: EcoVec = "hello, world! what's going on?".split_whitespace().map(B).collect(); assert_eq!(vec.len(), 5); assert_eq!(vec.capacity(), 8); assert_eq!(vec, [B("hello,"), B("world!"), B("what's"), B("going"), B("on?")]); assert_eq!(vec.pop(), Some(B("on?"))); assert_eq!(vec.len(), 4); assert_eq!(vec.last(), Some(&B("going"))); assert_eq!(vec.remove(1), B("world!")); assert_eq!(vec.len(), 3); assert_eq!(vec, [B("hello,"), B("what's"), B("going")]); assert_eq!(vec[1], B("what's")); vec.push(B("where?")); vec.insert(1, B("wonder!")); assert_eq!(vec, [B("hello,"), B("wonder!"), B("what's"), B("going"), B("where?")]); vec.retain(|b| b.0.starts_with('w')); assert_eq!(vec, [B("wonder!"), B("what's"), B("where?")]); vec.truncate(1); assert_eq!(vec.last(), vec.first()); let empty: EcoVec = EcoVec::new(); assert_eq!(empty, &[]); } #[test] #[should_panic(expected = "dropped the hot potato!")] #[allow(unused_must_use)] fn test_vec_drop_panic() { #[derive(Clone)] struct Potato; impl Drop for Potato { fn drop(&mut self) { panic!("dropped the hot potato!"); } } eco_vec![Potato]; } #[test] #[should_panic(expected = "dropped the hot potato!")] fn test_vec_clear_drop_panic() { #[derive(Clone)] struct Potato; impl Drop for Potato { fn drop(&mut self) { panic!("dropped the hot potato!"); } } let mut vec = eco_vec![Potato]; vec.clear(); } #[test] fn test_array_from_vec() { let array = [String::from("foo"), String::from("bar")]; let a = EcoVec::from(array.clone()); let b = a.clone(); let c: [String; 2] = a.try_into().unwrap(); assert_eq!(b, c); let d = b.clone(); assert_eq!(c, array); assert_eq!(d, array); drop(b); assert_eq!(c, array); drop(d); assert_eq!(c, array); assert_eq!(<[String; 0]>::try_from(EcoVec::new()).unwrap(), <[String; 0]>::default()); } #[test] fn test_str_new() { // Test inline strings. assert_eq!(EcoString::new(), ""); assert_eq!(EcoString::from('a'), "a"); assert_eq!(EcoString::from('😀'), "😀"); assert_eq!(EcoString::from("abc"), "abc"); // Test around the inline limit. assert_eq!(EcoString::from(&ALPH[..LIMIT - 1]), ALPH[..LIMIT - 1]); assert_eq!(EcoString::from(&ALPH[..LIMIT]), ALPH[..LIMIT]); assert_eq!(EcoString::from(&ALPH[..LIMIT + 1]), ALPH[..LIMIT + 1]); // Test heap string. assert_eq!(EcoString::from(ALPH), ALPH); } #[test] fn test_str_push() { let mut v = EcoString::new(); v.push('a'); v.push('b'); v.push_str("cd😀"); assert_eq!(v, "abcd😀"); assert_eq!(v.len(), 8); // Test fully filling the inline storage. v.push_str("efghijk"); assert_eq!(v.len(), 15); // Test spilling with `push`. let mut a = v.clone(); assert_eq!(a, "abcd😀efghijk"); a.push('l'); assert_eq!(a, "abcd😀efghijkl"); assert_eq!(a.len(), 16); // Test spilling with `push_str`. let mut b = v.clone(); b.push_str("lmn"); assert_eq!(b, "abcd😀efghijklmn"); assert_eq!(b.len(), 18); // v should be unchanged. assert_eq!(v.len(), 15); } #[test] fn test_str_pop() { // Test with inline string. let mut v = EcoString::from("Hello World!"); assert_eq!(v.pop(), Some('!')); assert_eq!(v, "Hello World"); // Remove one-by-one. for _ in 0..10 { v.pop(); } assert_eq!(v, "H"); assert_eq!(v.pop(), Some('H')); assert_eq!(v, ""); assert!(v.is_empty()); // Test with large string. let mut v = EcoString::from(ALPH); assert_eq!(v.pop(), Some('z')); assert_eq!(v.len(), 25); } #[test] fn test_str_index() { // Test that we can use the index syntax. let v = EcoString::from("abc"); assert_eq!(&v[..2], "ab"); } #[test] fn test_str_case() { assert_eq!(EcoString::new().to_uppercase(), ""); assert_eq!(EcoString::from("abc").to_uppercase(), "ABC"); assert_eq!(EcoString::from("AΣ").to_lowercase(), "aς"); assert_eq!( EcoString::from("a").repeat(100).to_uppercase(), EcoString::from("A").repeat(100) ); assert_eq!( EcoString::from("Ö").repeat(20).to_lowercase(), EcoString::from("ö").repeat(20) ); } #[test] fn test_str_repeat() { // Test with empty string. assert_eq!(EcoString::new().repeat(0), ""); assert_eq!(EcoString::new().repeat(100), ""); // Test non-spilling and spilling case. let v = EcoString::from("abc"); assert_eq!(v.repeat(0), ""); assert_eq!(v.repeat(3), "abcabcabc"); assert_eq!(v.repeat(5), "abcabcabcabcabc"); } #[test] fn test_str_inline_okay() { assert_eq!(EcoString::inline("hello"), "hello"); } #[test] #[should_panic(expected = "exceeded inline capacity")] fn test_str_inline_capacity_exceeded() { EcoString::inline("this is a pretty long string"); } #[test] fn test_str_clear() { let mut inline_clear = EcoString::from("foo"); inline_clear.clear(); assert_eq!(inline_clear, ""); let mut spilled_clear: EcoString = std::iter::repeat('a').take(100).collect(); let cloned = spilled_clear.clone(); spilled_clear.clear(); assert_eq!(spilled_clear, ""); assert_eq!(cloned.len(), 100); } #[test] fn test_str_construction() { let from_cow = EcoString::from(Cow::Borrowed("foo")); let from_char_iter: EcoString = "foo".chars().collect(); let from_eco_string_iter: EcoString = [EcoString::from("f"), EcoString::from("oo")].into_iter().collect(); assert_eq!(from_cow, from_char_iter); assert_eq!(from_char_iter, from_eco_string_iter); assert_eq!(from_eco_string_iter, "foo"); let str_from_eco_string_ref = String::from(&from_cow); let str_from_eco_string = String::from(from_cow); assert_eq!(str_from_eco_string, str_from_eco_string_ref); assert_eq!(str_from_eco_string_ref, "foo"); } #[test] fn test_str_extend() { let mut s = EcoString::from("Hello, "); s.extend("world!".chars()); assert_eq!(s, "Hello, world!"); } #[test] fn test_str_add() { let hello = EcoString::from("Hello, "); let world = EcoString::from("world!"); let add = hello.clone() + world.clone(); let mut add_assign = hello.clone(); add_assign += world; assert_eq!(add, add_assign); assert_eq!(add, "Hello, world!"); let add_str = hello.clone() + "world!"; let mut add_assign_str = hello.clone(); add_assign_str += "world!"; assert_eq!(add_str, add_assign_str); assert_eq!(add_str, "Hello, world!"); } #[test] fn test_str_complex() { let mut foo = EcoString::default(); foo.write_char('f').unwrap(); foo.write_str("oo").unwrap(); let bar = EcoString::from("bar"); let mut hash_map: HashMap<_, EcoVec<_>> = [ (foo.clone(), eco_vec![foo.clone(), bar.clone(), foo]), (bar.clone(), eco_vec![bar; 1]), ] .into_iter() .collect(); hash_map.get_mut("foo").unwrap().make_mut().sort(); assert_eq!(hash_map.get("foo").unwrap(), &["bar".into(), "foo".into(), "foo".into()]); assert_eq!(hash_map.get("bar").unwrap(), &["bar".into()]); } ecow-0.2.6/tests/ub_guards.rs000064400000000000000000000037441046102023000142610ustar 00000000000000use ecow::{eco_vec, EcoVec}; // Guarding against something like: // https://github.com/servo/rust-smallvec/issues/96 aka RUSTSEC-2018-0003 // If length isn't updated defensively then a panic when iterating could // double-free a value. #[test] #[should_panic(expected = "Panic on next")] fn panicky_iterator_unwinds_correctly() { struct PanicIter; impl Iterator for PanicIter { type Item = u32; fn size_hint(&self) -> (usize, Option) { (1, None) } fn next(&mut self) -> Option { panic!("Panic on next"); } } let mut v = eco_vec![1, 2, 3]; v.extend(PanicIter); } // Guarding against something like: // https://github.com/servo/rust-smallvec/issues/252 aka RUSTSEC-2021-0003 // size_hint should only be treated as a hint, nothing more. #[test] fn small_size_hint_is_fine() { let mut v = EcoVec::new(); v.push(123); let iter = (0u8..=255).filter(|n| n % 2 == 0); assert_eq!(iter.size_hint().0, 0); v.extend(iter); assert_eq!( v, core::iter::once(123) .chain((0u8..=255).filter(|n| n % 2 == 0)) .collect::>() ); } // Guarding against something like: // https://github.com/Alexhuszagh/rust-stackvector/issues/2 aka RUSTSEC-2021-0048 // size_hint should only be treated as a hint, nothing more. #[test] fn wacky_size_hint_is_fine() { struct IncorrectIterator(core::iter::Take>); impl IncorrectIterator { pub fn new() -> Self { IncorrectIterator(core::iter::repeat(1).take(20)) } } impl Iterator for IncorrectIterator { type Item = u8; fn next(&mut self) -> Option { self.0.next() } fn size_hint(&self) -> (usize, Option) { (20, Some(0)) } } let mut v = EcoVec::new(); v.extend(IncorrectIterator::new()); assert_eq!(v, IncorrectIterator::new().collect::>()) }