pydantic/monty · error

invalid ascii byte

Error message

invalid ascii byte

What it means

A const-evaluated table of all 128 ASCII byte values is built by decoding each byte as UTF-8 at first use. Since bytes 0x00–0x7F are always valid ASCII/UTF-8, the decode can never fail; the panic is a defensive invariant against the ASCII_BYTES table being corrupted or reordered.

Source

Thrown at crates/monty/src/intern.rs:94

/// them in lockstep — both tables must agree on the same `&str` per byte.
pub(crate) static ASCII_STRS: [&str; 128] = const {
    // Initialize array of 128 bytes which will be used as the raw storage
    const ASCII_BYTES: [u8; 128] = const {
        let mut bytes: [u8; 128] = [0; 128];
        let mut i: u8 = 0;
        while i < 128 {
            bytes[i as usize] = i;
            i += 1;
        }
        bytes
    };
    // Index into the above array to build the `&'static str` forms
    let mut strs: [&str; 128] = [""; 128];
    let mut i = 0;
    while i < 128 {
        strs[i] = match str::from_utf8(from_ref(&ASCII_BYTES[i])) {
            Ok(s) => s,
            Err(_) => panic!("invalid ascii byte"),
        };
        i += 1;
    }
    strs
};

/// Static string values which are known at compile time and don't need to be interned.
///
/// Discriminant starts from STATIC_STRING_ID_OFFSET to make conversion to/from stringid
/// cheap when within bounds. Discriminants are serialized `StringId`s, so append new
/// variants at the end — inserting one shifts every later id.
#[repr(u16)]
#[derive(
    Debug,
    Clone,
    Copy,
    FromRepr,
    EnumCount,

View on GitHub (pinned to adc986b362)

Solutions

  1. Inspect the ASCII_BYTES constant and restore it so entries 0..128 equal their own byte value.
  2. Verify the array length is exactly 128 and `from_ref` points at raw bytes, not a transformed representation.
  3. Replace the table with a safer generation (e.g. `char::from_u32` loop) so the invariant is enforced at compile time.

Example fix

// before
static ASCII_BYTES: [u8; 128] = generate_bytes(); // possibly wrong
// after
const ASCII_BYTES: [u8; 128] = {
    let mut b = [0u8; 128];
    let mut i = 0;
    while i < 128 { b[i] = i as u8; i += 1; }
    b
};
Defensive patterns

Strategy: validation

Validate before calling

// assert table shape before relying on the lazy table
assert_eq!(ASCII_BYTES.len(), 128);
assert!(ASCII_BYTES.iter().enumerate().all(|(i, b)| *b == i as u8));

Prevention

When it happens

Trigger: A contributor modifies the ASCII_BYTES static so that an entry is no longer a valid single ASCII byte, then any string interning triggers lazy table construction.

Common situations: Refactoring intern.rs (changing array size, initializer, or encoding) breaks the assumption that all 128 entries are ASCII.

Understand the failure class

Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.

Related errors


AI-assisted analysis of pydantic/monty@adc986b362 (2026-09-13). Data as JSON: /api/errors/555953b201038e24. Report an issue: GitHub.