sorug is an ultra-high-performance, zero-copy, WHATWG URL Living Standard-compliant URL parser written in Rust. It targets production parsers that need correctness and nanosecond-scale throughput — and currently outperforms both servo/rust-url and ada-url on the hot paths that matter.
| Pillar | What it means |
|---|---|
| Zero-Copy | Canonical ASCII inputs stay borrowed (Backing::Borrowed); heap allocation only on first required mutation (CoW). |
| SIMD / SWAR | 64-bit scheme-prefix jumps + SWAR delimiter scans for short inputs; memchr for longer buffers. |
| Custom Punycode | In-crate Punycode + UTS #46 mapping; membership tables from vendored Unicode UCD (data/ucd/) + idna_overlay.txt — no ICU/idna runtime dep. |
| 891 / 891 WPT | Full pass of the Web Platform Tests urltestdata suite shipped in-tree. |
| 278 / 278 setters | Full pass of WPT setters_tests.json (component mutators). |
forbid(unsafe_code) |
Zero unsafe in library code. Correctness first; speed without memory-safety shortcuts. |
no_std + alloc |
Embedded / WASM friendly (default-features = false). |
Criterion, Linux, release profile (lto = true, codegen-units = 1). Lower is better (nanoseconds / parse). Measured 2026-08-07 (0.5/0.6 prep; Fast Path ASCII now leads ada).
| Workload | sorug | ada-url | servo/url |
|---|---|---|---|
Fast Path ASCII (https://example.com/api/v1/users) |
28.3 ns | 31.9 ns | 97 ns |
| Complex Query / Fragment | 56.0 ns | 140 ns | 216 ns |
| IDNA / Punycode | 196 ns | 277 ns | 248 ns |
| File Edge Case | 31.1 ns | 95.0 ns | 129 ns |
Reproduce locally:
cargo bench --bench url_benchmarkNumbers are indicative. Absolute values vary by CPU; relative ordering is what we track. IDNA uses an in-tree UTS #46 path with a Latin-1/CJK/kana/Hangul identity fast path; membership tables are built from vendored Unicode UCD (no ICU/
idnacrate).
cargo add sorug[dependencies]
sorug = "0.6"use sorug::Url;
fn main() -> Result<(), sorug::ParseError> {
let url = Url::parse("https://example.com/path?q=1#frag")?;
assert_eq!(url.scheme(), "https");
assert_eq!(url.host(), Some("example.com"));
assert_eq!(url.as_str(), "https://example.com/path?q=1#frag");
assert_eq!(url.origin().serialized(), "https://example.com");
Ok(())
}Relative resolution with join / make_relative:
use sorug::Url;
let base = Url::parse("https://example.com/dir/page")?;
let joined = base.join("../other")?;
assert_eq!(joined.as_str(), "https://example.com/other");
let target = Url::parse("https://example.com/dir/x")?;
assert_eq!(base.make_relative(&target).as_deref(), Some("x"));Mutate components (WHATWG / WPT setters) and edit the query as form-urlencoded pairs:
use sorug::{SearchParams, Url};
let mut url = Url::parse("https://example.com/old")?;
url.set_pathname("/api/v1");
url.set_search("?q=1");
assert_eq!(url.href(), "https://example.com/api/v1?q=1");
let mut params = SearchParams::parse("q=1");
params.append("lang", "tr");
url.set_search_params(¶ms);
assert_eq!(url.search(), "?q=1&lang=tr");| Feature | Default | Purpose |
|---|---|---|
std |
yes | [std::error::Error] for ParseError; memchr std backend |
serde |
no | Serialize / deserialize Url as an href string |
http |
no | Convert between Url and http::Uri (implies std) |
# no_std + alloc
sorug = { version = "0.6", default-features = false }
# with serde
sorug = { version = "0.6", features = ["serde"] }
# with http::Uri bridge
sorug = { version = "0.6", features = ["http"] }http feature example:
use http::Uri;
use sorug::Url;
let url = Url::parse("https://example.com/api").unwrap();
let uri: Uri = url.to_uri().unwrap();
let back = sorug::uri_to_url(&uri).unwrap();
assert_eq!(back.as_str(), "https://example.com/api");Git dependency (tracking main):
[dependencies]
sorug = { git = "https://github.com/hocestnonsatis/sorug" }Today (0.6.2)
- Relative URL ops:
join/make_relative/path_segments/path_segments_mut/query_pairs(_mut). - Typed
Host(+Host::parse), rust-url-shaped getters,Hash/Ord, optionalserde/http,no_std+alloc. - File paths, unique opaque origins,
set_ip_host/socket_addrs,SearchParams(incl. value-awarehas/delete/size),parse_with_params. - IDNA: in-tree Punycode + UTS #46; membership tables from vendored Unicode UCD 17.0.0 +
data/idna_overlay.txt(Node/WPT). - WPT parser: 891 / 891; WPT setters: 278 / 278 (harness covers
relativeTo). - Docs: docs.rs/sorug; recipes in docs/cookbook.md.
- 0.6.2:
PathSegmentsMutfile Windows drive|→:(fuzz-long); broader idempotent setters; DX integration tests.
Breaking (0.3 → 0.4)
Origin::Opaqueis nowOpaque(OpaqueOrigin)with unique nonces — distinct opaque origins no longer compare equal.
Next (toward 1.0 — reliability clock, not feature work)
- No new public API and no new performance work until 1.0; only bug fix, WPT freshness, fuzz triage, oracle allowlist hygiene (intervention rules: docs/1.0-gate.md).
- Gate: ≥30 consecutive days green daily fuzz-smoke + ≥8 consecutive weeks green weekly long fuzz, plus current WPT 100% — see docs/1.0-gate.md.
- Keep fixtures current via
scripts/refresh-wpt.sh; differential fuzz vs rust-url with documented divergences. - Unicode UCD refresh when a new major is ready (
./scripts/refresh-ucd.sh); see data/ucd/README.md. - After 1.0: migration/shim → WASM/JS → N-API (not before the cut).
- 0.x: Breaking changes allowed in minor bumps (
0.N → 0.N+1) when called out in CHANGELOG.md. Additive APIs may land in patch or minor. - MSRV: Declared in
Cargo.tomlasrust-version(currently 1.85). MSRV bumps are minor in 0.x (documented in the changelog); CI verifies the declared toolchain. - Features:
std(default),serde,http(impliesstd). Disablingstdis supported (no_std+alloc). - FFI:
sorug-ffiis not on crates.io; ABI may change with the workspace version. Prefer GitHub Release binaries pinned to a tag.
Do not cut 1.0 until reliability gates hold. 1.0 = trust proof, not a perf/feature finish line. Details: docs/1.0-gate.md, docs/api-audit.md.
- Public surface audit signed off:
Url<'a>lifetime,Backingstays public/advanced,Statestaysdoc(hidden),ParseErrorstays two variants — docs/api-audit.md (2026-08-07). - rust-url migration notes complete (
hostvshost_parsed, port setters, origins, lifetimes) — see docs/cookbook.md. - WPT parser + setters 100% on current fixtures; ≥30d daily fuzz-smoke + ≥8w weekly long fuzz green; no serious
wpt-freshnessregressions (ordinary upstream test adds do not reset the clock). - CHANGELOG + docs.rs + Trusted Publishing ready for the freeze cut; FFI stays
publish = false— verify again at freeze time.
Not goals for 1.0: historical non-WHATWG quirk parity; trading forbid(unsafe_code) for micro-wins; adding ICU/idna crates; expanding ParseError; hiding Backing; URLPattern / RFC3986 modes / unsafe escape hatches.
C FFI
Optional C bindings live in ffi/ (sorug-ffi, workspace member, not on crates.io). Prebuilt cdylib / staticlib + sorug.h ship on GitHub Releases. The main crate stays forbid(unsafe_code). See ffi/README.md.
Not goals (for now)
- Matching every historical quirk of non-WHATWG parsers.
- Trading
forbid(unsafe_code)for micro-wins.
- Index-based record — component boundaries are
u32offsets into the WHATWGhrefserialization. - Lazy / CoW serialization — borrow when input is already canonical; upgrade to owned on mutation.
- Strict state machine — transitions follow the URL Living Standard basic URL parser.
cargo test # unit + integration (incl. WPT + comprehensive validation)
cargo test --all-features # includes serde / http
cargo test --workspace # includes sorug-ffi
cargo test --test api_maturity # Hash/Ord, getters, query_pairs, set_port
cargo test --test path_segments_mut # PathSegmentsMut vs rust-url
cargo check --no-default-features # no_std + alloc
cargo test --test wpt # WPT urltestdata only
cargo test --test wpt_setters # WPT setters_tests only
cargo bench # Criterion vs ada-url and servo/urlSee CONTRIBUTING.md for policies, style, and review expectations.
Integration snippets and rust-url migration: docs/cookbook.md. Known rust-url differentials: docs/differential-allowlist.md. 1.0 freeze gate (no rush): docs/1.0-gate.md.
Licensed under either of
at your option.
Participation is governed by our Code of Conduct.