nautechsystems/nautilus_trader · error

CStr::from_ptr failed

Error message

CStr::from_ptr failed

What it means

cstr_to_ustr in crates/core/src/ffi/string.rs:72 converts a non-null C string pointer into an interned Ustr. It asserts the pointer is non-null first, then converts the CStr bytes to &str with to_str().expect("CStr::from_ptr failed"), panicking when the bytes are not valid UTF-8. (Note: CStr::from_ptr itself only fails on a null pointer, which the assert already rejects — this expect fires on invalid UTF-8 in the buffer.)

Source

Thrown at crates/core/src/ffi/string.rs:72

    // SAFETY: Caller guarantees ptr is borrowed from a valid Python UTF-8 str
    Python::attach(|py| unsafe { Bound::from_borrowed_ptr(py, ptr).to_string() })
}

/// Convert a C string pointer into an owned `Ustr`.
///
/// # Safety
///
/// Assumes `ptr` is a valid C string pointer.
///
/// # Panics
///
/// Panics if `ptr` is null.
#[must_use]
pub unsafe fn cstr_to_ustr(ptr: *const c_char) -> Ustr {
    assert!(!ptr.is_null(), "`ptr` was NULL");
    // SAFETY: Caller guarantees ptr is valid per function contract
    let cstr = unsafe { CStr::from_ptr(ptr) };
    Ustr::from(cstr.to_str().expect("CStr::from_ptr failed"))
}

/// Convert a C string pointer into a borrowed byte slice.
///
/// # Safety
///
/// - Assumes `ptr` is a valid, null-terminated UTF-8 C string pointer.
/// - The returned slice borrows the underlying allocation; callers must ensure the
///   C buffer outlives every use of the slice.
///
/// # Panics
///
/// Panics if `ptr` is null.
#[must_use]
pub unsafe fn cstr_to_bytes<'a>(ptr: *const c_char) -> &'a [u8] {
    assert!(!ptr.is_null(), "`ptr` was NULL");
    // SAFETY: Caller guarantees ptr is valid per function contract
    let cstr = unsafe { CStr::from_ptr(ptr) };

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Ensure the producer encodes all strings as UTF-8 before placing them in the buffer (Python: str.encode('utf-8'); C: use UTF-8 literals/sources).
  2. Verify buffer lengths so multi-byte characters are not truncated mid-sequence.
  3. If input encoding is out of your control, use lossy conversion on the producer side or sanitize the string (encode to UTF-8 with replacement) before the FFI call.
  4. Inspect the raw bytes at the pointer to identify the actual encoding and re-encode it.

Example fix

// before (Python producer)
b"caf\xe9"            # Latin-1 encoded, invalid UTF-8
// after
"café".encode("utf-8")  # b'caf\xc3\xa9', valid UTF-8
Defensive patterns

Strategy: validation

Validate before calling

# Python producer, before FFI
raw = text.encode("utf-8")            # raises UnicodeEncodeError if already mojibake
raw.decode("utf-8")                   # round-trip: guarantees valid UTF-8 bytes

Type guard

def is_utf8(b: bytes) -> bool:
    try:
        b.decode("utf-8"); return True
    except UnicodeDecodeError:
        return False

Prevention

When it happens

Trigger: Calling cstr_to_ustr (directly or via optional_cstr_to_ustr, OrderDeniedFfi, OrderRejectedFfi deserialization) with a pointer to bytes that are not UTF-8: Latin-1 or GBK-encoded text, partial multi-byte characters cut by a buffer boundary, or random memory not intended as a string.

Common situations: A C/Python producer encoding strings in a legacy codepage instead of UTF-8; a struct field written as raw bytes without null-termination rules; truncated UTF-8 after a memcpy of the wrong length.

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


AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08). Data as JSON: /api/errors/b2bbd61aad004067. Report an issue: GitHub.