randregex is a Go library for generating pseudo-random strings that match
regular expressions. It is intended for test data, identifiers, fixtures,
property-style checks, and other workflows where a compact regexp is a clearer
way to describe valid sample strings than handwritten generation code.
go get github.com/ryanfowler/randregexpackage main
import (
"fmt"
"log"
"github.com/ryanfowler/randregex"
)
func main() {
g, err := randregex.Compile(`[a-z]{8}\d{2}`)
if err != nil {
log.Fatal(err)
}
fmt.Println(g.Generate())
}The public API is intentionally small:
Compile(pattern string) (*Generator, error)parses and validates a regexp pattern usingDefaultMaxRepeat.CompileMaxRepeat(pattern string, maxRepeat int) (*Generator, error)parses and validates a regexp pattern using a caller-provided unbounded-repeat limit.MustCompile(pattern string) *Generatoris suitable for package-level generators and panics on invalid input.MustCompileMaxRepeat(pattern string, maxRepeat int) *Generatorcombines package-level setup with a caller-provided unbounded-repeat limit.FromRegexp(re *syntax.Regexp) (*Generator, error)compiles an existingregexp/syntax.Regexpwithout mutating it, usingDefaultMaxRepeat.FromRegexpMaxRepeat(re *syntax.Regexp, maxRepeat int) (*Generator, error)compiles an existingregexp/syntax.Regexpwith a caller-provided unbounded-repeat limit.(*Generator).Generate() stringreturns a generated string using the default pseudo-random source.(*Generator).GenerateWithRand(r Rand) stringuses a caller-provided random source.(*Generator).Append(dst []byte) []byteappends generated output to a buffer.(*Generator).AppendWithRand(dst []byte, r Rand) []bytecombines buffer reuse with a caller-provided random source.CryptoRandis aRandvalue backed by Go'scrypto/randsource.
DefaultMaxRepeat is the bound used by Compile, MustCompile, and
FromRegexp for unbounded repetitions:
const DefaultMaxRepeat = 32Use a MaxRepeat variant when a pattern needs a different unbounded-repeat
policy:
g, err := randregex.CompileMaxRepeat(pattern, 8)FromRegexp and FromRegexpMaxRepeat return an error for a nil
*syntax.Regexp. They do not mutate the regexp passed by the caller. Passing an
already simplified regexp is supported, but regexp/syntax.Simplify may rewrite
counted unbounded repetitions such as a{3,} into forms that no longer preserve
the original minimum for randregex's maxRepeat policy.
Compile patterns once and reuse the generator:
var userID = randregex.MustCompile(`user-[a-z0-9]{12}`)
func newUserID() string {
return userID.Generate()
}*Generator is immutable after construction and safe for concurrent use.
Use GenerateWithRand or AppendWithRand with any value that satisfies:
type Rand interface {
IntN(n int) int
}This interface is satisfied by *math/rand/v2.Rand:
r := rand.New(rand.NewPCG(1, 2))
g := randregex.MustCompile(`[a-z]{8}`)
fmt.Println(g.GenerateWithRand(r))If a Rand value is shared across goroutines, the Rand implementation must
provide its own synchronization.
GenerateWithRand and AppendWithRand require a non-nil Rand that returns a
value in [0, n) from IntN(n). Invalid Rand implementations may cause a
panic or invalid output.
For security-sensitive output, pass CryptoRand to GenerateWithRand or
AppendWithRand:
g := randregex.MustCompile(`[a-zA-Z0-9_-]{32}`)
token := g.GenerateWithRand(randregex.CryptoRand)CryptoRand uses crypto/rand.Reader and panics if the system cryptographic
source fails. Direct calls to CryptoRand.IntN also panic when n <= 0.
The regular expression still determines the output entropy. CryptoRand
provides an unpredictable source of randomness, but it does not make a small
output space secure; for example, [0-9]{6} still has only one million
possible values.
Append and AppendWithRand are the allocation-conscious APIs:
g := randregex.MustCompile(`[a-zA-Z0-9_-]{24}`)
buf := make([]byte, 0, 64)
for range 1000 {
buf = buf[:0]
buf = g.Append(buf)
use(buf)
}For common ASCII patterns, AppendWithRand allocates zero times when the
provided buffer has enough capacity.
Patterns are parsed with Go's regexp/syntax package using syntax.Perl.
Supported:
- Empty expressions
- Literal strings and escaped literal characters
- Literal Unicode characters
- Concatenation
- Alternation, such as
foo|bar - Capturing and non-capturing groups
- Character classes, such as
[a-z],[abc], and[a-zA-Z0-9_] - Predefined ASCII classes:
\d,\D,\w,\W,\s,\S - Repetition:
?,*,+,{n},{n,m},{n,} - Dot
. - Anchors as zero-width nodes:
^,$,\A,\z,\b,\B
Unsupported expressions return compile-time errors. Go's regexp syntax does not
support lookaround or backreferences, so randregex does not either.
Word-boundary assertions are accepted only when the adjacent generated
characters make the assertion guaranteed. For example, \b[a-z]{4}\b is valid,
while a?\b is rejected because one random branch would violate the assertion.
The maxRepeat argument controls unbounded repetitions:
a*generates 0 throughmaxRepeatrepetitions.a+generates 1 throughmaxRepeatrepetitions, or exactly 1 whenmaxRepeatis 0.a{3,}generates 3 throughmaxRepeatrepetitions whenmaxRepeat > 3.- If the minimum is greater than or equal to
maxRepeat, an unbounded repeat generates exactly the minimum.
maxRepeat must be greater than or equal to zero. Compile, MustCompile, and
FromRegexp use DefaultMaxRepeat. Passing 0 to a MaxRepeat variant
explicitly chooses a zero upper bound.
Character generation is intentionally ASCII-first for performance, predictability, and testability.
- Literal Unicode characters are supported and emitted literally.
- ASCII character classes are sampled directly.
- Dot
.samples from printable ASCII, from space through tilde. - Negated and very broad character classes sample from printable ASCII after applying the class.
\dis[0-9].\wis[0-9A-Za-z_].\sis tab, newline, vertical tab, form feed, carriage return, and space.
Full Unicode character-class sampling is intentionally out of scope. Use Unicode literals outside character classes when exact Unicode characters are needed.
The default methods use Go's pseudo-random math/rand/v2 default source.
Generated strings are not cryptographic secrets.
For reproducible output, pass a seeded math/rand/v2.Rand. For
security-sensitive use, pass randregex.CryptoRand.
randregex samples choices locally at each regexp node. It does not attempt to
provide a uniform distribution over all strings accepted by a pattern.
randregex compiles regular expressions into an immutable internal generator
tree. Generation does not re-parse patterns or walk regexp/syntax trees.
The implementation:
- Appends directly into caller-provided buffers.
- Precomputes sampleable character sets.
- Chooses alternation branches without generating unused branches.
- Generates repetition counts once per repeat node.
- Avoids reflection and external runtime dependencies.
Benchmarks are included in randregex_benchmark_test.go and can be run with:
go test -bench=. -benchmem ./...Invalid patterns, unsupported regexp nodes, unsafe word-boundary assertions, and unsampleable character classes are rejected during compilation. Generation methods assume a valid compiled generator and therefore return only generated output.
This design makes errors explicit at setup time and keeps hot-path generation simple.