Skip to content

Lorem Ipsum Generator — Rust source

Generate placeholder Lorem Ipsum text - paragraphs and sentences with adjustable counts. Classic Latin word pool, instant copy.

This is the Rust implementation — the same logic the interactive tool runs, in a shareable, citable form.

//! Lorem Ipsum generator — Rust polyglot showcase port.
//!
//! Pure structure generator: random words from the canonical Lorem Ipsum
//! pool assembled into capitalized sentences, paragraphs, and multi-
//! paragraph blocks. Behaviour mirrors the TypeScript reference in
//! `src/lib/lorem.ts` 1:1 (same word pool, same defaults, same edge-case
//! handling).
//!
//! Rust's standard library ships no RNG, so this port implements a tiny
//! xorshift64 PRNG (non-cryptographic — matching the TypeScript source's
//! `Math.random()`) seeded from the wall clock. Production code should
//! prefer the `rand` crate; this stays stdlib-only for the showcase.
//!
//! Rust lacks default parameters, so callers pass counts explicitly. The
//! canonical defaults are a sentence of 8..16 words, a paragraph of 5
//! sentences, and a block of 3 paragraphs; `paragraph` and `lorem` apply
//! them internally.
//!
//! Ported from src/lib/lorem.ts.
//! Display source — part of CosmoDev's polyglot tool pages.

use std::cell::RefCell;
use std::sync::OnceLock;
use std::time::{SystemTime, UNIX_EPOCH};

/// Source string for the canonical Lorem Ipsum word pool — identical across
/// every port so output is drawn from the same vocabulary.
const RAW_WORDS: &str = "lorem ipsum dolor sit amet consectetur adipiscing elit sed do eiusmod tempor \
incididunt ut labore et dolore magna aliqua enim ad minim veniam quis nostrud exercitation \
ullamco laboris nisi aliquip ex ea commodo consequat duis aute irure in reprehenderit voluptate \
velit esse cillum eu fugiat nulla pariatur excepteur sint occaecat cupidatat non proident sunt \
culpa qui officia deserunt mollit anim id est laborum";

static WORDS_CELL: OnceLock<Vec<&'static str>> = OnceLock::new();

/// Access the word pool, splitting it once and caching the result. The
/// source string is `'static`, so every slice it yields is `'static` too.
pub fn words() -> &'static [&'static str] {
    WORDS_CELL
        .get_or_init(|| RAW_WORDS.split(' ').collect())
        .as_slice()
}

/// A minimal xorshift64 PRNG (Marsaglia). Non-cryptographic; used only to
/// match the TypeScript source's `Math.random()` semantics for placeholder
/// text generation.
struct Xorshift64 {
    state: u64,
}

impl Xorshift64 {
    /// Seed from wall-clock nanoseconds. A zero state is an absorbing fixed
    /// point for xorshift, so the low bit is forced on as a fallback.
    fn from_clock() -> Self {
        let seed = SystemTime::now()
            .duration_since(UNIX_EPOCH)
            .map(|d| d.as_nanos() as u64)
            .unwrap_or(0x9E37_79B9_7F4A_7C15); // arbitrary nonzero fallback if the clock is unavailable
        Self { state: seed | 1 }
    }

    fn next_u64(&mut self) -> u64 {
        let mut x = self.state;
        x ^= x << 13;
        x ^= x >> 7;
        x ^= x << 17;
        self.state = x;
        x
    }

    /// Uniform int in [0, max). Returns 0 for max == 0 (matching
    /// `Math.floor(Math.random() * 0)`). Light modulo bias is acceptable
    /// for non-crypto placeholder use.
    fn below(&mut self, max: usize) -> usize {
        if max == 0 {
            0
        } else {
            (self.next_u64() % max as u64) as usize
        }
    }
}

thread_local! {
    // Each thread gets its own RNG state, lazily seeded on first use.
    static RNG: RefCell<Xorshift64> = RefCell::new(Xorshift64::from_clock());
}

/// Uniform int in [0, max) drawn from the thread-local RNG.
fn rand_int(max: usize) -> usize {
    RNG.with(|cell| cell.borrow_mut().below(max))
}

/// Build a single sentence of `min_words`..`max_words` words (inclusive).
/// When `max_words <= min_words` the range collapses to exactly `min_words`.
pub fn sentence(min_words: usize, max_words: usize) -> String {
    // Equivalent of Math.max(1, max - min + 1), computed without unsigned
    // underflow (max - min is only evaluated when max > min).
    let span = if max_words > min_words {
        max_words - min_words + 1
    } else {
        1
    };
    let n = min_words + rand_int(span);

    let pool = words();
    let mut pieces: Vec<&str> = Vec::with_capacity(n);
    for _ in 0..n {
        pieces.push(pool[rand_int(pool.len())]);
    }

    let body = pieces.join(" ");
    // Capitalize only the first character and terminate with a period.
    // (usize bounds guarantee n words, but the empty case is handled too.)
    let mut out = String::with_capacity(body.len() + 1);
    let mut chars = body.chars();
    if let Some(first) = chars.next() {
        out.push(first.to_ascii_uppercase());
        out.push_str(chars.as_str());
    }
    out.push('.');
    out
}

/// Join `sentence_count` default-length (8..16 word) sentences into a paragraph.
pub fn paragraph(sentence_count: usize) -> String {
    (0..sentence_count)
        .map(|_| sentence(8, 16))
        .collect::<Vec<_>>()
        .join(" ")
}

/// Build `paragraph_count` paragraphs of `sentences_per_paragraph`
/// sentences each, separated by a blank line.
pub fn lorem(paragraph_count: usize, sentences_per_paragraph: usize) -> String {
    (0..paragraph_count)
        .map(|_| paragraph(sentences_per_paragraph))
        .collect::<Vec<_>>()
        .join("\n\n")
}

Also available in 13 other languages

Every CosmoDev tool ships its pure logic in TypeScript (web) and Go (CLI), with authored implementations in a dozen-plus languages — the same contract, ported. Compare all languages side by side →