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)))
}
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.