How pslr compares to other PSL libraries

The Public Suffix List website catalogs implementations in C, C#, C++, Go, Haskell, Java, JavaScript, Perl, PHP, Python, Ruby, Rust, Swift, and more — but no R. pslr fills that gap, and it is built to be a reproducibility- and correctness-first engine rather than a quick suffix splitter.

The table compares pslr with a representative set of the most established libraries from that catalog, across the dimensions that matter for correct, auditable suffix handling. ✅ first-class · ◐ partial/limited · ⚠ with a caveat · ❌ absent.

Library (language) Full algorithm (*/!) ICANN / PRIVATE / both IDN + Punycode Offline default + explicit refresh Queryable provenance Compiled core Strict input validation Unlisted-TLD policy configurable
pslr (R) ✅ ✅ 3-way ✅ in & out ✅ bundled; validated HTTPS-only psl_refresh() ✅ list and normalization identity ✅ cpp11 ✅ rejects URLs, IPv4, IPv6 ✅ unknown=
libpsl (C) ✅ ✅ 3-way ✅ ✅ bundled; OS-package update ◐ sha1 + mtime ✅ ◐ ✅
x/net/publicsuffix (Go) ✅ ◐ icann flag ❌ ASCII only ✅ embedded; bump module ◐ date constant ✅ ◐ ❌
publicsuffix-go (Go) ✅ ◐ toggle private ✅ ✅ embedded ❌ ❌ ◐ ✅
psl crate (Rust) ✅ ◐ per-suffix type ✅ ✅ compiled-in ◐ dated releases ✅ ❌ ◐
tldextract (Python) ✅ ◐ private on/off ✅ ⚠ network-first; offline opt-in ❌ ❌ ◐ ❌
publicsuffixlist (Python) ✅ ◐ exclude private ✅ ✅ bundled + updater ◐ date in version ❌ ❌ ❌
public_suffix (Ruby) ✅ ◐ ignore_private ❌ caller pre-encodes ✅ bundled ❌ ❌ ◐ ✅
php-domain-parser (PHP) ✅ ✅ 3-way ✅ ⚠ not bundled; PSR-16 cache ◐ isKnown flags ❌ ◐ ◐
tldts (JS/TS) ✅ ◐ allowPrivateDomains ✅ ✅ embedded; npm bump ◐ submodule pin ◐ optional WASM ✅ ◐
Guava InternetDomainName (Java) ◐ ✅ registry vs public ✅ ◐ in-jar; bump Guava ❌ ❌ ◐ ❌

What pslr does differently

  • Provenance is best-in-class. psl_version() records the list identity (source, commit, date, SHA-256) and the normalization identity (normalizer package + version, profile, Unicode version). A PSL answer depends on both which list answered and how the host was normalized — pslr is the only surveyed library that surfaces both, so a result is genuinely reproducible.
  • Conformance is verified. The package ships the official upstream tests.txt vectors, pinned in lockstep with the bundled snapshot and run on every check.
  • Offline by default, one hardened network path. Every query works with zero network access; psl_refresh() is the only code that touches the network — HTTPS-only, credential- and downgrade-rejecting, size-capped, conditional when it can be, published append-only. This is stricter than network-first designs and pairs with the reproducibility story rather than fighting it.
  • Freshness reported honestly. psl_status() separates “a check is due” from “a newer list was actually observed”, and never infers the second from snapshot age. Surveyed libraries report a date or nothing at all.
  • Snapshots can be diffed. Because every retained download is an immutable, checksummed artifact, psl_diff() reports what actually changed between two snapshots — canonically, offline, keyed on each rule’s logical identity so a wildcard/exception change or an ICANN/PRIVATE move reads as one change rather than an unrelated removal and addition. No surveyed library offers this.
  • Strict, policy-driven validation. URLs, IPv6, dotted-decimal IPv4, and malformed labels are rejected; invalid = "na" / "error" lets you choose silent NA or a hard stop. Many libraries are deliberately lenient.
  • Vectorized, NA-safe, name-preserving. Every function operates on a whole character vector — the idiomatic shape for data work in R.

Trade-offs

  • Session-global default list. Queries read the per-session active list (psl_use() / psl_refresh()) unless a call passes engine =, a psl_engine() built for one specific snapshot.
  • Hostnames, not URLs. URL-shaped input is rejected by design; parse the host out first or use rurl.
  • No network-first auto-fetch. Refreshing is always explicit — a deliberate choice for reproducibility, not a convenience feature.