BoundaryML/baml · error

substring() start argument must be an int at

Error message

substring() start argument must be an int at {:?}

What it means

The first argument to substring (the start index) must be an integer. The interpreter destructures args[0] as BamlValueWithMeta::Int and bails with this message otherwise. No implicit conversion from string or float indices is performed.

Solutions

  1. Pass an integer literal or an int-typed variable as the start index; convert first if it comes as a string/float.
  2. Check the type of the expression in argument position 0 at the failing call site.
  3. Clamp/round computed indices to integers before the call.

Example fix

// before
let head = s.substring(startStr, 5); // startStr: string

// after
let head = s.substring(startStr.to_int(), 5);
Defensive patterns

Strategy: validation

Validate before calling

// start index must be an int
let start_ok = start is int;

Type guard

fn is_int(v: BamlValue) -> bool { matches!(v, BamlValue::Int(_)) }

Try / catch

match result {
  Err(e) if e.to_string().contains("start argument must be an int") => use_default_start,
  other => other,
}

Prevention

When it happens

Trigger: Calling s.substring("0", 5), s.substring(startFloat, end), or passing a null/non-int value as the start index in a BAML expression.

Common situations: Indices read from parsed/config data typed as strings; using float positions from computed arithmetic; optional index variables that are null at runtime.

Understand the failure class

Background: Type mismatch errors: IllegalArgumentException, TypeError and type guards across 150 open-source libraries — this error's family across 150 libraries.

Related errors


AI-assisted analysis of BoundaryML/baml@bd85ce9dee (2026-09-12). Data as JSON: /api/errors/6d2c03e6e58d2dc0. Report an issue: GitHub.

Appendix: source

Thrown at engine/baml-compiler/src/thir/interpret.rs:2958

                .map(|part| BamlValueWithMeta::String(part.to_string(), meta.clone()))
                .collect();
            Ok(BamlValueWithMeta::List(parts, meta.clone()))
        }
        "substring" => {
            let BamlValueWithMeta::String(s, _) = receiver else {
                bail!(
                    "substring() method only available on strings at {:?}",
                    meta.0
                );
            };
            if args.len() != 2 {
                bail!(
                    "substring() method takes exactly 2 arguments at {:?}",
                    meta.0
                );
            }
            let BamlValueWithMeta::Int(start, _) = &args[0] else {
                bail!("substring() start argument must be an int at {:?}", meta.0);
            };
            let BamlValueWithMeta::Int(end, _) = &args[1] else {
                bail!("substring() end argument must be an int at {:?}", meta.0);
            };

            let start = (*start as usize).min(s.len());
            let end = (*end as usize).min(s.len()).max(start);

            Ok(BamlValueWithMeta::String(
                s[start..end].to_string(),
                meta.clone(),
            ))
        }
        "replace" => {
            let BamlValueWithMeta::String(s, _) = receiver else {
                bail!("replace() method only available on strings at {:?}", meta.0);
            };
            if args.len() != 2 {

View on GitHub (pinned to bd85ce9dee)