nautechsystems/nautilus_trader · critical
Failed to convert C string to UTF-8
Error message
Failed to convert C string to UTF-8
What it means
uuid4_from_cstr in crates/core/src/ffi/uuid.rs:51 builds a UUID4 from a C string pointer, converting the CStr to &str with to_str().expect("Failed to convert C string to UTF-8") inside abort_on_panic (so the process aborts, not just unwinds). It panics when the pointed-to bytes are not valid UTF-8; note the abort_on_panic wrapper makes this failure harsher than the similar panics in ffi/string.rs.
Source
Thrown at crates/core/src/ffi/uuid.rs:51
abort_on_panic(UUID4::new)
}
/// Returns a [`UUID4`] from C string pointer.
///
/// # Safety
///
/// Assumes `ptr` is a valid C string pointer.
///
/// # Panics
///
/// Panics if `ptr` cannot be cast to a valid C string.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn uuid4_from_cstr(ptr: *const c_char) -> UUID4 {
abort_on_panic(|| {
assert!(!ptr.is_null(), "`ptr` was NULL");
// SAFETY: Caller guarantees ptr is valid per function contract
let cstr = unsafe { CStr::from_ptr(ptr) };
let value = cstr.to_str().expect("Failed to convert C string to UTF-8");
UUID4::from(value)
})
}
/// Return a borrowed *null-terminated* UTF-8 C string representing `uuid`.
///
/// The pointer remains valid for as long as the input `UUID4` reference lives - callers **must
/// not** attempt to free it.
#[unsafe(no_mangle)]
pub extern "C" fn uuid4_to_cstr(uuid: &UUID4) -> *const c_char {
abort_on_panic(|| uuid.to_cstr().as_ptr())
}
/// Compare two UUID values, returning `1` when they are equal and `0` otherwise.
#[unsafe(no_mangle)]
pub extern "C" fn uuid4_eq(lhs: &UUID4, rhs: &UUID4) -> u8 {
abort_on_panic(|| u8::from(lhs == rhs))
}View on GitHub (pinned to 18893faf8b)
Solutions
- Serialize UUIDs as canonical lowercase hex strings (e.g. Python str(uuid.uuid4())) before crossing the boundary.
- Never pass raw 16-byte UUID binaries to this API — convert to the hex string representation first.
- Validate the string matches ^[0-9a-fA-F-]{36}$ on the producer side before the call.
- Because this aborts the process, add producer-side unit tests covering every ID emission path to keep bad bytes from reaching the FFI boundary.
Example fix
// before (Python producer)
struct.pack("16s", uuid.uuid4().bytes) # raw binary, not UTF-8 text
// after
str(uuid.uuid4()).encode("utf-8") # '550e8400-e29b-41d4-...' hex string Defensive patterns
Strategy: validation
Validate before calling
# Python producer
u = str(uuid.uuid4())
import re
assert re.fullmatch(r"[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}", u)
send_to_ffi(u.encode("utf-8")) Type guard
def is_uuid4_text(value: bytes) -> bool:
import re, string
try:
s = value.decode("utf-8")
except UnicodeDecodeError:
return False
return bool(re.fullmatch(r"[0-9a-fA-F-]{36}", s)) Prevention
- Always serialize UUIDs as canonical hex strings, never raw 16-byte binaries.
- Because uuid4_from_cstr aborts the process on panic, validate IDs on the producer side religiously.
- Unit-test every ID emission path with canonical string UUIDs.
- Never pass raw struct-packed UUID bytes to this FFI entry point.
When it happens
Trigger: Calling uuid4_from_cstr with a pointer to non-UTF-8 bytes: a binary/misinterpreted UUID representation (raw 16 bytes instead of the hex string form), legacy-encoded text, or truncated multi-byte data.
Common situations: Passing a UUID as raw 16-byte binary instead of its canonical 36-char hyphenated hex string; an ID field produced by a non-UTF-8 system; buffer corruption between producer and FFI call.
Understand the failure class
Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.
Related errors
- CStr::from_ptr failed
- C string contains invalid UTF-8
- Invalid UTF-8 in C string
- Invalid scientific notation exponent '{exponent}': must be a
- invalid `AccountType` enum string value, was '{value}'
AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08).
Data as JSON: /api/errors/aa7e6d8a74ad8d4b.
Report an issue: GitHub.