# pct
[](https://crates.io/crates/pct)
[](https://docs.rs/pct)
[](https://crates.io/crates/pct)
[](#license)
A small, allocation-conscious Rust crate for percent-encoded strings used in URLs, URIs, IRIs, etc. — parse, validate, encode, decode, compare.
```rust
use pct::{PctStr, PctString, UriReserved};
let s = PctStr::new("Hello%20World%21")?;
assert_eq!(s, "Hello World!");
assert_eq!(s.decode(), "Hello World!");
let encoded = PctString::encode("Hello World!".chars(), UriReserved::Any);
assert_eq!(encoded.as_str(), "Hello%20World%21");
```
Pick an `Encoder` impl (`UriReserved`, `IriReserved`) or write your own:
```rust
use pct::{Encoder, PctString, UriReserved};
struct Upper;
impl Encoder for Upper {
fn encode(&self, c: char) -> bool {
UriReserved::Any.encode(c) || c.is_uppercase()
}
}
let s = PctString::encode("Hello World!".chars(), Upper);
assert_eq!(s.as_str(), "%48ello%20%57orld%21");
```
---
## Why this fork
Fork of [`pct-str`](https://crates.io/crates/pct-str) by [Timothée Haudebourg](https://github.com/timothee-haudebourg/pct-str). Public API and RFC behavior unchanged; this fork adds:
- SWAR plain-run scanner and portable-SIMD path (nightly, gated on `simd`) for validate / decode / encode.
- Hex-decode lookup tables replacing per-nibble branches.
- Byte-level fast paths for `new`, `decode`, `encode_bytes`, `eq`, `ord`, `hash`, `len`.
- `memchr`-accelerated `%` scan (default-on).
- Criterion bench suite under `benches/`.
- Rust 2024 edition, MSRV 1.85.
- Renamed crate to `pct`.
Credit and history preserved — see [Attribution](#attribution).
## Install
```sh
cargo add pct
```
## Feature flags
| `std` | yes | `std::error::Error` impls, owned `PctString`, `String` APIs |
| `memchr` | yes | `memchr`-accelerated `%` scan in validate / decode / encode |
| `simd` | | Portable-SIMD plain-run scanner. **Requires nightly rustc.** |
For `no_std`, disable default features. You get `PctStr` (borrowed, zero-alloc) and the `Encoder` trait. Re-enable `std` for `PctString` and `String`-returning APIs.
## Streaming decode
`PctStr::chars()` and `PctStr::bytes()` are lazy iterators over the decoded form. No intermediate `String`, works under `no_std`:
```rust
let s = pct::PctStr::new("caf%C3%A9")?;
for ch in s.chars() { /* 'c', 'a', 'f', 'é' */ }
```
For encoding from a known `&str`, prefer `PctString::encode_bytes` — it skips UTF-8 re-iteration and hits the SWAR/SIMD scanner directly.
## Equivalence
Equality, ordering, and hashing compare the **decoded** bytes — `PctStr::new("%41") == "A"`, and hex case (`%2f` vs `%2F`) is irrelevant. Keep this in mind when using `PctStr` / `PctString` as map keys: two values with different `as_str()` can hash equal.
## Examples
See `examples/` for runnable end-to-end usage:
```sh
cargo run --example encode
cargo run --example str
cargo run --example string
```
## Benchmarks
```sh
cargo bench
cargo +nightly bench --features simd
```
Criterion output: `target/criterion/`.
## MSRV
Rust 1.85 (edition 2024). The `simd` feature additionally requires nightly.
## Attribution
Original crate: [`pct-str`](https://crates.io/crates/pct-str) by [Timothée Haudebourg](https://github.com/timothee-haudebourg/pct-str). Upstream commits are preserved in this repo's history under their original authorship. This fork is a thin layer of performance and ergonomics work on top of their design.
## License
Dual-licensed, same as upstream. Pick whichever fits:
- [Apache-2.0](https://github.com/mskvarc/pct/blob/master/LICENSE-APACHE.md)
- [MIT](https://github.com/mskvarc/pct/blob/master/LICENSE-MIT.md)