BoundaryML/baml · error
substring() method only available on strings at {:?}
Error message
substring() method only available on strings at {:?} What it means
The "substring" method is only defined for string receivers. When the interpreter dispatches "substring" it pattern-matches the receiver as BamlValueWithMeta::String and bails with this message for any other value type. It enforces that substring(start, end) operates on a string.
Source
Thrown at engine/baml-compiler/src/thir/interpret.rs:2946
"split" => {
let BamlValueWithMeta::String(s, _) = receiver else {
bail!("split() method only available on strings at {:?}", meta.0);
};
if args.len() != 1 {
bail!("split() method takes exactly 1 argument at {:?}", meta.0);
}
let BamlValueWithMeta::String(delimiter, _) = &args[0] else {
bail!("split() argument must be a string at {:?}", meta.0);
};
let parts: Vec<BamlValueWithMeta<ExprMetadata>> = s
.split(delimiter.as_str())
.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());View on GitHub (pinned to bd85ce9dee)
Solutions
- Confirm the receiver's actual type at the failing expression and convert it to a string before slicing.
- Fix the upstream expression/annotation so the value is a string when substring is called.
- Guard the call with a type check when the receiver type is dynamic or optional.
Example fix
// before (n is an int) let head = n.substring(0, 2); // after let head = n.to_string().substring(0, 2);
Defensive patterns
Strategy: type-guard
Validate before calling
// guard the receiver before substring
if (v is string) {
return v.substring(0, 5);
} Type guard
fn is_string(v: BamlValue) -> bool { matches!(v, BamlValue::String(_)) } Try / catch
match result {
Err(e) if e.to_string().contains("substring() method only available on strings") => fallback_string,
other => other,
} Prevention
- Convert numbers/lists to strings before slicing.
- Null-check optional fields before string operations.
- Keep extraction outputs string-typed when downstream code slices them.
When it happens
Trigger: Calling <non-string>.substring(0, 5) in a BAML expression — e.g. on an int, a list, a map, or a null value.
Common situations: Slicing a value assumed to be text but produced by extraction as a number/list; calling substring on an optional field that is null at runtime; refactors that changed a variable's type upstream.
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
- endsWith() method only available on strings at {:?}
- split() method only available on strings at {:?}
- baml.json.serialize returned non-string value: {other:?}
- endsWith() argument must be a string at {:?}
- split() argument must be a string at {:?}
AI-assisted analysis of BoundaryML/baml@bd85ce9dee (2026-09-12).
Data as JSON: /api/errors/f60c3a9a704a43ec.
Report an issue: GitHub.