Repository: ParkMyCar/compact_str
Pull request #287 (2023-08-26 03:44:54)

support no_std
I have a compiler/interpreter project I'm developing for embedded systems, and this crate looks perfect for saving a ton of heap space and gaining locality for my many symbol tables. The only issue is that it needs to be `no_std` and #29 was closed a long time ago. So I've gone ahead and redone that work here.

It's pretty much all just a switch from `std` to `core` or `alloc` versions of things, plus a new (default) feature flag called `std` that enables a few small things like operations on `OsString` and the like.

The new version passes all test cases in standard mode, successfully builds in no-std mode, and miri has no issues with anything.


Patch:
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644
index 00000000..8bb64082
--- /dev/null
+++ b/.github/dependabot.yml
@@ -0,0 +1,14 @@
+# Dependabot dependency version checks / updates
+
+version: 2
+updates:
+  - package-ecosystem: "github-actions"
+    # Workflow files stored in the
+    # default location of `.github/workflows`
+    directory: "/"
+    schedule:
+      interval: "daily"
+  - package-ecosystem: "cargo"
+    directory: "/"
+    schedule:
+      interval: "daily"
diff --git a/.github/workflows/benchmarks.yml b/.github/workflows/benchmarks.yml
index 8783c0c9..201a72c3 100644
--- a/.github/workflows/benchmarks.yml
+++ b/.github/workflows/benchmarks.yml
@@ -12,7 +12,7 @@ jobs:
     name: cargo bench
     runs-on: ubuntu-latest
     steps:
-      - uses: actions/checkout@v2
+      - uses: actions/checkout@v3
       - uses: actions-rs/toolchain@v1
         with:
           profile: minimal
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index a83cb86d..51d992e8 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -18,7 +18,7 @@ jobs:
     name: cargo check
     runs-on: ubuntu-latest
     steps:
-      - uses: actions/checkout@v2
+      - uses: actions/checkout@v3
       - uses: actions-rs/toolchain@v1
         with:
           profile: minimal
@@ -44,7 +44,7 @@ jobs:
     name: cargo test
     runs-on: ubuntu-latest
     steps:
-      - uses: actions/checkout@v2
+      - uses: actions/checkout@v3
       - uses: actions-rs/toolchain@v1
         with:
           profile: minimal
@@ -71,7 +71,7 @@ jobs:
     name: cargo test nightly
     runs-on: ubuntu-latest
     steps:
-      - uses: actions/checkout@v2
+      - uses: actions/checkout@v3
       - uses: actions/cache@v3
         with:
           path: |
@@ -99,7 +99,7 @@ jobs:
     name: cargo miri test
     runs-on: ubuntu-latest
     steps:
-      - uses: actions/checkout@v2
+      - uses: actions/checkout@v3
       - uses: actions-rs/toolchain@v1
         with:
           profile: minimal
@@ -128,7 +128,7 @@ jobs:
     env:
       RUSTFLAGS: -Zrandomize-layout
     steps:
-      - uses: actions/checkout@v2
+      - uses: actions/checkout@v3
       - uses: actions-rs/toolchain@v1
         with:
           profile: minimal
@@ -154,7 +154,7 @@ jobs:
     name: example - bytes
     runs-on: ubuntu-latest
     steps:
-      - uses: actions/checkout@v2
+      - uses: actions/checkout@v3
       - uses: actions-rs/toolchain@v1
         with:
           profile: minimal
@@ -181,7 +181,7 @@ jobs:
     name: example - macros
     runs-on: ubuntu-latest
     steps:
-      - uses: actions/checkout@v2
+      - uses: actions/checkout@v3
       - uses: actions-rs/toolchain@v1
         with:
           profile: minimal
@@ -208,7 +208,7 @@ jobs:
     name: example - serde
     runs-on: ubuntu-latest
     steps:
-      - uses: actions/checkout@v2
+      - uses: actions/checkout@v3
       - uses: actions-rs/toolchain@v1
         with:
           profile: minimal
@@ -235,7 +235,7 @@ jobs:
     name: example - traits
     runs-on: ubuntu-latest
     steps:
-      - uses: actions/checkout@v2
+      - uses: actions/checkout@v3
       - uses: actions-rs/toolchain@v1
         with:
           profile: minimal
diff --git a/.github/workflows/clippy.yml b/.github/workflows/clippy.yml
index 47982787..fc8a442c 100644
--- a/.github/workflows/clippy.yml
+++ b/.github/workflows/clippy.yml
@@ -16,7 +16,7 @@ jobs:
     name: cargo fmt
     runs-on: ubuntu-latest
     steps:
-      - uses: actions/checkout@v2
+      - uses: actions/checkout@v3
         name: Checkout compact_str
       - uses: actions-rs/toolchain@v1
         name: Install Rust
@@ -37,7 +37,7 @@ jobs:
     name: cargo clippy
     runs-on: ubuntu-latest
     steps:
-      - uses: actions/checkout@v2
+      - uses: actions/checkout@v3
         name: Checkout compact_str
       - uses: actions-rs/toolchain@v1
         name: Install Rust
@@ -58,7 +58,7 @@ jobs:
     env:
       RUSTDOCFLAGS: "-D warnings --cfg docsrs"
     steps:
-      - uses: actions/checkout@v2
+      - uses: actions/checkout@v3
         name: Checkout compact_str
       - uses: actions-rs/toolchain@v1
         name: Install Rust
diff --git a/.github/workflows/cross_platform.yml b/.github/workflows/cross_platform.yml
index 09110f44..f6829eae 100644
--- a/.github/workflows/cross_platform.yml
+++ b/.github/workflows/cross_platform.yml
@@ -24,7 +24,7 @@ jobs:
     name: Windows
     runs-on: windows-latest
     steps:
-      - uses: actions/checkout@v2
+      - uses: actions/checkout@v3
         name: Checkout Repo
       - uses: actions-rs/toolchain@v1
         name: Install Rust
@@ -57,7 +57,7 @@ jobs:
     name: macOS
     runs-on: macos-latest
     steps:
-      - uses: actions/checkout@v2
+      - uses: actions/checkout@v3
         name: Checkout Repo
       - uses: actions-rs/toolchain@v1
         name: Install Rust
@@ -86,172 +86,174 @@ jobs:
           command: miri
           args: test --all-features --manifest-path=compact_str/Cargo.toml
 
-  linux_32bit:
-    name: Linux 32-bit
-    runs-on: ubuntu-latest
-    steps:
-      - uses: actions/checkout@v2
-        name: Checkout Repo
-      - uses: actions-rs/toolchain@v1
-        name: Install Rust
-        with:
-          toolchain: nightly
-          target: i686-unknown-linux-gnu
-          override: true
-          components: miri
-      - uses: actions/cache@v3
-        with:
-          path: |
-            ~/.cargo/bin/
-            ~/.cargo/registry/index/
-            ~/.cargo/registry/cache/
-            ~/.cargo/git/db/
-            target/
-          key: ${{ runner.os }}-${{ hashFiles('**/Cargo.lock') }}-x-plat-linux-32bit-v2
-      - uses: actions-rs/cargo@v1
-        name: cargo test
-        with:
-          use-cross: true
-          command: test
-          args: --release --all-features --manifest-path=compact_str/Cargo.toml --target i686-unknown-linux-gnu
-      - uses: actions-rs/cargo@v1
-        name: cargo test miri
-        with:
-          command: miri
-          args: test --all-features --manifest-path=compact_str/Cargo.toml --target i686-unknown-linux-gnu
+  # TODO(parkmycar): Re-enable before release of next version.
+  #
+  # linux_32bit:
+  #   name: Linux 32-bit
+  #   runs-on: ubuntu-latest
+  #   steps:
+  #     - uses: actions/checkout@v3
+  #       name: Checkout Repo
+  #     - uses: actions-rs/toolchain@v1
+  #       name: Install Rust
+  #       with:
+  #         toolchain: nightly
+  #         target: i686-unknown-linux-gnu
+  #         override: true
+  #         components: miri
+  #     - uses: actions/cache@v3
+  #       with:
+  #         path: |
+  #           ~/.cargo/bin/
+  #           ~/.cargo/registry/index/
+  #           ~/.cargo/registry/cache/
+  #           ~/.cargo/git/db/
+  #           target/
+  #         key: ${{ runner.os }}-${{ hashFiles('**/Cargo.lock') }}-x-plat-linux-32bit-v2
+  #     - uses: actions-rs/cargo@v1
+  #       name: cargo test
+  #       with:
+  #         use-cross: true
+  #         command: test
+  #         args: --release --all-features --manifest-path=compact_str/Cargo.toml --target i686-unknown-linux-gnu
+  #     - uses: actions-rs/cargo@v1
+  #       name: cargo test miri
+  #       with:
+  #         command: miri
+  #         args: test --all-features --manifest-path=compact_str/Cargo.toml --target i686-unknown-linux-gnu
 
-  linux_mips_32bit:
-    name: Linux MIPS Big Endian 32-bit
-    runs-on: ubuntu-latest
-    steps:
-      - uses: actions/checkout@v2
-        name: Checkout Repo
-      - uses: actions-rs/toolchain@v1
-        name: Install Rust
-        with:
-          toolchain: nightly
-          target: mips-unknown-linux-gnu
-          override: true
-          components: miri
-      - uses: actions/cache@v3
-        with:
-          path: |
-            ~/.cargo/bin/
-            ~/.cargo/registry/index/
-            ~/.cargo/registry/cache/
-            ~/.cargo/git/db/
-            target/
-          key: ${{ runner.os }}-${{ hashFiles('**/Cargo.lock') }}-x-plat-linux-mips-32bit-v2
-      - uses: actions-rs/cargo@v1
-        name: cargo test
-        with:
-          use-cross: true
-          command: test
-          args: --release --all-features --manifest-path=compact_str/Cargo.toml --target mips-unknown-linux-gnu
-      - uses: actions-rs/cargo@v1
-        name: cargo test miri
-        with:
-          command: miri
-          args: test --all-features --manifest-path=compact_str/Cargo.toml --target mips-unknown-linux-gnu
+  # linux_mips_32bit:
+  #   name: Linux MIPS Big Endian 32-bit
+  #   runs-on: ubuntu-latest
+  #   steps:
+  #     - uses: actions/checkout@v3
+  #       name: Checkout Repo
+  #     - uses: actions-rs/toolchain@v1
+  #       name: Install Rust
+  #       with:
+  #         toolchain: nightly
+  #         target: mips-unknown-linux-gnu
+  #         override: true
+  #         components: miri
+  #     - uses: actions/cache@v3
+  #       with:
+  #         path: |
+  #           ~/.cargo/bin/
+  #           ~/.cargo/registry/index/
+  #           ~/.cargo/registry/cache/
+  #           ~/.cargo/git/db/
+  #           target/
+  #         key: ${{ runner.os }}-${{ hashFiles('**/Cargo.lock') }}-x-plat-linux-mips-32bit-v2
+  #     - uses: actions-rs/cargo@v1
+  #       name: cargo test
+  #       with:
+  #         use-cross: true
+  #         command: test
+  #         args: --release --all-features --manifest-path=compact_str/Cargo.toml --target mips-unknown-linux-gnu
+  #     - uses: actions-rs/cargo@v1
+  #       name: cargo test miri
+  #       with:
+  #         command: miri
+  #         args: test --all-features --manifest-path=compact_str/Cargo.toml --target mips-unknown-linux-gnu
 
-  linux_mips_le_32bit:
-    name: Linux MIPS Little Endian 32-bit
-    runs-on: ubuntu-latest
-    steps:
-      - uses: actions/checkout@v2
-        name: Checkout Repo
-      - uses: actions-rs/toolchain@v1
-        name: Install Rust
-        with:
-          toolchain: nightly
-          target: mipsel-unknown-linux-gnu
-          override: true
-          components: miri
-      - uses: actions/cache@v3
-        with:
-          path: |
-            ~/.cargo/bin/
-            ~/.cargo/registry/index/
-            ~/.cargo/registry/cache/
-            ~/.cargo/git/db/
-            target/
-          key: ${{ runner.os }}-${{ hashFiles('**/Cargo.lock') }}-x-plat-linux-mips-le-32bit-v2
-      - uses: actions-rs/cargo@v1
-        name: cargo test
-        with:
-          use-cross: true
-          command: test
-          args: --release --all-features --manifest-path=compact_str/Cargo.toml --target mipsel-unknown-linux-gnu
-      - uses: actions-rs/cargo@v1
-        name: cargo test miri
-        with:
-          command: miri
-          args: test --all-features --manifest-path=compact_str/Cargo.toml --target mipsel-unknown-linux-gnu
+  # linux_mips_le_32bit:
+  #   name: Linux MIPS Little Endian 32-bit
+  #   runs-on: ubuntu-latest
+  #   steps:
+  #     - uses: actions/checkout@v3
+  #       name: Checkout Repo
+  #     - uses: actions-rs/toolchain@v1
+  #       name: Install Rust
+  #       with:
+  #         toolchain: nightly
+  #         target: mipsel-unknown-linux-gnu
+  #         override: true
+  #         components: miri
+  #     - uses: actions/cache@v3
+  #       with:
+  #         path: |
+  #           ~/.cargo/bin/
+  #           ~/.cargo/registry/index/
+  #           ~/.cargo/registry/cache/
+  #           ~/.cargo/git/db/
+  #           target/
+  #         key: ${{ runner.os }}-${{ hashFiles('**/Cargo.lock') }}-x-plat-linux-mips-le-32bit-v2
+  #     - uses: actions-rs/cargo@v1
+  #       name: cargo test
+  #       with:
+  #         use-cross: true
+  #         command: test
+  #         args: --release --all-features --manifest-path=compact_str/Cargo.toml --target mipsel-unknown-linux-gnu
+  #     - uses: actions-rs/cargo@v1
+  #       name: cargo test miri
+  #       with:
+  #         command: miri
+  #         args: test --all-features --manifest-path=compact_str/Cargo.toml --target mipsel-unknown-linux-gnu
 
-  linux_powerpc_bit:
-    name: Linux PowerPC Big Endian 32-bit
-    runs-on: ubuntu-latest
-    steps:
-      - uses: actions/checkout@v2
-        name: Checkout Repo
-      - uses: actions-rs/toolchain@v1
-        name: Install Rust
-        with:
-          toolchain: nightly
-          target: powerpc-unknown-linux-gnu
-          override: true
-          components: miri
-      - uses: actions/cache@v3
-        with:
-          path: |
-            ~/.cargo/bin/
-            ~/.cargo/registry/index/
-            ~/.cargo/registry/cache/
-            ~/.cargo/git/db/
-            target/
-          key: ${{ runner.os }}-${{ hashFiles('**/Cargo.lock') }}-x-plat-linux-powerpc-32bit-v2
-      - uses: actions-rs/cargo@v1
-        name: cargo test
-        with:
-          use-cross: true
-          command: test
-          args: --release --all-features --manifest-path=compact_str/Cargo.toml --target powerpc-unknown-linux-gnu
-      - uses: actions-rs/cargo@v1
-        name: cargo test miri
-        with:
-          command: miri
-          args: test --all-features --manifest-path=compact_str/Cargo.toml --target powerpc-unknown-linux-gnu
+  # linux_powerpc_bit:
+  #   name: Linux PowerPC Big Endian 32-bit
+  #   runs-on: ubuntu-latest
+  #   steps:
+  #     - uses: actions/checkout@v3
+  #       name: Checkout Repo
+  #     - uses: actions-rs/toolchain@v1
+  #       name: Install Rust
+  #       with:
+  #         toolchain: nightly
+  #         target: powerpc-unknown-linux-gnu
+  #         override: true
+  #         components: miri
+  #     - uses: actions/cache@v3
+  #       with:
+  #         path: |
+  #           ~/.cargo/bin/
+  #           ~/.cargo/registry/index/
+  #           ~/.cargo/registry/cache/
+  #           ~/.cargo/git/db/
+  #           target/
+  #         key: ${{ runner.os }}-${{ hashFiles('**/Cargo.lock') }}-x-plat-linux-powerpc-32bit-v2
+  #     - uses: actions-rs/cargo@v1
+  #       name: cargo test
+  #       with:
+  #         use-cross: true
+  #         command: test
+  #         args: --release --all-features --manifest-path=compact_str/Cargo.toml --target powerpc-unknown-linux-gnu
+  #     - uses: actions-rs/cargo@v1
+  #       name: cargo test miri
+  #       with:
+  #         command: miri
+  #         args: test --all-features --manifest-path=compact_str/Cargo.toml --target powerpc-unknown-linux-gnu
 
-  linux_powerpc_le_64bit:
-    name: Linux PowerPC Little Endian 64-bit
-    runs-on: ubuntu-latest
-    steps:
-      - uses: actions/checkout@v2
-        name: Checkout Repo
-      - uses: actions-rs/toolchain@v1
-        name: Install Rust
-        with:
-          toolchain: nightly
-          target: powerpc64le-unknown-linux-gnu
-          override: true
-          components: miri
-      - uses: actions/cache@v3
-        with:
-          path: |
-            ~/.cargo/bin/
-            ~/.cargo/registry/index/
-            ~/.cargo/registry/cache/
-            ~/.cargo/git/db/
-            target/
-          key: ${{ runner.os }}-${{ hashFiles('**/Cargo.lock') }}-x-plat-linux-powerpc-le-64bit-v2
-      - uses: actions-rs/cargo@v1
-        name: cargo test
-        with:
-          use-cross: true
-          command: test
-          args: --release --all-features --manifest-path=compact_str/Cargo.toml --target powerpc64le-unknown-linux-gnu
-      - uses: actions-rs/cargo@v1
-        name: cargo test miri
-        with:
-          command: miri
-          args: test --all-features --manifest-path=compact_str/Cargo.toml --target powerpc64le-unknown-linux-gnu
+  # linux_powerpc_le_64bit:
+  #   name: Linux PowerPC Little Endian 64-bit
+  #   runs-on: ubuntu-latest
+  #   steps:
+  #     - uses: actions/checkout@v3
+  #       name: Checkout Repo
+  #     - uses: actions-rs/toolchain@v1
+  #       name: Install Rust
+  #       with:
+  #         toolchain: nightly
+  #         target: powerpc64le-unknown-linux-gnu
+  #         override: true
+  #         components: miri
+  #     - uses: actions/cache@v3
+  #       with:
+  #         path: |
+  #           ~/.cargo/bin/
+  #           ~/.cargo/registry/index/
+  #           ~/.cargo/registry/cache/
+  #           ~/.cargo/git/db/
+  #           target/
+  #         key: ${{ runner.os }}-${{ hashFiles('**/Cargo.lock') }}-x-plat-linux-powerpc-le-64bit-v2
+  #     - uses: actions-rs/cargo@v1
+  #       name: cargo test
+  #       with:
+  #         use-cross: true
+  #         command: test
+  #         args: --release --all-features --manifest-path=compact_str/Cargo.toml --target powerpc64le-unknown-linux-gnu
+  #     - uses: actions-rs/cargo@v1
+  #       name: cargo test miri
+  #       with:
+  #         command: miri
+  #         args: test --all-features --manifest-path=compact_str/Cargo.toml --target powerpc64le-unknown-linux-gnu
diff --git a/.github/workflows/fuzz.yml b/.github/workflows/fuzz.yml
index 076d525f..ccc5806e 100644
--- a/.github/workflows/fuzz.yml
+++ b/.github/workflows/fuzz.yml
@@ -24,7 +24,7 @@ jobs:
     name: libFuzzer [x86_64]
     runs-on: ubuntu-latest
     steps:
-      - uses: actions/checkout@v2
+      - uses: actions/checkout@v3
         name: Checkout compact_str
       - uses: actions-rs/toolchain@v1
         name: Install Rust
@@ -64,7 +64,7 @@ jobs:
           args: run --features=libfuzzer-sys --debug-assertions compact_str -- -max_total_time=${{ env.fuzz_time }}
       - name: File Issue (if failure found)
         if: failure()
-        uses: JasonEtco/create-an-issue@v2.6
+        uses: JasonEtco/create-an-issue@v2.9
         env:
           GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
         with:
diff --git a/.github/workflows/msrv.yml b/.github/workflows/msrv.yml
index b506604a..e863b14d 100644
--- a/.github/workflows/msrv.yml
+++ b/.github/workflows/msrv.yml
@@ -21,7 +21,7 @@ jobs:
     name: cargo test msrv..
     runs-on: ubuntu-latest
     steps:
-      - uses: actions/checkout@v2
+      - uses: actions/checkout@v3
       - uses: actions-rs/toolchain@v1
         with:
           profile: minimal
@@ -58,13 +58,13 @@ jobs:
         # Instead of pinning to a specific version of `arbitrary` or `proptest`, we'll let user's 
         # deps decide the version since the API should still be semver compatible
         run: |
-          cargo hack check --features bytes,markup,quickcheck,rkyv,serde,smallvec --manifest-path=compact_str/Cargo.toml --version-range 1.57..
+          cargo hack check --features bytes,markup,quickcheck,rkyv,serde,smallvec --manifest-path=compact_str/Cargo.toml --version-range 1.59..
 
   feature_powerset:
     name: cargo check feature-powerset
     runs-on: ubuntu-latest
     steps:
-      - uses: actions/checkout@v2
+      - uses: actions/checkout@v3
       - uses: actions-rs/toolchain@v1
         with:
           profile: minimal
diff --git a/.github/workflows/nostd.yml b/.github/workflows/nostd.yml
new file mode 100644
index 00000000..201e7735
--- /dev/null
+++ b/.github/workflows/nostd.yml
@@ -0,0 +1,28 @@
+name: no-std
+
+on:
+  push:
+    branches:
+      - main
+  pull_request:
+  workflow_dispatch:
+
+jobs:
+  no-std:
+    name: no-std build
+    runs-on: ubuntu-latest
+    strategy:
+      matrix:
+        opt: ["", "--release"]
+    steps:
+      - uses: actions/checkout@v2
+      - uses: actions-rs/toolchain@v1
+        with:
+          profile: minimal
+          toolchain: stable
+          override: true
+      - uses: actions-rs/cargo@v1
+        with:
+          use-cross: true
+          command: build
+          args: --lib --manifest-path compact_str/Cargo.toml --no-default-features ${{ matrix.opt }} --target thumbv6m-none-eabi
diff --git a/CHANGELOG.md b/CHANGELOG.md
index dcfdec2f..e7f9ff61 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,5 @@
 # Upcoming
-... all released!
+* Update MSRV to 1.59
 
 # 0.7.0
 ### February 21, 2023
diff --git a/README.md b/README.md
index d531783a..561eb819 100644
--- a/README.md
+++ b/README.md
@@ -5,7 +5,7 @@
   <a href="https://crates.io/crates/compact_str">
     <img alt="version on crates.io" src="https://img.shields.io/crates/v/compact_str"/>
   </a>
-  <img alt="Minimum supported Rust Version: 1.57" src="https://img.shields.io/badge/MSRV-1.57-blueviolet">
+  <img alt="Minimum supported Rust Version: 1.59" src="https://img.shields.io/badge/MSRV-1.59-blueviolet">
   <a href="LICENSE">
     <img alt="mit license" src="https://img.shields.io/crates/l/compact_str"/>
   </a>
@@ -134,12 +134,13 @@ Specifically, the last byte on the stack for a `CompactString` has the following
 * `[0, 191]` - Is the last byte of a UTF-8 char, the `CompactString` is stored on the stack and implicitly has a length of `24`
 * `[192, 215]` - Denotes a length in the range of `[0, 23]`, this `CompactString` is stored on the stack.
 * `216` - Denotes this `CompactString` is stored on the heap
-* `[217, 255]` - Unused, denotes e.g. the `None` variant for `Option<CompactString>`
+* `217` - Denotes this `CompactString` stores a `&'static str`.
+* `[218, 255]` - Unused, denotes e.g. the `None` variant for `Option<CompactString>`
 
 ### Testing
 Strings and unicode can be quite messy, even further, we're working with things at the bit level. `compact_str` has an _extensive_ test suite comprised of unit testing, property testing, and fuzz testing, to ensure our invariants are upheld. We test across all major OSes (Windows, macOS, and Linux), architectures (64-bit and 32-bit), and endian-ness (big endian and little endian).
 
-Fuzz testing is run with `libFuzzer`, `AFL++`, *and* `honggfuzz`, with `AFL++` running on both `x86_64` and `ARMv7` architectures. We test with [`miri`](https://github.com/rust-lang/miri) to catch cases of undefined behavior, and run all tests on every Rust compiler since `v1.57` to ensure support for our minimum supported Rust version (MSRV).
+Fuzz testing is run with `libFuzzer`, `AFL++`, *and* `honggfuzz`, with `AFL++` running on both `x86_64` and `ARMv7` architectures. We test with [`miri`](https://github.com/rust-lang/miri) to catch cases of undefined behavior, and run all tests on every Rust compiler since `v1.59` to ensure support for our minimum supported Rust version (MSRV).
 
 ### `unsafe` code
 `CompactString` uses a bit of unsafe code because we manually define what variant we are, so unlike an enum, the compiler can't guarantee what value is actually stored.
diff --git a/bench/Cargo.toml b/bench/Cargo.toml
index 870e4740..488a03d9 100644
--- a/bench/Cargo.toml
+++ b/bench/Cargo.toml
@@ -11,7 +11,7 @@ compact_str_6 = { package = "compact_str", version = "0.6" }
 criterion = { version = "0.4", default-features = false }
 iai = "0.1.1"
 smartstring = "1.0.1"
-smol_str = "0.1"
+smol_str = "0.2"
 
 [[bench]]
 name = "apis"
diff --git a/compact_str/Cargo.toml b/compact_str/Cargo.toml
index 1c09b7ef..e85c23bc 100644
--- a/compact_str/Cargo.toml
+++ b/compact_str/Cargo.toml
@@ -11,6 +11,12 @@ readme = "../README.md"
 keywords = ["string", "compact", "small", "memory", "mutable"]
 categories = ["encoding", "parsing", "memory-management", "text-processing"]
 
+[features]
+default = [
+    "std",
+]
+std = []
+
 [dependencies]
 arbitrary = { version = "1", optional = true, default-features = false }
 bytes = { version = "1", optional = true }
@@ -21,12 +27,17 @@ rkyv = { version = "0.7", optional = true, default-features = false, features =
 serde = { version = "1", optional = true }
 smallvec = { version = "1", optional = true, features = ["union"] }
 
-castaway = "0.2"
+# TODO: replace with the below version when castaway is updated on crates.io
+castaway = { version = "0.2", default-features = false }
+# castaway = { version = "0.2", default-features = false, features = ["alloc"] }
+
 cfg-if = "1"
 itoa = "1"
 ryu = "1"
 static_assertions = "1"
 
+rustversion = "1"
+
 [dev-dependencies]
 cfg-if = "1"
 proptest = { version = "1.0.*", default-features = false, features = ["std"] }
@@ -36,7 +47,7 @@ rayon = "1.6.0"
 rkyv = { version = "0.7", default-features = false, features = ["alloc", "size_32"] }
 serde = { version = "1", features = ["derive"] }
 serde_json = "1"
-test-case = "2"
+test-case = "3"
 test-strategy = "0.2"
 
 [package.metadata.docs.rs]
diff --git a/compact_str/src/features/bytes.rs b/compact_str/src/features/bytes.rs
index 617a5a0a..a26b1a7a 100644
--- a/compact_str/src/features/bytes.rs
+++ b/compact_str/src/features/bytes.rs
@@ -69,6 +69,9 @@ impl CompactString {
 
 #[cfg(test)]
 mod test {
+    use alloc::string::String;
+    use alloc::vec::Vec;
+    #[cfg(feature = "std")]
     use std::io::Cursor;
 
     use proptest::prelude::*;
diff --git a/compact_str/src/features/markup.rs b/compact_str/src/features/markup.rs
index 9d3fdc1c..0fd37356 100644
--- a/compact_str/src/features/markup.rs
+++ b/compact_str/src/features/markup.rs
@@ -1,4 +1,5 @@
-use std::fmt;
+#[cfg(test)]
+use alloc::string::String;
 
 use markup::Render;
 
@@ -7,7 +8,7 @@ use crate::CompactString;
 #[cfg_attr(docsrs, doc(cfg(feature = "markup")))]
 impl Render for CompactString {
     #[inline]
-    fn render(&self, writer: &mut impl fmt::Write) -> fmt::Result {
+    fn render(&self, writer: &mut impl core::fmt::Write) -> core::fmt::Result {
         self.as_str().render(writer)
     }
 }
diff --git a/compact_str/src/features/quickcheck.rs b/compact_str/src/features/quickcheck.rs
index 286de025..34caa0cf 100644
--- a/compact_str/src/features/quickcheck.rs
+++ b/compact_str/src/features/quickcheck.rs
@@ -1,5 +1,8 @@
 //! Implements the [`quickcheck::Arbitrary`] trait for [`CompactString`]
 
+use alloc::boxed::Box;
+use alloc::vec::Vec;
+
 use quickcheck::{
     Arbitrary,
     Gen,
@@ -33,6 +36,8 @@ impl Arbitrary for CompactString {
 
 #[cfg(test)]
 mod test {
+    use alloc::string::String;
+
     use quickcheck_macros::quickcheck;
 
     use crate::CompactString;
@@ -47,7 +52,7 @@ mod test {
     #[quickcheck]
     #[cfg_attr(miri, ignore)]
     fn quickcheck_inlines_strings(compact: CompactString) {
-        if compact.len() <= std::mem::size_of::<String>() {
+        if compact.len() <= core::mem::size_of::<String>() {
             assert!(!compact.is_heap_allocated())
         } else {
             assert!(compact.is_heap_allocated())
diff --git a/compact_str/src/features/rkyv.rs b/compact_str/src/features/rkyv.rs
index 22cab001..8dd10682 100644
--- a/compact_str/src/features/rkyv.rs
+++ b/compact_str/src/features/rkyv.rs
@@ -54,13 +54,15 @@ impl PartialEq<CompactString> for ArchivedString {
 
 impl PartialOrd<CompactString> for ArchivedString {
     #[inline]
-    fn partial_cmp(&self, other: &CompactString) -> Option<std::cmp::Ordering> {
+    fn partial_cmp(&self, other: &CompactString) -> Option<core::cmp::Ordering> {
         PartialOrd::partial_cmp(self.as_str(), other.as_str())
     }
 }
 
 #[cfg(test)]
 mod tests {
+    use alloc::string::String;
+
     use rkyv::Deserialize;
     use test_strategy::proptest;
 
diff --git a/compact_str/src/features/serde.rs b/compact_str/src/features/serde.rs
index eb2aa2c6..a78dd820 100644
--- a/compact_str/src/features/serde.rs
+++ b/compact_str/src/features/serde.rs
@@ -1,4 +1,5 @@
-use std::fmt;
+use alloc::string::String;
+use alloc::vec::Vec;
 
 use serde::de::{
     Deserializer,
@@ -17,7 +18,7 @@ fn compact_string<'de: 'a, 'a, D: Deserializer<'de>>(
     impl<'a> Visitor<'a> for CompactStringVisitor {
         type Value = CompactString;
 
-        fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
+        fn expecting(&self, formatter: &mut core::fmt::Formatter) -> core::fmt::Result {
             formatter.write_str("a string")
         }
 
@@ -34,14 +35,14 @@ fn compact_string<'de: 'a, 'a, D: Deserializer<'de>>(
         }
 
         fn visit_bytes<E: Error>(self, v: &[u8]) -> Result<Self::Value, E> {
-            match std::str::from_utf8(v) {
+            match core::str::from_utf8(v) {
                 Ok(s) => Ok(CompactString::from(s)),
                 Err(_) => Err(Error::invalid_value(Unexpected::Bytes(v), &self)),
             }
         }
 
         fn visit_borrowed_bytes<E: Error>(self, v: &'a [u8]) -> Result<Self::Value, E> {
-            match std::str::from_utf8(v) {
+            match core::str::from_utf8(v) {
                 Ok(s) => Ok(CompactString::from(s)),
                 Err(_) => Err(Error::invalid_value(Unexpected::Bytes(v), &self)),
             }
@@ -77,6 +78,12 @@ impl<'de> serde::Deserialize<'de> for CompactString {
 
 #[cfg(test)]
 mod tests {
+    use alloc::string::{
+        String,
+        ToString,
+    };
+    use alloc::vec::Vec;
+
     use serde::{
         Deserialize,
         Serialize,
diff --git a/compact_str/src/features/smallvec.rs b/compact_str/src/features/smallvec.rs
index 118ba04d..128b9304 100644
--- a/compact_str/src/features/smallvec.rs
+++ b/compact_str/src/features/smallvec.rs
@@ -9,8 +9,8 @@ impl CompactString {
     /// This consumes the [`CompactString`] and returns a [`SmallVec`], so we do not need to copy
     /// contents
     ///
-    /// Note: [`SmallVec`] is an inline-able version [`Vec`], just like [`CompactString`] is an
-    /// inline-able version of [`String`].
+    /// Note: [`SmallVec`] is an inline-able version [`Vec`](alloc::vec::Vec), just like
+    /// [`CompactString`] is an inline-able version of [`String`](alloc::string::String).
     ///
     /// # Example
     /// ```
@@ -29,6 +29,8 @@ impl CompactString {
 
 #[cfg(test)]
 mod tests {
+    use alloc::string::String;
+
     use proptest::prelude::*;
     use test_strategy::proptest;
 
diff --git a/compact_str/src/lib.rs b/compact_str/src/lib.rs
index c9707d35..e4cd1ea2 100644
--- a/compact_str/src/lib.rs
+++ b/compact_str/src/lib.rs
@@ -1,6 +1,17 @@
 #![doc = include_str!("../README.md")]
 #![cfg_attr(docsrs, feature(doc_cfg))]
+#![no_std]
 
+#[cfg(feature = "std")]
+#[macro_use]
+extern crate std;
+
+#[cfg_attr(test, macro_use)]
+extern crate alloc;
+
+use alloc::borrow::Cow;
+use alloc::boxed::Box;
+use alloc::string::String;
 #[doc(hidden)]
 pub use core;
 use core::borrow::{
@@ -12,7 +23,10 @@ use core::hash::{
     Hash,
     Hasher,
 };
-use core::iter::FromIterator;
+use core::iter::{
+    FromIterator,
+    FusedIterator,
+};
 use core::ops::{
     Add,
     AddAssign,
@@ -30,9 +44,8 @@ use core::{
     mem,
     slice,
 };
-use std::borrow::Cow;
+#[cfg(feature = "std")]
 use std::ffi::OsStr;
-use std::iter::FusedIterator;
 
 mod features;
 mod macros;
@@ -216,6 +229,37 @@ impl CompactString {
         CompactString(Repr::new_inline(text))
     }
 
+    /// Creates a new inline [`CompactString`] from `&'static str`
+    /// at compile time.
+    ///
+    /// Complexity: O(1)
+    ///
+    /// # Examples
+    /// ```
+    /// use compact_str::CompactString;
+    ///
+    /// const DEFAULT_NAME: CompactString = CompactString::from_static_str("untitled");
+    /// ```
+    #[inline]
+    pub const fn from_static_str(text: &'static str) -> Self {
+        CompactString(Repr::from_static_str(text))
+    }
+
+    /// Get back the `&'static str` constructed by [`CompactString::from_static_str`].
+    ///
+    /// # Examples
+    /// ```
+    /// use compact_str::CompactString;
+    ///
+    /// const DEFAULT_NAME: CompactString = CompactString::from_static_str("untitled");
+    /// assert_eq!(DEFAULT_NAME.as_static_str().unwrap(), "untitled");
+    /// ```
+    #[inline]
+    #[rustversion::attr(since(1.64), const)]
+    pub fn as_static_str(&self) -> Option<&'static str> {
+        self.0.as_static_str()
+    }
+
     /// Creates a new empty [`CompactString`] with the capacity to fit at least `capacity` bytes.
     ///
     /// A `CompactString` will inline strings on the stack, if they're small enough. Specifically,
@@ -396,7 +440,7 @@ impl CompactString {
     pub fn from_utf16_lossy<B: AsRef<[u16]>>(buf: B) -> Self {
         let buf = buf.as_ref();
         let mut ret = CompactString::with_capacity(buf.len());
-        for c in std::char::decode_utf16(buf.iter().copied()) {
+        for c in core::char::decode_utf16(buf.iter().copied()) {
             match c {
                 Ok(c) => ret.push(c),
                 Err(_) => ret.push_str("�"),
@@ -523,7 +567,7 @@ impl CompactString {
     #[inline]
     pub fn as_mut_str(&mut self) -> &mut str {
         let len = self.len();
-        unsafe { std::str::from_utf8_unchecked_mut(&mut self.0.as_mut_buf()[..len]) }
+        unsafe { core::str::from_utf8_unchecked_mut(&mut self.0.as_mut_buf()[..len]) }
     }
 
     unsafe fn spare_capacity_mut(&mut self) -> &mut [mem::MaybeUninit<u8>] {
@@ -960,14 +1004,14 @@ impl CompactString {
         unsafe {
             // first move the tail to the new back
             let data = self.as_mut_ptr();
-            std::ptr::copy(
+            core::ptr::copy(
                 data.add(idx),
                 data.add(idx + string.len()),
                 new_len - idx - string.len(),
             );
 
             // then insert the new bytes
-            std::ptr::copy_nonoverlapping(string.as_ptr(), data.add(idx), string.len());
+            core::ptr::copy_nonoverlapping(string.as_ptr(), data.add(idx), string.len());
 
             // and lastly resize the string
             self.set_len(new_len);
@@ -1020,15 +1064,22 @@ impl CompactString {
     ///
     /// ```
     /// # use compact_str::CompactString;
-    /// let mut s = CompactString::new("Hello, world!");
+    /// let mut s = CompactString::from_static_str("Hello, world!");
     /// assert_eq!(s.split_off(5), ", world!");
     /// assert_eq!(s, "Hello");
     /// ```
     pub fn split_off(&mut self, at: usize) -> Self {
-        let result = self[at..].into();
-        // SAFETY: the previous line `self[at...]` would have panicked if `at` was invalid
-        unsafe { self.set_len(at) };
-        result
+        if let Some(s) = self.as_static_str() {
+            let result = Self::from_static_str(&s[at..]);
+            // SAFETY: the previous line `self[at...]` would have panicked if `at` was invalid
+            unsafe { self.set_len(at) };
+            result
+        } else {
+            let result = self[at..].into();
+            // SAFETY: the previous line `self[at...]` would have panicked if `at` was invalid
+            unsafe { self.set_len(at) };
+            result
+        }
     }
 
     /// Remove a range from the [`CompactString`], and return it as an iterator.
@@ -1292,7 +1343,7 @@ impl CompactString {
         let mut iter = v.iter();
         while let Some(s) = next_char(&mut iter, &mut buf) {
             // SAFETY: next_char() only returns valid strings
-            let s = unsafe { std::str::from_utf8_unchecked(s) };
+            let s = unsafe { core::str::from_utf8_unchecked(s) };
             result.push_str(s);
         }
         result
@@ -1319,7 +1370,7 @@ impl CompactString {
         match unsafe { v.align_to::<u16>() } {
             (&[], v, &[]) => {
                 // Input is correcty aligned.
-                for c in std::char::decode_utf16(v.iter().copied().map(from_int)) {
+                for c in core::char::decode_utf16(v.iter().copied().map(from_int)) {
                     result.push(c.map_err(|_| Utf16Error(()))?);
                 }
             }
@@ -1327,7 +1378,7 @@ impl CompactString {
                 // Input's alignment is off.
                 // SAFETY: we can always reinterpret a `[u8; 2*N]` slice as `[[u8; 2]; N]`
                 let v = unsafe { slice::from_raw_parts(v.as_ptr().cast(), v.len() / 2) };
-                for c in std::char::decode_utf16(v.iter().copied().map(from_bytes)) {
+                for c in core::char::decode_utf16(v.iter().copied().map(from_bytes)) {
                     result.push(c.map_err(|_| Utf16Error(()))?);
                 }
             }
@@ -1355,7 +1406,7 @@ impl CompactString {
         match unsafe { v.align_to::<u16>() } {
             (&[], v, &[]) => {
                 // Input is correcty aligned.
-                for c in std::char::decode_utf16(v.iter().copied().map(from_int)) {
+                for c in core::char::decode_utf16(v.iter().copied().map(from_int)) {
                     match c {
                         Ok(c) => result.push(c),
                         Err(_) => result.push_str("�"),
@@ -1366,7 +1417,7 @@ impl CompactString {
                 // Input's alignment is off.
                 // SAFETY: we can always reinterpret a `[u8; 2*N]` slice as `[[u8; 2]; N]`
                 let v = unsafe { slice::from_raw_parts(v.as_ptr().cast(), v.len() / 2) };
-                for c in std::char::decode_utf16(v.iter().copied().map(from_bytes)) {
+                for c in core::char::decode_utf16(v.iter().copied().map(from_bytes)) {
                     match c {
                         Ok(c) => result.push(c),
                         Err(_) => result.push_str("�"),
@@ -1647,14 +1698,58 @@ impl CompactString {
     ///
     /// assert_eq!(new_year, new_year.to_lowercase());
     /// ```
-    #[must_use = "this returns the lowercase string as a new String, \
+    #[must_use = "this returns the lowercase string as a new CompactString, \
                   without modifying the original"]
     pub fn to_lowercase(&self) -> Self {
-        let mut s = convert_while_ascii(self.as_bytes(), u8::to_ascii_lowercase);
+        Self::from_str_to_lowercase(self.as_str())
+    }
+
+    /// Returns the lowercase equivalent of this string slice, as a new [`CompactString`].
+    ///
+    /// 'Lowercase' is defined according to the terms of the Unicode Derived Core Property
+    /// `Lowercase`.
+    ///
+    /// Since some characters can expand into multiple characters when changing
+    /// the case, this function returns a [`CompactString`] instead of modifying the
+    /// parameter in-place.
+    ///
+    /// # Examples
+    ///
+    /// Basic usage:
+    ///
+    /// ```
+    /// use compact_str::CompactString;
+    ///
+    /// assert_eq!("hello", CompactString::from_str_to_lowercase("HELLO"));
+    /// ```
+    ///
+    /// A tricky example, with sigma:
+    ///
+    /// ```
+    /// use compact_str::CompactString;
+    ///
+    /// assert_eq!("σ", CompactString::from_str_to_lowercase("Σ"));
+    ///
+    /// // but at the end of a word, it's ς, not σ:
+    /// assert_eq!("ὀδυσσεύς", CompactString::from_str_to_lowercase("ὈΔΥΣΣΕΎΣ"));
+    /// ```
+    ///
+    /// Languages without case are not changed:
+    ///
+    /// ```
+    /// use compact_str::CompactString;
+    ///
+    /// let new_year = "农历新年";
+    /// assert_eq!(new_year, CompactString::from_str_to_lowercase(new_year));
+    /// ```
+    #[must_use = "this returns the lowercase string as a new CompactString, \
+                  without modifying the original"]
+    pub fn from_str_to_lowercase(input: &str) -> Self {
+        let mut s = convert_while_ascii(input.as_bytes(), u8::to_ascii_lowercase);
 
         // Safety: we know this is a valid char boundary since
         // out.len() is only progressed if ascii bytes are found
-        let rest = unsafe { self.get_unchecked(s.len()..) };
+        let rest = unsafe { input.get_unchecked(s.len()..) };
 
         for (i, c) in rest.char_indices() {
             if c == 'Σ' {
@@ -1725,14 +1820,54 @@ impl CompactString {
     ///
     /// assert_eq!("TSCHÜSS", s.to_uppercase());
     /// ```
-    #[must_use = "this returns the uppercase string as a new String, \
+    #[must_use = "this returns the uppercase string as a new CompactString, \
                   without modifying the original"]
     pub fn to_uppercase(&self) -> Self {
-        let mut out = convert_while_ascii(self.as_bytes(), u8::to_ascii_uppercase);
+        Self::from_str_to_uppercase(self.as_str())
+    }
+
+    /// Returns the uppercase equivalent of this string slice, as a new [`CompactString`].
+    ///
+    /// 'Uppercase' is defined according to the terms of the Unicode Derived Core Property
+    /// `Uppercase`.
+    ///
+    /// Since some characters can expand into multiple characters when changing
+    /// the case, this function returns a [`CompactString`] instead of modifying the
+    /// parameter in-place.
+    ///
+    /// # Examples
+    ///
+    /// Basic usage:
+    ///
+    /// ```
+    /// use compact_str::CompactString;
+    ///
+    /// assert_eq!("HELLO", CompactString::from_str_to_uppercase("hello"));
+    /// ```
+    ///
+    /// Scripts without case are not changed:
+    ///
+    /// ```
+    /// use compact_str::CompactString;
+    ///
+    /// let new_year = "农历新年";
+    /// assert_eq!(new_year, CompactString::from_str_to_uppercase(new_year));
+    /// ```
+    ///
+    /// One character can become multiple:
+    /// ```
+    /// use compact_str::CompactString;
+    ///
+    /// assert_eq!("TSCHÜSS", CompactString::from_str_to_uppercase("tschüß"));
+    /// ```
+    #[must_use = "this returns the uppercase string as a new CompactString, \
+                  without modifying the original"]
+    pub fn from_str_to_uppercase(input: &str) -> Self {
+        let mut out = convert_while_ascii(input.as_bytes(), u8::to_ascii_uppercase);
 
         // Safety: we know this is a valid char boundary since
         // out.len() is only progressed if ascii bytes are found
-        let rest = unsafe { self.get_unchecked(out.len()..) };
+        let rest = unsafe { input.get_unchecked(out.len()..) };
 
         for c in rest.chars() {
             out.extend(c.to_uppercase());
@@ -1820,6 +1955,7 @@ impl AsRef<str> for CompactString {
     }
 }
 
+#[cfg(feature = "std")]
 impl AsRef<OsStr> for CompactString {
     #[inline]
     fn as_ref(&self) -> &OsStr {
@@ -1940,7 +2076,11 @@ impl From<CompactString> for String {
 impl From<CompactString> for Cow<'_, str> {
     #[inline]
     fn from(s: CompactString) -> Self {
-        Self::Owned(s.into_string())
+        if let Some(s) = s.as_static_str() {
+            Self::Borrowed(s)
+        } else {
+            Self::Owned(s.into_string())
+        }
     }
 }
 
@@ -2106,7 +2246,15 @@ impl fmt::Write for CompactString {
     fn write_fmt(mut self: &mut Self, args: fmt::Arguments<'_>) -> fmt::Result {
         match args.as_str() {
             Some(s) => {
-                self.push_str(s);
+                if self.is_empty() && !self.is_heap_allocated() {
+                    // Since self is currently an empty inline variant or
+                    // an empty `StaticStr` variant, constructing a new one
+                    // with `Self::from_static_str` is more efficient since
+                    // it is guaranteed to be O(1).
+                    *self = Self::from_static_str(s);
+                } else {
+                    self.push_str(s);
+                }
                 Ok(())
             }
             None => fmt::write(&mut self, args),
@@ -2160,7 +2308,7 @@ pub struct Drain<'a> {
     compact_string: *mut CompactString,
     start: usize,
     end: usize,
-    chars: std::str::Chars<'a>,
+    chars: core::str::Chars<'a>,
 }
 
 // SAFETY: Drain keeps the lifetime of the CompactString it belongs to.
diff --git a/compact_str/src/repr/bytes.rs b/compact_str/src/repr/bytes.rs
index 0bab577f..798baeaf 100644
--- a/compact_str/src/repr/bytes.rs
+++ b/compact_str/src/repr/bytes.rs
@@ -81,6 +81,7 @@ impl Repr {
 
 #[cfg(test)]
 mod test {
+    #[cfg(feature = "std")]
     use std::io::Cursor;
 
     use test_case::test_case;
@@ -156,7 +157,7 @@ mod test {
             51, 51, 0, 52, 55, 247, 204, 45, 44, 210, 132, 50, 206, 51,
         ];
         let (front, back) = data.split_at(data.len() / 2 + 1);
-        let mut queue = std::collections::VecDeque::with_capacity(data.len());
+        let mut queue = alloc::collections::VecDeque::with_capacity(data.len());
 
         // create a non-contiguous slice of memory in queue
         front.into_iter().copied().for_each(|x| queue.push_back(x));
diff --git a/compact_str/src/repr/capacity.rs b/compact_str/src/repr/capacity.rs
index 2bff6ebb..ff3d10df 100644
--- a/compact_str/src/repr/capacity.rs
+++ b/compact_str/src/repr/capacity.rs
@@ -1,31 +1,36 @@
-use crate::repr::HEAP_MASK;
+use core::fmt;
+
+use crate::repr::LastUtf8Char;
 
 // how many bytes a `usize` occupies
 const USIZE_SIZE: usize = core::mem::size_of::<usize>();
 
-/// Used to generate [`CAPACITY_IS_ON_THE_HEAP`]
-#[allow(non_snake_case)]
-const fn CAP_ON_HEAP_FLAG() -> [u8; USIZE_SIZE] {
-    // all bytes 255, with the last being HEAP_MASK
-    let mut flag = [255; USIZE_SIZE];
-    flag[USIZE_SIZE - 1] = HEAP_MASK;
-    flag
-}
+/// Mask of bits in [`Capacity`] that encode the value.
+const VALID_MASK: usize = {
+    let mut bytes = [255; USIZE_SIZE];
+    bytes[USIZE_SIZE - 1] = 0;
+    usize::from_ne_bytes(bytes)
+};
+
+/// Mask of bits that are set in [`Capacity`] if the string data is stored on the heap.
+const HEAP_MARKER: usize = {
+    let mut bytes = [0; USIZE_SIZE];
+    bytes[USIZE_SIZE - 1] = LastUtf8Char::Heap as u8;
+    usize::from_ne_bytes(bytes)
+};
 
 /// State that describes the capacity as being stored on the heap.
 ///
-/// All bytes `255`, with the last being [`HEAP_MASK`], using the same amount of bytes as `usize`
-/// Example (64-bit): `[255, 255, 255, 255, 255, 255, 255, 254]`
-const CAPACITY_IS_ON_THE_HEAP: [u8; USIZE_SIZE] = CAP_ON_HEAP_FLAG();
-
-// how many bytes we can use for capacity
-const SPACE_FOR_CAPACITY: usize = USIZE_SIZE - 1;
-// the maximum value we're able to store, e.g. on 64-bit arch this is 2^56 - 2
-//
-// note: Preferably we'd used usize.pow(..) here, but that's not a `const fn`, so we need to use
-// bitshift operators, and there's a lint against using them in this pattern, which IMO isn't a
-// great lint
-pub const MAX_VALUE: usize = 2usize.pow(SPACE_FOR_CAPACITY as u32 * 8) - 2;
+/// All bytes `255`, with the last being [`LastUtf8Char::Heap`], using the same amount of bytes
+/// as `usize`. Example (64-bit): `[255, 255, 255, 255, 255, 255, 255, 216]`
+const CAPACITY_IS_ON_THE_HEAP: Capacity = Capacity(VALID_MASK | HEAP_MARKER);
+
+/// The maximum value we're able to store, e.g. on 64-bit arch this is 2^56 - 2.
+pub const MAX_VALUE: usize = {
+    let mut bytes = [255; USIZE_SIZE];
+    bytes[USIZE_SIZE - 1] = 0;
+    usize::from_le_bytes(bytes) - 1
+};
 
 /// An integer type that uses `core::mem::size_of::<usize>() - 1` bytes to store the capacity of
 /// a heap buffer.
@@ -44,14 +49,19 @@ pub const MAX_VALUE: usize = 2usize.pow(SPACE_FOR_CAPACITY as u32 * 8) - 2;
 /// heap, because with it's impossible to create a string that is 64 petabytes or larger. But for
 /// 32-bit architectures we need to be able to store a capacity larger than 16 megabytes, since a
 /// string larger than 16 megabytes probably isn't that uncommon.
-#[derive(Copy, Clone, Debug, PartialEq, Eq)]
-#[cfg_attr(target_pointer_width = "64", repr(align(8)))]
-#[cfg_attr(target_pointer_width = "32", repr(align(4)))]
-pub struct Capacity([u8; USIZE_SIZE]);
+#[derive(Copy, Clone, PartialEq, Eq)]
+#[repr(transparent)]
+pub struct Capacity(usize);
 
 static_assertions::assert_eq_size!(Capacity, usize);
 static_assertions::assert_eq_align!(Capacity, usize);
 
+impl fmt::Debug for Capacity {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        write!(f, "Capacity(0x{:x})", usize::from_le(self.0))
+    }
+}
+
 impl Capacity {
     #[inline]
     pub const fn new(capacity: usize) -> Self {
@@ -60,21 +70,17 @@ impl Capacity {
                 // on 64-bit arches we can always fit the capacity inline
                 debug_assert!(capacity <= MAX_VALUE);
 
-                let mut bytes = capacity.to_le_bytes();
-                bytes[core::mem::size_of::<usize>() - 1] = HEAP_MASK;
-                Capacity(bytes)
+                Capacity(capacity.to_le() | HEAP_MARKER)
             } else if #[cfg(target_pointer_width = "32")] {
                 // on 32-bit arches we might need to store the capacity on the heap
                 if capacity > MAX_VALUE {
                     // if we need the last byte to encode this capacity then we need to put the capacity on
                     // the heap. return an Error so `BoxString` can do the right thing
-                    Capacity(CAPACITY_IS_ON_THE_HEAP)
+                    CAPACITY_IS_ON_THE_HEAP
                 } else {
-                    // otherwise, we can store this capacity inline! Set the last byte to be our `HEAP_MASK`
+                    // otherwise, we can store this capacity inline! Set the last byte to be our `LastUtf8Char::Heap as u8`
                     // for our discriminant, using the leading bytes to store the actual value
-                    let mut bytes = capacity.to_le_bytes();
-                    bytes[core::mem::size_of::<usize>() - 1] = HEAP_MASK;
-                    Capacity(bytes)
+                    Capacity(capacity.to_le() | HEAP_MARKER)
                 }
             } else {
                 compile_error!("Unsupported target_pointer_width");
@@ -87,21 +93,15 @@ impl Capacity {
     /// # SAFETY:
     /// * `self` must be less than or equal to [`MAX_VALUE`]
     #[inline(always)]
-    pub unsafe fn as_usize(&self) -> usize {
-        let mut usize_buf = [0u8; USIZE_SIZE];
-        // SAFETY:
-        // * `src` is valid for reads of `SPACE_FOR_CAPACITY` because it is less than `USIZE_SIZE`
-        // * `dst` is valid for reads of `SPACE_FOR_CAPACITY` because it is less than `USIZE_SIZE`
-        // * `src` and `dst` do not overlap because we created `usize_buf`
-        core::ptr::copy_nonoverlapping(self.0.as_ptr(), usize_buf.as_mut_ptr(), SPACE_FOR_CAPACITY);
-        usize::from_le_bytes(usize_buf)
+    pub unsafe fn as_usize(self) -> usize {
+        usize::from_le(self.0 & VALID_MASK)
     }
 
     /// Returns whether or not this [`Capacity`] has a value that indicates the capacity is being
     /// stored on the heap
     #[inline(always)]
-    pub fn is_heap(&self) -> bool {
-        self.0 == CAPACITY_IS_ON_THE_HEAP
+    pub fn is_heap(self) -> bool {
+        self == CAPACITY_IS_ON_THE_HEAP
     }
 }
 
@@ -138,7 +138,6 @@ mod tests {
 
     #[cfg(target_pointer_width = "32")]
     #[test]
-
     fn test_invalid_value() {
         let invalid_val = usize::MAX;
         let cap = Capacity::new(invalid_val);
diff --git a/compact_str/src/repr/heap.rs b/compact_str/src/repr/heap.rs
index 31054dc9..04001bbe 100644
--- a/compact_str/src/repr/heap.rs
+++ b/compact_str/src/repr/heap.rs
@@ -148,7 +148,7 @@ impl HeapBuffer {
                 // * `new_size` will be > 0, we return early if the requested capacity is 0
                 // * Checked above if `new_size` overflowed when rounding to alignment
                 match ptr::NonNull::new(unsafe {
-                    std::alloc::realloc(self.ptr.as_ptr(), cur_layout, new_size)
+                    ::alloc::alloc::realloc(self.ptr.as_ptr(), cur_layout, new_size)
                 }) {
                     Some(ptr) => (new_cap, ptr),
                     None => return Err(()),
@@ -178,7 +178,7 @@ impl HeapBuffer {
                 // * The layout is the same because we checked that the capacity is on the heap
                 // * `new_size` will be > 0, we return early if the requested capacity is 0
                 // * Checked above if `new_size` overflowed when rounding to alignment
-                let cap_ptr = unsafe { std::alloc::realloc(adj_ptr, cur_layout, new_size) };
+                let cap_ptr = unsafe { alloc::alloc::realloc(adj_ptr, cur_layout, new_size) };
                 // Check if reallocation succeeded
                 if cap_ptr.is_null() {
                     return Err(());
@@ -331,8 +331,10 @@ pub fn deallocate_ptr(ptr: ptr::NonNull<u8>, cap: Capacity) {
 }
 
 mod heap_capacity {
-    use core::ptr;
-    use std::alloc;
+    use core::{
+        alloc,
+        ptr,
+    };
 
     use super::StrBuffer;
 
@@ -343,13 +345,13 @@ mod heap_capacity {
 
         // SAFETY: `alloc(...)` has undefined behavior if the layout is zero-sized. We know the
         // layout can't be zero-sized though because we're always at least allocating one `usize`
-        let raw_ptr = unsafe { alloc::alloc(layout) };
+        let raw_ptr = unsafe { ::alloc::alloc::alloc(layout) };
 
         // Check to make sure our pointer is non-null, some allocators return null pointers instead
         // of panicking
         match ptr::NonNull::new(raw_ptr) {
             Some(ptr) => ptr,
-            None => alloc::handle_alloc_error(layout),
+            None => ::alloc::alloc::handle_alloc_error(layout),
         }
     }
 
@@ -360,7 +362,7 @@ mod heap_capacity {
     ///   must have `ptr -> [cap<usize> ; string<bytes>]`
     pub unsafe fn dealloc(ptr: ptr::NonNull<u8>, capacity: usize) {
         let layout = layout(capacity);
-        alloc::dealloc(ptr.as_ptr(), layout);
+        ::alloc::alloc::dealloc(ptr.as_ptr(), layout);
     }
 
     #[repr(C)]
@@ -381,8 +383,10 @@ mod heap_capacity {
 }
 
 mod inline_capacity {
-    use core::ptr;
-    use std::alloc;
+    use core::{
+        alloc,
+        ptr,
+    };
 
     use super::StrBuffer;
 
@@ -396,13 +400,13 @@ mod inline_capacity {
         // SAFETY: `alloc(...)` has undefined behavior if the layout is zero-sized. We specify that
         // `capacity` must be > 0 as a constraint to uphold the safety of this method. If capacity
         // is greater than 0, then our layout will be non-zero-sized.
-        let raw_ptr = alloc::alloc(layout);
+        let raw_ptr = ::alloc::alloc::alloc(layout);
 
         // Check to make sure our pointer is non-null, some allocators return null pointers instead
         // of panicking
         match ptr::NonNull::new(raw_ptr) {
             Some(ptr) => ptr,
-            None => alloc::handle_alloc_error(layout),
+            None => ::alloc::alloc::handle_alloc_error(layout),
         }
     }
 
@@ -412,7 +416,7 @@ mod inline_capacity {
     /// * `ptr` must point to the start of a `HeapBuffer` whose capacity is on the inline
     pub unsafe fn dealloc(ptr: ptr::NonNull<u8>, capacity: usize) {
         let layout = layout(capacity);
-        alloc::dealloc(ptr.as_ptr(), layout);
+        ::alloc::alloc::dealloc(ptr.as_ptr(), layout);
     }
 
     #[repr(C)]
diff --git a/compact_str/src/repr/inline.rs b/compact_str/src/repr/inline.rs
index 2aab72b9..94f47bb9 100644
--- a/compact_str/src/repr/inline.rs
+++ b/compact_str/src/repr/inline.rs
@@ -152,6 +152,8 @@ mod tests {
 
     #[cfg(feature = "smallvec")]
     mod smallvec {
+        use alloc::string::String;
+
         use quickcheck_macros::quickcheck;
 
         use crate::repr::{
@@ -172,7 +174,7 @@ mod tests {
             assert!(array[length..].iter().all(|b| *b == 0));
 
             // taking a string slice should give back the same string as the original
-            let ex_s = unsafe { std::str::from_utf8_unchecked(&array[..length]) };
+            let ex_s = unsafe { core::str::from_utf8_unchecked(&array[..length]) };
             assert_eq!(s, ex_s);
         }
 
@@ -196,7 +198,7 @@ mod tests {
             assert!(array[length..].iter().all(|b| *b == 0));
 
             // taking a string slice should give back the same string as the original
-            let ex_s = unsafe { std::str::from_utf8_unchecked(&array[..length]) };
+            let ex_s = unsafe { core::str::from_utf8_unchecked(&array[..length]) };
             assert_eq!(s, ex_s);
         }
     }
diff --git a/compact_str/src/repr/iter.rs b/compact_str/src/repr/iter.rs
index 38ca6bcd..e0f2ebd3 100644
--- a/compact_str/src/repr/iter.rs
+++ b/compact_str/src/repr/iter.rs
@@ -1,7 +1,9 @@
 //! Implementations of the [`FromIterator`] trait to make building [`Repr`]s more ergonomic
 
+use alloc::borrow::Cow;
+use alloc::boxed::Box;
+use alloc::string::String;
 use core::iter::FromIterator;
-use std::borrow::Cow;
 
 use super::{
     InlineBuffer,
@@ -139,6 +141,8 @@ impl<'a> FromIterator<Cow<'a, str>> for Repr {
 
 #[cfg(test)]
 mod tests {
+    use alloc::string::String;
+
     use super::Repr;
 
     #[test]
diff --git a/compact_str/src/repr/last_utf8_char.rs b/compact_str/src/repr/last_utf8_char.rs
index 903227a7..fc28aeea 100644
--- a/compact_str/src/repr/last_utf8_char.rs
+++ b/compact_str/src/repr/last_utf8_char.rs
@@ -1,3 +1,5 @@
+use alloc::string::String;
+
 /// [`LastUtf8Char`] is an unsigned 8-bit integer data type that has a valid range of `[0, 216]`.
 /// Excluding `[217, 255]` allows the Rust compiler to use these values as niches.
 ///
@@ -236,7 +238,8 @@ pub enum LastUtf8Char {
     L23 = 215,
 
     Heap = 216,
+    Static = 217,
 }
 
 static_assertions::assert_eq_size!(LastUtf8Char, Option<LastUtf8Char>, u8);
-static_assertions::const_assert!(std::mem::size_of::<String>() <= 24);
+static_assertions::const_assert!(core::mem::size_of::<String>() <= 24);
diff --git a/compact_str/src/repr/mod.rs b/compact_str/src/repr/mod.rs
index e65b412e..88865ee5 100644
--- a/compact_str/src/repr/mod.rs
+++ b/compact_str/src/repr/mod.rs
@@ -1,9 +1,10 @@
+use alloc::borrow::Cow;
+use alloc::boxed::Box;
 use core::str::Utf8Error;
 use core::{
     mem,
     ptr,
 };
-use std::borrow::Cow;
 
 #[cfg(feature = "bytes")]
 mod bytes;
@@ -16,21 +17,27 @@ mod inline;
 mod iter;
 mod last_utf8_char;
 mod num;
+mod static_str;
 mod traits;
 
+use alloc::string::String;
+
 use capacity::Capacity;
 use heap::HeapBuffer;
 use inline::InlineBuffer;
 use last_utf8_char::LastUtf8Char;
+use static_str::StaticStr;
 pub use traits::IntoRepr;
 
 /// The max size of a string we can fit inline
-pub const MAX_SIZE: usize = std::mem::size_of::<String>();
+pub const MAX_SIZE: usize = core::mem::size_of::<String>();
 /// Used as a discriminant to identify different variants
 pub const HEAP_MASK: u8 = LastUtf8Char::Heap as u8;
+/// Used for `StaticStr` variant
+pub const STATIC_STR_MASK: u8 = LastUtf8Char::Static as u8;
 /// When our string is stored inline, we represent the length of the string in the last byte, offset
 /// by `LENGTH_MASK`
-pub const LENGTH_MASK: u8 = LastUtf8Char::L0 as u8;
+pub const LENGTH_MASK: u8 = 0b11000000;
 
 const EMPTY: Repr = Repr::new_inline("");
 
@@ -47,6 +54,7 @@ pub struct Repr(
     // ...so that the last byte can be a NonMax, which allows the compiler to see a niche value
     LastUtf8Char,
 );
+static_assertions::assert_eq_size!([u8; MAX_SIZE], Repr);
 
 unsafe impl Send for Repr {}
 unsafe impl Sync for Repr {}
@@ -76,10 +84,17 @@ impl Repr {
             let inline = InlineBuffer::new_const(text);
             Repr::from_inline(inline)
         } else {
-            panic!("Inline string was too long, max length is `std::mem::size_of::<CompactString>()` bytes");
+            panic!("Inline string was too long, max length is `core::mem::size_of::<CompactString>()` bytes");
         }
     }
 
+    #[inline]
+    pub const fn from_static_str(text: &'static str) -> Self {
+        let repr = StaticStr::new(text);
+        // SAFETY: A `StaticStr` and `Repr` have the same size
+        unsafe { core::mem::transmute(repr) }
+    }
+
     /// Create a [`Repr`] with the provided `capacity`
     #[inline]
     pub fn with_capacity(capacity: usize) -> Self {
@@ -186,8 +201,6 @@ impl Repr {
     /// Converts a [`Repr`] into a [`String`], in `O(1)` time, if possible
     #[inline]
     pub fn into_string(self) -> String {
-        let last_byte = self.last_byte();
-
         #[cold]
         fn into_string_heap(this: HeapBuffer) -> String {
             // SAFETY: We know pointer is valid for `length` bytes
@@ -198,7 +211,7 @@ impl Repr {
             String::from(s)
         }
 
-        if last_byte == HEAP_MASK {
+        if self.is_heap_allocated() {
             // SAFTEY: we just checked that the discriminant indicates we're a HeapBuffer
             let heap_buffer = unsafe { self.into_heap() };
 
@@ -221,15 +234,7 @@ impl Repr {
                 unsafe { String::from_raw_parts(this.ptr.as_ptr(), this.len, cap) }
             }
         } else {
-            let pointer = &self as *const _ as *const u8;
-            let length = core::cmp::min((last_byte.wrapping_sub(LENGTH_MASK)) as usize, MAX_SIZE);
-
-            // SAFETY: We know pointer is valid for `length` bytes
-            let slice = unsafe { core::slice::from_raw_parts(pointer, length) };
-            // SAFETY: A `Repr` contains valid UTF-8
-            let s = unsafe { core::str::from_utf8_unchecked(slice) };
-
-            String::from(s)
+            String::from(self.as_str())
         }
     }
 
@@ -242,8 +247,10 @@ impl Repr {
             .checked_add(additional)
             .expect("Attempted to reserve more than 'usize' bytes");
 
-        if needed_capacity < self.capacity() {
+        if !self.is_static_str() && needed_capacity <= self.capacity() {
             // we already have enough space, no-op
+            // If self.is_static_str() is true, then we would have to convert
+            // it to other variants since static_str variant cannot be modified.
             return;
         }
 
@@ -276,11 +283,10 @@ impl Repr {
     }
 
     pub fn shrink_to(&mut self, min_capacity: usize) {
-        let last_byte = self.last_byte();
-
-        // Note: We can't shrink the inline variant since it's buffer is a fixed size, so we only
+        // Note: We can't shrink the inline variant since it's buffer is a fixed size
+        // or the static str variant since it is just a pointer, so we only
         // take action here if our string is heap allocated
-        if last_byte == HEAP_MASK {
+        if self.is_heap_allocated() {
             // SAFETY: We just checked the discriminant to make sure we're heap allocated
             let heap = unsafe { self.as_mut_heap() };
 
@@ -311,6 +317,12 @@ impl Repr {
 
     #[inline]
     pub fn push_str(&mut self, s: &str) {
+        // If `s` is empty, then there's no reason to reserve or push anything
+        // at all.
+        if s.is_empty() {
+            return;
+        }
+
         let len = self.len();
         let str_len = s.len();
 
@@ -361,7 +373,7 @@ impl Repr {
         //
         // Note: We should never add an `else` statement here, keeping the conditional simple allows
         // the compiler to optimize this to a conditional-move instead of a branch
-        if last_byte == HEAP_MASK {
+        if last_byte == HEAP_MASK || last_byte == STATIC_STR_MASK {
             pointer = heap_pointer;
             length = heap_length;
         }
@@ -381,31 +393,45 @@ impl Repr {
     #[allow(clippy::len_without_is_empty)] // is_empty exists on CompactString
     #[inline]
     pub fn len(&self) -> usize {
-        // the last byte stores our discriminant and stack length
+        // This ugly looking code results in two conditional moves and only one comparison, without
+        // branching. The outcome of a comparison is a tristate `{lt, eq, gt}`, but the compiler
+        // won't use this optimization if you match on `len_inline.cmp(&MAX_SIZE)`, so we have to
+        // do it manually.
+
+        // Force the compiler to read the variable, so it won't put the reading in a branch.
+        let len_heap = self.1;
+        // SAFETY: This assembly instruction is a noop that only affects the instruction ordering.
+        #[cfg(not(miri))]
+        unsafe {
+            std::arch::asm!(
+                "/* {len_heap} */",
+                len_heap = in(reg) len_heap,
+                options(nomem, nostack),
+            );
+        };
+
         let last_byte = self.last_byte();
 
-        // initially has the value of the stack length, conditionally becomes the heap length
-        let mut length = core::cmp::min((last_byte.wrapping_sub(LENGTH_MASK)) as usize, MAX_SIZE);
-        let heap_length = self.1;
-        let length_ref = &mut length;
+        // Extending the variable early results in fewer instructions, because loading and
+        // extending can be done in one instruction.
+        let mut len = (last_byte as usize)
+            .wrapping_sub(LENGTH_MASK as usize)
+            .min(MAX_SIZE);
 
         // our discriminant is stored in the last byte and denotes stack vs heap
         //
         // Note: We should never add an `else` statement here, keeping the conditional simple allows
         // the compiler to optimize this to a conditional-move instead of a branch
-        if last_byte == HEAP_MASK {
-            *length_ref = heap_length;
+        if last_byte == HEAP_MASK || last_byte == STATIC_STR_MASK {
+            len = len_heap;
         }
 
-        *length_ref
+        len
     }
 
     /// Returns the overall capacity of the underlying buffer
     #[inline]
     pub fn capacity(&self) -> usize {
-        // the last byte stores our discriminant and stack length
-        let last_byte = self.last_byte();
-
         #[cold]
         fn heap_capacity(this: &Repr) -> usize {
             // SAFETY: We just checked the discriminant to make sure we're heap allocated
@@ -413,7 +439,9 @@ impl Repr {
             heap_buffer.capacity()
         }
 
-        if last_byte == HEAP_MASK {
+        if let Some(s) = self.as_static_str() {
+            s.len()
+        } else if self.is_heap_allocated() {
             heap_capacity(self)
         } else {
             MAX_SIZE
@@ -426,11 +454,44 @@ impl Repr {
         last_byte == HEAP_MASK
     }
 
+    #[inline(always)]
+    const fn is_static_str(&self) -> bool {
+        let last_byte = self.last_byte();
+        last_byte == STATIC_STR_MASK
+    }
+
+    #[inline]
+    #[rustversion::attr(since(1.64), const)]
+    pub fn as_static_str(&self) -> Option<&'static str> {
+        if self.is_static_str() {
+            // SAFETY: A `Repr` is transmuted from `StaticStr`
+            let s: &StaticStr = unsafe { &*(self as *const Self as *const StaticStr) };
+            Some(s.get_text())
+        } else {
+            None
+        }
+    }
+
+    #[inline]
+    fn as_static_variant_mut(&mut self) -> Option<&mut StaticStr> {
+        if self.is_static_str() {
+            // SAFETY: A `Repr` is transmuted from `StaticStr`
+            let s: &mut StaticStr = unsafe { &mut *(self as *mut Self as *mut StaticStr) };
+            Some(s)
+        } else {
+            None
+        }
+    }
+
     /// Return a mutable reference to the entirely underlying buffer
     ///
     /// # Safety
     /// * Callers must guarantee that any modifications made to the buffer are valid UTF-8
     pub unsafe fn as_mut_buf(&mut self) -> &mut [u8] {
+        if let Some(s) = self.as_static_str() {
+            *self = Repr::new(s);
+        }
+
         // the last byte stores our discriminant and stack length
         let last_byte = self.last_byte();
 
@@ -456,9 +517,9 @@ impl Repr {
     /// * `len` bytes in the buffer must be valid UTF-8
     /// * If the underlying buffer is stored inline, `len` must be <= MAX_SIZE
     pub unsafe fn set_len(&mut self, len: usize) {
-        let last_byte = self.last_byte();
-
-        if last_byte == HEAP_MASK {
+        if let Some(s) = self.as_static_variant_mut() {
+            s.set_len(len);
+        } else if self.is_heap_allocated() {
             // SAFETY: We just checked the discriminant to make sure we're heap allocated
             let heap_buffer = self.as_mut_heap();
             // SAFETY: The caller guarantees that `len` bytes is valid UTF-8
@@ -623,8 +684,9 @@ impl Clone for Repr {
                 Repr::from_heap(new)
             }
         }
-
-        if last_byte == HEAP_MASK {
+        if let Some(s) = self.as_static_str() {
+            Self::from_static_str(s)
+        } else if last_byte == HEAP_MASK {
             clone_heap(self)
         } else {
             // SAFETY: We checked above that the discriminant indicates we're inline
@@ -700,6 +762,12 @@ impl Extend<String> for Repr {
 
 #[cfg(test)]
 mod tests {
+    use alloc::string::{
+        String,
+        ToString,
+    };
+    use alloc::vec::Vec;
+
     use quickcheck_macros::quickcheck;
     use test_case::test_case;
 
@@ -709,8 +777,7 @@ mod tests {
     };
 
     const EIGHTEEN_MB: usize = 18 * 1024 * 1024;
-    const EIGHTEEN_MB_STR: &'static str =
-        unsafe { core::str::from_utf8_unchecked(&[42; EIGHTEEN_MB]) };
+    const EIGHTEEN_MB_STR: &str = unsafe { core::str::from_utf8_unchecked(&[42; EIGHTEEN_MB]) };
 
     #[test_case("hello world!"; "inline")]
     #[test_case("this is a long string that should be stored on the heap"; "heap")]
@@ -718,6 +785,11 @@ mod tests {
         let repr = Repr::new(s);
         assert_eq!(repr.as_str(), s);
         assert_eq!(repr.len(), s.len());
+
+        // test StaticStr variant
+        let repr = Repr::from_static_str(s);
+        assert_eq!(repr.as_str(), s);
+        assert_eq!(repr.len(), s.len());
     }
 
     #[quickcheck]
@@ -826,6 +898,13 @@ mod tests {
 
         assert_eq!(control.len(), s.len());
         assert_eq!(control, s.as_str());
+
+        // test StaticStr variant
+        let r = Repr::from_static_str(control);
+        let s = r.into_string();
+
+        assert_eq!(control.len(), s.len());
+        assert_eq!(control, s.as_str());
     }
 
     #[quickcheck]
@@ -853,6 +932,18 @@ mod tests {
         assert_eq!(r.len(), c.len());
 
         assert_eq!(r.is_heap_allocated(), is_heap);
+
+        // test StaticStr variant
+        let mut r = Repr::from_static_str(control);
+        let mut c = String::from(control);
+
+        r.push_str(append);
+        c.push_str(append);
+
+        assert_eq!(r.as_str(), c.as_str());
+        assert_eq!(r.len(), c.len());
+
+        assert_eq!(r.is_heap_allocated(), is_heap);
     }
 
     #[quickcheck]
@@ -905,6 +996,13 @@ mod tests {
 
         assert!(r.capacity() >= initial.len() + additional);
         assert_eq!(r.is_heap_allocated(), is_heap);
+
+        // Test static_str variant
+        let mut r = Repr::from_static_str(initial);
+        r.reserve(additional);
+
+        assert!(r.capacity() >= initial.len() + additional);
+        assert_eq!(r.is_heap_allocated(), is_heap);
     }
 
     #[test]
@@ -929,6 +1027,18 @@ mod tests {
         assert_eq!(r_a.len(), r_b.len());
         assert_eq!(r_a.capacity(), r_b.capacity());
         assert_eq!(r_a.is_heap_allocated(), r_b.is_heap_allocated());
+
+        // test StaticStr variant
+        let r_a = Repr::from_static_str(initial);
+        let r_b = r_a.clone();
+
+        assert_eq!(r_a.as_str(), initial);
+        assert_eq!(r_a.len(), initial.len());
+
+        assert_eq!(r_a.as_str(), r_b.as_str());
+        assert_eq!(r_a.len(), r_b.len());
+        assert_eq!(r_a.capacity(), r_b.capacity());
+        assert_eq!(r_a.is_heap_allocated(), r_b.is_heap_allocated());
     }
 
     #[quickcheck]
diff --git a/compact_str/src/repr/num.rs b/compact_str/src/repr/num.rs
index c2baa13d..af49f769 100644
--- a/compact_str/src/repr/num.rs
+++ b/compact_str/src/repr/num.rs
@@ -418,6 +418,8 @@ impl NumChars for isize {
 
 #[cfg(test)]
 mod tests {
+    use alloc::string::ToString;
+
     use super::IntoRepr;
 
     #[test]
diff --git a/compact_str/src/repr/smallvec.rs b/compact_str/src/repr/smallvec.rs
index f1538866..5f42641d 100644
--- a/compact_str/src/repr/smallvec.rs
+++ b/compact_str/src/repr/smallvec.rs
@@ -2,7 +2,6 @@ use smallvec::SmallVec;
 
 use super::{
     Repr,
-    HEAP_MASK,
     MAX_SIZE,
 };
 
@@ -12,9 +11,9 @@ impl Repr {
     /// Note: both for the inlined case and the heap case, the buffers are re-used
     #[inline]
     pub fn into_bytes(self) -> SmallVec<[u8; MAX_SIZE]> {
-        let last_byte = self.last_byte();
-
-        if last_byte == HEAP_MASK {
+        if let Some(s) = self.as_static_str() {
+            SmallVec::from(s.as_bytes())
+        } else if self.is_heap_allocated() {
             let string = self.into_string();
             let bytes = string.into_bytes();
             SmallVec::from_vec(bytes)
@@ -44,5 +43,14 @@ mod tests {
 
         let ex_compact = CompactString::from_utf8(bytes).unwrap();
         assert_eq!(ex_compact, s);
+
+        // test `StaticStr` variant
+        let og_compact = CompactString::from_static_str(s);
+        assert_eq!(og_compact, s);
+
+        let bytes = og_compact.into_bytes();
+
+        let ex_compact = CompactString::from_utf8(bytes).unwrap();
+        assert_eq!(ex_compact, s);
     }
 }
diff --git a/compact_str/src/repr/static_str.rs b/compact_str/src/repr/static_str.rs
new file mode 100644
index 00000000..404cfd9b
--- /dev/null
+++ b/compact_str/src/repr/static_str.rs
@@ -0,0 +1,56 @@
+use core::{
+    mem,
+    ptr,
+    slice,
+    str,
+};
+
+use super::{
+    Repr,
+    MAX_SIZE,
+    STATIC_STR_MASK,
+};
+
+pub(super) const DISCRIMINANT_SIZE: usize = MAX_SIZE - mem::size_of::<&'static str>();
+
+/// A buffer stored on the stack whose size is equal to the stack size of `String`
+/// The last byte is set to 0.
+#[derive(Copy, Clone)]
+#[repr(C)]
+pub struct StaticStr {
+    ptr: ptr::NonNull<u8>,
+    len: usize,
+    #[allow(unused)]
+    discriminant: [u8; DISCRIMINANT_SIZE],
+}
+static_assertions::assert_eq_size!(StaticStr, Repr);
+static_assertions::assert_eq_size!(&'static str, (*const u8, usize));
+
+impl StaticStr {
+    #[inline]
+    pub const fn new(text: &'static str) -> Self {
+        let mut discriminant = [0; DISCRIMINANT_SIZE];
+        discriminant[DISCRIMINANT_SIZE - 1] = STATIC_STR_MASK;
+
+        Self {
+            // SAFETY: `&'static str` must have a non-null, properly aligned
+            // address
+            ptr: unsafe { ptr::NonNull::new_unchecked(text.as_ptr() as *mut _) },
+            len: text.len(),
+            discriminant,
+        }
+    }
+
+    #[rustversion::attr(since(1.64), const)]
+    pub(super) fn get_text(&self) -> &'static str {
+        // SAFETY: `StaticStr` invariants requires it to be a valid str
+        unsafe { str::from_utf8_unchecked(slice::from_raw_parts(self.ptr.as_ptr(), self.len)) }
+    }
+
+    /// # Safety
+    /// * `len` bytes in the buffer must be valid UTF-8 and
+    /// * `len` must be <= `self.get_text().len()`
+    pub(super) unsafe fn set_len(&mut self, len: usize) {
+        *self = Self::new(&self.get_text()[..len]);
+    }
+}
diff --git a/compact_str/src/repr/traits.rs b/compact_str/src/repr/traits.rs
index b75b91cf..28b7b591 100644
--- a/compact_str/src/repr/traits.rs
+++ b/compact_str/src/repr/traits.rs
@@ -43,6 +43,8 @@ impl IntoRepr for char {
 
 #[cfg(test)]
 mod tests {
+    use alloc::string::ToString;
+
     use quickcheck_macros::quickcheck;
 
     use super::IntoRepr;
diff --git a/compact_str/src/traits.rs b/compact_str/src/traits.rs
index 20e273c7..b579a431 100644
--- a/compact_str/src/traits.rs
+++ b/compact_str/src/traits.rs
@@ -1,3 +1,4 @@
+use alloc::string::String;
 use core::fmt::{
     self,
     Write,
@@ -192,6 +193,11 @@ where
 
 #[cfg(test)]
 mod tests {
+    use alloc::string::{
+        String,
+        ToString,
+    };
+    use alloc::vec::Vec;
     use core::num;
 
     use proptest::prelude::*;
diff --git a/compact_str/src/unicode_data.rs b/compact_str/src/unicode_data.rs
index 1393b4fc..7fe99aba 100644
--- a/compact_str/src/unicode_data.rs
+++ b/compact_str/src/unicode_data.rs
@@ -8,7 +8,7 @@ fn skip_search<const SOR: usize, const OFFSETS: usize>(
     offsets: &[u8; OFFSETS],
 ) -> bool {
     // Note that this *cannot* be past the end of the array, as the last
-    // element is greater than std::char::MAX (the largest possible needle).
+    // element is greater than char::MAX (the largest possible needle).
     //
     // So, we cannot have found it (i.e. Ok(idx) + 1 != length) and the correct
     // location cannot be past it, so Err(idx) != length either.


Test patch:
diff --git a/compact_str/src/features/proptest.rs b/compact_str/src/features/proptest.rs
index d756fd61..d1cfd1fe 100644
--- a/compact_str/src/features/proptest.rs
+++ b/compact_str/src/features/proptest.rs
@@ -1,5 +1,7 @@
 //! Implements the [`proptest::arbitrary::Arbitrary`] trait for [`CompactString`]
 
+use alloc::string::String;
+
 use proptest::arbitrary::{
     Arbitrary,
     StrategyFor,
@@ -25,11 +27,13 @@ impl Arbitrary for CompactString {
 
 #[cfg(test)]
 mod test {
+    use alloc::string::String;
+
     use proptest::prelude::*;
 
     use crate::CompactString;
 
-    const MAX_SIZE: usize = std::mem::size_of::<String>();
+    const MAX_SIZE: usize = core::mem::size_of::<String>();
 
     proptest! {
         #[test]
diff --git a/compact_str/src/tests.rs b/compact_str/src/tests.rs
index 757acd40..20ac7c5e 100644
--- a/compact_str/src/tests.rs
+++ b/compact_str/src/tests.rs
@@ -1,11 +1,20 @@
-use core::slice;
-use std::borrow::Cow;
-use std::num;
-use std::str::FromStr;
+use alloc::borrow::Cow;
+use alloc::boxed::Box;
+use alloc::string::{
+    String,
+    ToString,
+};
+use alloc::vec::Vec;
+use core::str::FromStr;
+use core::{
+    num,
+    slice,
+};
 
 use proptest::collection::SizeRange;
 use proptest::prelude::*;
 use proptest::strategy::Strategy;
+use test_case::test_case;
 use test_strategy::proptest;
 
 use crate::{
@@ -307,9 +316,9 @@ fn proptest_from_utf8_unchecked(#[strategy(rand_bytes())] bytes: Vec<u8>) {
 
     // check if we were valid UTF-8, if so, assert the data written into the CompactString is
     // correct
-    let data_is_valid = std::str::from_utf8(&bytes);
-    let compact_is_valid = std::str::from_utf8(compact.as_bytes());
-    let std_str_is_valid = std::str::from_utf8(std_str.as_bytes());
+    let data_is_valid = core::str::from_utf8(&bytes);
+    let compact_is_valid = core::str::from_utf8(compact.as_bytes());
+    let std_str_is_valid = core::str::from_utf8(std_str.as_bytes());
 
     match (data_is_valid, compact_is_valid, std_str_is_valid) {
         (Ok(d), Ok(c), Ok(s)) => {
@@ -375,18 +384,34 @@ fn test_const_creation() {
     const EMPTY: CompactString = CompactString::new_inline("");
     const SHORT: CompactString = CompactString::new_inline("rust");
 
+    const EMPTY_STATIC_STR: CompactString = CompactString::from_static_str("");
+    const SHORT_STATIC_STR: CompactString = CompactString::from_static_str("rust");
+
     #[cfg(target_pointer_width = "64")]
     const PACKED: CompactString = CompactString::new_inline("i am 24 characters long!");
     #[cfg(target_pointer_width = "32")]
     const PACKED: CompactString = CompactString::new_inline("i am 12 char");
 
+    const PACKED_STATIC_STR0: CompactString =
+        CompactString::from_static_str("i am 24 characters long!");
+    const PACKED_STATIC_STR1: CompactString = CompactString::from_static_str("i am 12 char");
+
     assert_eq!(EMPTY, CompactString::new(""));
     assert_eq!(SHORT, CompactString::new("rust"));
 
+    assert_eq!(EMPTY_STATIC_STR, CompactString::new(""));
+    assert_eq!(SHORT_STATIC_STR, CompactString::new("rust"));
+
     #[cfg(target_pointer_width = "64")]
     assert_eq!(PACKED, CompactString::new("i am 24 characters long!"));
     #[cfg(target_pointer_width = "32")]
     assert_eq!(PACKED, CompactString::new("i am 12 char"));
+
+    assert_eq!(
+        PACKED_STATIC_STR0,
+        CompactString::new("i am 24 characters long!")
+    );
+    assert_eq!(PACKED_STATIC_STR1, CompactString::new("i am 12 char"));
 }
 
 #[test]
@@ -478,7 +503,6 @@ fn test_from_str_trait() {
 #[cfg_attr(target_pointer_width = "32", ignore)]
 fn test_from_char_iter() {
     let s = "\u{0} 0 \u{0}a𐀀𐀀 𐀀a𐀀";
-    println!("{}", s.len());
     let compact: CompactString = s.chars().into_iter().collect();
 
     assert!(!compact.is_heap_allocated());
@@ -500,11 +524,10 @@ fn test_extend_packed_from_empty() {
     assert!(!compact.is_heap_allocated());
 }
 
-#[test]
-fn test_pop_empty() {
+#[test_case(CompactString::from(""); "inline")]
+#[test_case(CompactString::from_static_str(""); "static_str")]
+fn test_pop_empty(mut compact: CompactString) {
     let num_pops = 256;
-    let mut compact = CompactString::from("");
-
     (0..num_pops).for_each(|_| {
         let ch = compact.pop();
         assert!(ch.is_none());
@@ -532,12 +555,11 @@ fn test_compact_str_is_send_and_sync() {
     is_send_and_sync::<CompactString>();
 }
 
-#[test]
-fn test_fmt_write() {
+#[test_case(CompactString::default(); "inline")]
+#[test_case(CompactString::from_static_str(""); "static_str")]
+fn test_fmt_write(mut compact: CompactString) {
     use core::fmt::Write;
 
-    let mut compact = CompactString::default();
-
     write!(compact, "test").unwrap();
     assert_eq!(compact, "test");
 
@@ -575,6 +597,42 @@ fn test_plus_operator() {
     assert_eq!(String::from("a") + &"b", "ab");
 }
 
+#[test]
+fn test_plus_operator_static_str() {
+    // + &CompactString
+    assert_eq!(
+        CompactString::from_static_str("a") + &CompactString::from_static_str("b"),
+        "ab"
+    );
+    // + &str
+    assert_eq!(CompactString::from_static_str("a") + "b", "ab");
+    // + &String
+    assert_eq!(
+        CompactString::from_static_str("a") + &String::from("b"),
+        "ab"
+    );
+    // + &Box<str>
+    let box_str = String::from("b").into_boxed_str();
+    assert_eq!(CompactString::from_static_str("a") + &box_str, "ab");
+    // + &Cow<'a, str>
+    let cow = Cow::from("b");
+    assert_eq!(CompactString::from_static_str("a") + &cow, "ab");
+
+    // Implementing `Add<T> for String` can break adding &String or other types to String, so we
+    // explicitly don't do this. See https://github.com/rust-lang/rust/issues/77143 for more details.
+    // Below we assert adding types to String still compiles
+
+    // String + &CompactString
+    assert_eq!(
+        String::from("a") + &CompactString::from_static_str("b"),
+        "ab"
+    );
+    // String + &String
+    assert_eq!(String::from("a") + &("b".to_string()), "ab");
+    // String + &str
+    assert_eq!(String::from("a") + &"b", "ab");
+}
+
 #[test]
 fn test_plus_equals_operator() {
     let mut m = CompactString::from("a");
@@ -582,6 +640,13 @@ fn test_plus_equals_operator() {
     assert_eq!(m, "ab");
 }
 
+#[test]
+fn test_plus_equals_operator_static_str() {
+    let mut m = CompactString::from_static_str("a");
+    m += "b";
+    assert_eq!(m, "ab");
+}
+
 #[test]
 fn test_u8_to_compact_string() {
     let vals = [u8::MIN, 1, 42, u8::MAX - 2, u8::MAX - 1, u8::MAX];
@@ -1083,6 +1148,23 @@ fn test_into_string_small_str() {
     assert_eq!(str_len, new_str_cap);
 }
 
+#[test]
+fn test_into_string_small_static_str() {
+    let data = "abcdef";
+    let str_addr = data.as_ptr();
+    let str_len = data.len();
+
+    let compact = CompactString::from_static_str(data);
+    let new_string = String::from(compact);
+    let new_str_addr = new_string.as_ptr();
+    let new_str_len = new_string.len();
+    let new_str_cap = new_string.capacity();
+
+    assert_ne!(str_addr, new_str_addr);
+    assert_eq!(str_len, new_str_len);
+    assert_eq!(str_len, new_str_cap);
+}
+
 #[test]
 fn test_into_string_long_str() {
     let data = "this is a long string that will be on the heap";
@@ -1100,6 +1182,23 @@ fn test_into_string_long_str() {
     assert_eq!(str_len, new_str_cap);
 }
 
+#[test]
+fn test_into_string_long_static_str() {
+    let data = "this is a long string that will be on the heap";
+    let str_addr = data.as_ptr();
+    let str_len = data.len();
+
+    let compact = CompactString::from_static_str(data);
+    let new_string = String::from(compact);
+    let new_str_addr = new_string.as_ptr();
+    let new_str_len = new_string.len();
+    let new_str_cap = new_string.capacity();
+
+    assert_ne!(str_addr, new_str_addr);
+    assert_eq!(str_len, new_str_len);
+    assert_eq!(str_len, new_str_cap);
+}
+
 #[test]
 fn test_into_string_empty_str() {
     let data = "";
@@ -1116,6 +1215,22 @@ fn test_into_string_empty_str() {
     assert_eq!(str_len, new_str_cap);
 }
 
+#[test]
+fn test_into_string_empty_static_str() {
+    let data = "";
+    let str_len = data.len();
+
+    let compact = CompactString::from_static_str(data);
+    let new_string = String::from(compact);
+    let new_str_addr = new_string.as_ptr();
+    let new_str_len = new_string.len();
+    let new_str_cap = new_string.capacity();
+
+    assert_eq!(String::new().as_ptr(), new_str_addr);
+    assert_eq!(str_len, new_str_len);
+    assert_eq!(str_len, new_str_cap);
+}
+
 #[test]
 fn test_truncate_noops_if_new_len_greater_than_current() {
     let mut short = CompactString::from("short");
@@ -1133,6 +1248,22 @@ fn test_truncate_noops_if_new_len_greater_than_current() {
     assert_eq!(long.capacity(), long_cap);
 }
 
+#[test]
+fn test_truncate_noops_if_new_len_greater_than_current_static_str() {
+    let mut short = CompactString::from_static_str("short");
+    short.truncate(100);
+
+    assert_eq!(short.len(), 5);
+    assert_eq!(short.capacity(), 5);
+
+    let mut long =
+        CompactString::from_static_str("i am a long string that will be allocated on the heap");
+    long.truncate(500);
+
+    assert_eq!(long.len(), 53);
+    assert_eq!(long.capacity(), 53);
+}
+
 #[test]
 #[should_panic(expected = "new_len must lie on char boundary")]
 fn test_truncate_panics_on_non_char_boundary() {
@@ -1141,78 +1272,79 @@ fn test_truncate_panics_on_non_char_boundary() {
     emojis.truncate(1);
 }
 
-#[test]
-fn test_insert() {
+#[test_case(CompactString::from; "inline")]
+#[test_case(CompactString::from_static_str; "static_str")]
+fn test_insert(to_compact: fn(&'static str) -> CompactString) {
     // insert into empty string
-    let mut one_byte = CompactString::from("");
+    let mut one_byte = to_compact("");
     one_byte.insert(0, '.');
     assert_eq!(one_byte, ".");
 
-    let mut two_bytes = CompactString::from("");
+    let mut two_bytes = to_compact("");
     two_bytes.insert(0, 'Ü');
     assert_eq!(two_bytes, "Ü");
 
-    let mut three_bytes = CompactString::from("");
+    let mut three_bytes = to_compact("");
     three_bytes.insert(0, '€');
     assert_eq!(three_bytes, "€");
 
-    let mut four_bytes = CompactString::from("");
+    let mut four_bytes = to_compact("");
     four_bytes.insert(0, '😀');
     assert_eq!(four_bytes, "😀");
 
     // insert at the front of string
-    let mut one_byte = CompactString::from("😀");
+    let mut one_byte = to_compact("😀");
     one_byte.insert(0, '.');
     assert_eq!(one_byte, ".😀");
 
-    let mut two_bytes = CompactString::from("😀");
+    let mut two_bytes = to_compact("😀");
     two_bytes.insert(0, 'Ü');
     assert_eq!(two_bytes, "Ü😀");
 
-    let mut three_bytes = CompactString::from("😀");
+    let mut three_bytes = to_compact("😀");
     three_bytes.insert(0, '€');
     assert_eq!(three_bytes, "€😀");
 
-    let mut four_bytes = CompactString::from("😀");
+    let mut four_bytes = to_compact("😀");
     four_bytes.insert(0, '😀');
     assert_eq!(four_bytes, "😀😀");
 
     // insert at the end of string
-    let mut one_byte = CompactString::from("😀");
+    let mut one_byte = to_compact("😀");
     one_byte.insert(4, '.');
     assert_eq!(one_byte, "😀.");
 
-    let mut two_bytes = CompactString::from("😀");
+    let mut two_bytes = to_compact("😀");
     two_bytes.insert(4, 'Ü');
     assert_eq!(two_bytes, "😀Ü");
 
-    let mut three_bytes = CompactString::from("😀");
+    let mut three_bytes = to_compact("😀");
     three_bytes.insert(4, '€');
     assert_eq!(three_bytes, "😀€");
 
-    let mut four_bytes = CompactString::from("😀");
+    let mut four_bytes = to_compact("😀");
     four_bytes.insert(4, '😀');
     assert_eq!(four_bytes, "😀😀");
 
     // insert in the middle of string
-    let mut one_byte = CompactString::from("😀😀");
+    let mut one_byte = to_compact("😀😀");
     one_byte.insert(4, '.');
     assert_eq!(one_byte, "😀.😀");
 
-    let mut two_bytes = CompactString::from("😀😀");
+    let mut two_bytes = to_compact("😀😀");
     two_bytes.insert(4, 'Ü');
     assert_eq!(two_bytes, "😀Ü😀");
 
-    let mut three_bytes = CompactString::from("😀😀");
+    let mut three_bytes = to_compact("😀😀");
     three_bytes.insert(4, '€');
     assert_eq!(three_bytes, "😀€😀");
 
-    let mut four_bytes = CompactString::from("😀😀");
+    let mut four_bytes = to_compact("😀😀");
     four_bytes.insert(4, '😀');
     assert_eq!(four_bytes, "😀😀😀");
 
     // edge case: new length is 24 bytes
-    let mut s = CompactString::from("\u{ffff}\u{ffff}\u{ffff}\u{ffff}\u{ffff}\u{ffff}\u{ffff}");
+    let mut s = to_compact("\u{ffff}\u{ffff}\u{ffff}\u{ffff}\u{ffff}\u{ffff}\u{ffff}");
     s.insert(21, '\u{ffff}');
     assert_eq!(
         s,
@@ -1246,6 +1378,13 @@ fn test_remove_empty_string() {
     compact.remove(0);
 }
 
+#[test]
+#[should_panic(expected = "cannot remove a char from the end of a string")]
+fn test_remove_empty_string_static() {
+    let mut compact = CompactString::from_static_str("");
+    compact.remove(0);
+}
+
 #[test]
 #[should_panic(expected = "cannot remove a char from the end of a string")]
 fn test_remove_str_len() {
@@ -1349,6 +1488,51 @@ fn test_reserve_shrink_roundtrip() {
     assert_eq!(s, TEXT);
 }
 
+#[test]
+fn test_reserve_shrink_roundtrip_static() {
+    const TEXT: &str = "Hello.";
+
+    let mut s = CompactString::from_static_str(TEXT);
+    assert!(!s.is_heap_allocated());
+    assert_eq!(s.capacity(), s.len());
+    assert_eq!(s, TEXT);
+
+    s.reserve(128);
+    assert!(s.is_heap_allocated());
+    assert!(s.capacity() >= 128 + TEXT.len());
+    assert_eq!(s, TEXT);
+
+    s.shrink_to(64);
+    assert!(s.is_heap_allocated());
+    assert!(s.capacity() >= 64);
+    assert_eq!(s, TEXT);
+
+    s.shrink_to_fit();
+    assert!(!s.is_heap_allocated());
+    assert_eq!(s.capacity(), MAX_SIZE);
+    assert_eq!(s, TEXT);
+
+    s.reserve(SIXTEEN_MB);
+    assert!(s.is_heap_allocated());
+    assert!(s.capacity() >= SIXTEEN_MB + TEXT.len());
+    assert_eq!(s, TEXT);
+
+    s.shrink_to(64);
+    assert!(s.is_heap_allocated());
+    assert!(s.capacity() >= 64);
+    assert_eq!(s, TEXT);
+
+    s.reserve(SIXTEEN_MB);
+    assert!(s.is_heap_allocated());
+    assert!(s.capacity() >= SIXTEEN_MB + TEXT.len());
+    assert_eq!(s, TEXT);
+
+    s.shrink_to_fit();
+    assert!(!s.is_heap_allocated());
+    assert_eq!(s.capacity(), MAX_SIZE);
+    assert_eq!(s, TEXT);
+}
+
 #[test]
 fn test_from_utf8_unchecked_sanity() {
     let text = "hello 🌎, you are nice";
@@ -1556,7 +1740,7 @@ fn test_collect() {
 fn test_into_cow() {
     let og = "aaa";
     let compact = CompactString::new(og);
-    let cow: std::borrow::Cow<'_, str> = compact.into();
+    let cow: alloc::borrow::Cow<'_, str> = compact.into();
 
     assert_eq!(og, cow);
 }
@@ -1591,5 +1775,8 @@ fn multiple_nieches_test() {
         Unsigned(usize),
         Null,
     }
-    assert_eq!(std::mem::size_of::<Value>(), std::mem::size_of::<String>());
+    assert_eq!(
+        core::mem::size_of::<Value>(),
+        core::mem::size_of::<String>()
+    );
 }