Punycode/IDN libraries exist in most ecosystems.
punycoder is most directly a maintained, IDNA2008-era
successor to the libidn-based R tooling — its public API
(puny_encode() / puny_decode() /
is_punycode()) descends from hrbrmstr/punycode.
The table below situates it against representative libraries.
| punycoder (R) | hrbrmstr/punycode (R) | punycoder (Dart) | simonmittag/punycoder (Go) | |
|---|---|---|---|---|
| Form | library | library | library | CLI tool |
| RFC 3492 codec | yes | yes | yes | yes |
| Engine | libidn2 + in-tree fallback |
GNU libidn |
pure Dart | Go x/net/idna |
| IDNA standard | 2008 / UTS #46 (non-transitional) | 2003 (nameprep) | RFC 3492 + IDNA helpers | UTS #46 (via x/net) |
| Unicode NFC | explicit (UAX #15) | implicit in nameprep | not documented | via x/net |
| Pinned Unicode version | yes — 17.0.0, regenerable; 16.0.0 also selectable | no (frozen at build) | no | tracks Go release |
| CheckBidi / CheckJoiners | always on | not surfaced | not documented | partial |
UTS #46 conformance corpus (IdnaTestV2) |
yes — one per shipped Unicode version | no | no | — |
Strict / NA per-element policy |
yes | undocumented | validate flag |
n/a (CLI) |
| Vectorized | yes | yes | n/a | n/a |
| Maintenance | active | last commit 2015 | maintained | maintained |
The most consequential row is IDNA standard. IDNA2003 (GNU
libidn, nameprep) and IDNA2008 / UTS #46 disagree on real domains: the deviation charactersß,ς, and the joiners ZWJ/ZWNJ. Under IDNA2003faß.deis mapped tofass.de— a different host — whereaspunycoder’s pinned UTS #46 non-transitional profile preserves it asxn--fa-hia.de. A libidn-era pipeline therefore silently rewrites some hosts rather than erroring, which is the class of bugpunycoderexists to remove.
Comparisons reflect each project’s public documentation as of this writing and describe documented behavior, not an independent audit.
Running the same inputs through the comparable R packages surfaces
concrete behavioral differences (observed against punycode
0.2.5, urltools 1.7.3.1, and the author’s own upstack
toolkit rurl
1.4.0). The raw RFC 3492 codec output agrees byte-for-byte across the
codecs once direction is aligned — the divergences are in multi-label
handling, idempotency, validity philosophy, and input scope.
rurl is a URL parser/normalizer rather than a Punycode
codec; it is included to show where the URL-shaped inputs
punycoder deliberately rejects are actually handled (it
delegates IDNA host conversion to punycoder), so
— below means “out of scope for that layer,” not a
defect:
| Behavior | punycoder | hrbrmstr/punycode | urltools | rurl |
|---|---|---|---|---|
| Primary role | Punycode/IDNA host codec | Punycode codec (IDNA2003) | URL + punycode utilities | URL parser / normalizer |
puny_encode() direction |
Unicode → ASCII | ASCII → Unicode (names inverted) | Unicode → ASCII | — (no codec; IDNA via punycoder) |
Decode multi-label xn--hxakfddc2amo8b.xn--qxam |
ελράδειγμα.ελ ✓ |
ελράδειγμα.ελ ✓ |
ελράδειγμα.ελράδειγμα ✗ (second label corrupted) |
— (no xn-- → Unicode decoder) |
Re-encode an already-xn-- label |
unchanged — idempotent ✓ | unchanged ✓ | xn--xn--…-.xn--xn--…- ✗ (double-encoded) |
— |
Round-trip decode(encode(x)) == x |
yes | yes | no (from the decode bug above) | — |
gr€€n.no — EURO SIGN, valid under UTS #46 |
accepted → xn--grn-l50aa.no |
rejected by puny_tld_check (IDNA2008) |
— | parses; host preserved |
Full-URL input (http://…) |
rejected with an actionable error pointing at a URL parser
(rurl) |
n/a (domain-only) | passed through unchanged | parsed — scheme/host/domain/TLD extracted;
get_clean_url() lowercases the host and resolves
dot-segments |
| Required system library | none (libidn2 optional) |
GNU libidn (v1) required to build |
none | none |
punycodenames its functions opposite to the usual convention:punycode::puny_encode()mapsxn--→ Unicode andpunycode::puny_decode()maps Unicode →xn--. The rows above align by transform direction, not by function name.
punycoder+rurl(+pslrfor the public-suffix/TLD truth) are designed to compose:rurlparses the URL and hands the host topunycoderfor IDNA canonicalization, each package owning a single concern.