rust

Getting a reference to a Rust `AsRef<Path>` as a `&str` with the same lifetime


A Rust Path at its core is a slice of u8 ([u8]). My understanding is that a &str is a reference to a slice of u8 as well, but a slice that is guaranteed to represent a valid UTF-8 sequence.

Therefore if a Path contains valid UTF-8, I should be be able to reference the same bytes with a &str, as long as I obey lifetime constraints, no?

The following function works (admittedly offering no benefit over a direct call, but it illustrates that I have the lifetimes correct):

pub fn path_to_str<'a>(path: &'a Path) -> Option<&'a str> {
    path.to_str()
}

But I want my function to be more flexible, so I try using impl AsRef<Path> as the parameter:

pub fn path_to_str<'a>(path: impl AsRef<Path> + 'a) -> Option<&'a str> {
    let path = path.as_ref();
    path.to_str()
}

That gives me an error:

error[E0515]: cannot return value referencing function parameter `path`
  --> src\lib.rs:72:5
   |
71 |     let path = path.as_ref();
   |                ---- `path` is borrowed here
72 |     path.to_str()
   |     ^^^^^^^^^^^^^ returns a value referencing data owned by the current function

I was hoping that AsRef would let me get a reference to the Path that was no different than if I had passed &Path to the function, but apparently that's not the case. I'm not quite getting the distinction—how are the ownership rules different, if I have the lifetime rules correct? Maybe the lifetime needs to be applied to the resulting reference, and not to the AsRef<_>? But how do I do that?

The larger question here is: How do I have a parameter similar to impl AsRef<Path> to allow me the most flexibility of accepting paths, while returning a &str or Option<&str> without creating any copies of the underlying bytes (which I assume String would do)?

For a more complete explanation of the context, here is what I'm really trying to get to, but with more flexibility for the path:

/// Converts a path to a string slice reference, returning an I/O error
/// if the path does not represent valid UTF-8.
/// See [`Path::to_str`].
pub fn path_to_string(path: &Path) -> io::Result<&str> {
    path.to_str().ok_or_else(|| io::Error::new(io::ErrorKind::Other,
        format!("Could not convert path `{:?}` to Unicode using UTF-8.", path)))
}

Solution

  • AsRef always yields a reference with the lifetime bound to itself. In this case, you can ensure that lifetime stays valid to return by using a reference for the parameter:

    pub fn path_to_str<P: AsRef<Path> + ?Sized>(path: &P) -> Option<&str> {
        path.as_ref().to_str()
    }
    

    Full demo on the playground.

    This only really prevents you from passing owned values like String as you could normally with impl AsRef<Path> but that wouldn't work here anyway since the reference needs to be valid after the call (which is exactly what the error was complaining about). The + ?Sized is to ensure you can still pass &str and &Path as well.