GitoxideLabs/gitoxide · error

borrowed cows stay borrowed

Error message

borrowed cows stay borrowed

What it means

This is an `unreachable!()` panic assertion inside `os_str_into_bstr`, a public API in gix-path. The function converts an `OsStr` to a `&BStr` via `try_into_bstr(Cow::Borrowed(...))` and asserts the resulting `Cow` is still `Borrowed`. The library throws it if the conversion of a borrowed OS string unexpectedly allocated an owned value, which would mean an internal invariant about platform encoding losslessness is broken.

Solutions

  1. Upgrade or downgrade gix-path to a released version; this indicates a bug in the crate, not in caller code
  2. Inspect the platform: on non-Unix platforms (e.g. Windows with non-UTF-8 encodable paths) verify gix-path supports the encoding path taken and file an issue if it panics
  3. Reproduce with a minimal `OsStr` input and report it upstream with the input value
  4. As a workaround, use `try_os_str_into_bstr` or `into_bstr` which tolerate owned conversion
Defensive patterns

Strategy: try-catch

Validate before calling

fn is_ascii_or_valid_os(s: &std::ffi::OsStr) -> bool { s.to_str().map(|v| v.is_character_based()).unwrap_or(true) } // unix: borrowed path is guaranteed
let _ = is_ascii_or_valid_os(path);

Type guard

fn borrowed_bstr<'a>(r: Result<&'a gix_hash::bstr::BStr, std::str::Utf8Error>) -> Option<&'a gix_hash::bstr::BStr> { r.ok() }

Try / catch

// panics are not catchable in Rust without std::panic::catch_unwind
let result = std::panic::catch_unwind(|| gix_path::os_str_into_bstr(path));
match result { Ok(Ok(bstr)) => use(bstr), _ => fallback_to_owned_conversion(path) }

Prevention

When it happens

Trigger: Calling `gix_path::os_str_into_bstr` on a Unix-like platform where the conversion always borrows; the panic fires only if `try_into_bstr` returns `Cow::Owned` for borrowed input, which indicates a bug in `try_into_bstr` (e.g. after a gix-path upgrade changing the internal conversion behavior) rather than anything the caller passed.

Common situations: Practically only hit during gix development or when a patched/forked version of gix-path altered `try_into_bstr` so that borrowed input is re-allocated; end users essentially never trigger it through normal path handling on supported platforms.

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 GitoxideLabs/gitoxide@e73179060b (2026-09-08). Data as JSON: /api/errors/9b5cfb6db634a34d. Report an issue: GitHub.

Appendix: source

Thrown at gix-path/src/convert.rs:26

#[derive(Debug)]
/// The error type returned by [`into_bstr()`] and others may suffer from failed conversions from or to bytes.
pub struct Utf8Error;

impl std::fmt::Display for Utf8Error {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.write_str("Could not convert to UTF8 or from UTF8 due to ill-formed input")
    }
}

impl std::error::Error for Utf8Error {}

/// Like [`into_bstr()`], but takes `OsStr` as input for a lossless, but fallible, conversion.
pub fn os_str_into_bstr(path: &OsStr) -> Result<&BStr, Utf8Error> {
    let path = try_into_bstr(Cow::Borrowed(path.as_ref()))?;
    match path {
        Cow::Borrowed(path) => Ok(path),
        Cow::Owned(_) => unreachable!("borrowed cows stay borrowed"),
    }
}

/// Like [`into_bstr()`], but takes `OsString` as input for a lossless, but fallible, conversion.
pub fn os_string_into_bstring(path: OsString) -> Result<BString, Utf8Error> {
    let path = try_into_bstr(Cow::Owned(path.into()))?;
    match path {
        Cow::Borrowed(_path) => unreachable!("borrowed cows stay borrowed"),
        Cow::Owned(path) => Ok(path),
    }
}

/// Like [`into_bstr()`], but takes `Cow<OsStr>` as input for a lossless, but fallible, conversion.
pub fn try_os_str_into_bstr(path: Cow<'_, OsStr>) -> Result<Cow<'_, BStr>, Utf8Error> {
    match path {
        Cow::Borrowed(path) => os_str_into_bstr(path).map(Cow::Borrowed),
        Cow::Owned(path) => os_string_into_bstring(path).map(Cow::Owned),
    }

View on GitHub (pinned to e73179060b)