Rand Stats

Prompt::Hidden

zef:ash

Prompt::Hidden

prompt with a :hidden adverb — a password typed at a terminal that the terminal never shows.

0.0.1. The interface below is implemented and tested on both engines: 30 assertions across four files, green on Rakudo 2026.08 and Raku++ 3.26.0. The echo suppression itself is verified separately under a pseudo-terminal, since a pipe has no echo to suppress — see Compatibility. What is deliberately left out is in Scope.

use Prompt::Hidden;

my $user = prompt "Username: ";
my $pass = prompt "Password: ", :hidden;

say "Hello, $user ({$pass.chars} characters)";
say Prompt::Hidden::prompt-backend;   # 'core' on Raku++, 'stty' on Rakudo/Unix
raku -I lib example.raku
rakupp -I lib example.raku

The same file runs on every Raku. What differs is who suppresses the echo.

Why it exists

Raku has prompt, and it echoes. Reading a password means reaching past it: Terminal::Getpass shells out to stty, and everyone else writes the same half-dozen lines again. None of that is prompt, so a program wanting one visible field and one hidden one ends up with two different calls, two signatures, and two return types.

Here the hidden read is an adverb on the call you already write. prompt without :hidden is CORE::<&prompt> — the whole capture is forwarded, so the message, the allomorph return and the Nil at end of input are the core ones, not an imitation. Only :hidden is new.

The adverb belongs to this module, not to any engine. prompt("pw: ", :hidden) without use Prompt::Hidden is an error everywhere, Raku++ included — core prompt has two signatures, () and ($msg), and takes no named arguments on any of them. That is deliberate: an engine that quietly accepted the adverb would mint a dialect, and the program would run on one Raku and die on the next. What Raku++ supplies is the capability, as the rakupp-prompt-hidden primitive this module probes for; the spelling is the module's, so the same source means the same thing everywhere.

What it exports

One name, and it is the one you came for:

exportwhat it does
prompt($message?, :hidden)the core prompt, plus the adverb

Prompt::Hidden::prompt-backend'core', 'stty' or 'msvcrt', whichever did the reading — is not exported. It is introspection, worth having and not worth a bare name in every importer's scope, so it is spelled in full.

There is no import list. With one export there is nothing to select, and a list that could only ever name prompt would be a second spelling of the default — so one is refused rather than accepted and ignored, because the one thing an import list must never do is swallow a typo written beside it.

The three backends

backendwhenhow
coreRaku++ with rakupp-prompt-hiddenthe engine's own unechoed read
sttyany other Unix Rakustty -g to save, stty -echo, restored in a LEAVE
msvcrtWindows without the primitive_getch, which returns a key unechoed

The module picks by probing, in that order. The Windows half lives in Prompt::Hidden::Win32 and is loaded only on Windows: use NativeCall costs about 70 ms on Rakudo, and no Unix program should pay it for a branch it cannot take.

Two things the core backend does that the shell-out cannot:

The stty backend does not take its own success on trust. stty -echo exiting 0 is not proof the terminal obeyed, so the module asks stty -a afterwards and refuses to read at all if echo is still on, rather than accepting a password in the clear.

A secret comes back a Str

prompt returns an allomorph: type 1234 and you get an IntStr, which is an Int as much as it is a Str. For a secret that is a trap, and not a theoretical one — the same on both engines:

my $pin = prompt "PIN: ";        # user types 01234
say to-json({ pin => $pin });    # {"pin": 1234}

The leading zero is gone and the secret has been retyped as a number. So prompt(:hidden) returns a plain Str, always. prompt without :hidden keeps the allomorph, because that is what prompt does.

Not a terminal is not an error

A pipe, a file, a here-doc, a CI harness: there is no echo to suppress, so the line is read plainly and returned. That is what makes :hidden testable, and it is why this distribution's suite can assert the return type, the end-of-input answer and the whole export surface without a pseudo-terminal.

Scope

Compatibility

engineversiont/backendhidden read on a terminal
Rakudo2026.0830/30sttyverified under a pty
Raku++3.26.030/30coreverified under a pty
Raku++3.25.030/30sttybroken — see below

Neither Rakudo version is a floor; no older one has been tried. The Raku++ figure is a floor, and it is the engine's story rather than the module's:

The suite passes on 3.25.0 because it exercises pipes, where there is no echo to suppress. It does not, and cannot, catch that one; a pseudo-terminal does.

Author

Andrew Shitov (zef:ash)

Licence

Artistic-2.0.


The design log — what running this on two engines turned up, and the four engine bugs it found — is in notes/Prompt-Hidden.md.