PetPaulsen
PetPaulsen

Reputation: 3508

Rewriting a function to accept AsRef<Path> instead of &Path

How do I write the following function to accept not only Path, but also String or &str?

fn find_database1<'a>(path: &'a Path) -> Option<&'a Path> {
    path.parent()
}

After writing the above mentioned function, I wanted to convert it into a form to not just accept a Path, but also String or &str. I ended up with the two following versions, each of which does not work. Function find_database3 was an attempt to get a better understanding of the cause, but unfortunately I don't see why it's not working.

fn find_database2<'a, P>(path: P) -> Option<&'a Path>
where
    P: 'a + AsRef<Path>,
{
    path.as_ref().parent()
}

fn find_database3<'a, P>(path: P) -> Option<&'a Path>
where
    P: 'a + AsRef<Path>,
{
    let _path: &'a Path = path.as_ref();
    _path.parent()
}

These are the errors I get:

error[E0515]: cannot return value referencing function parameter `path`
  --> src/main.rs:11:5
   |
11 |     path.as_ref().parent()
   |     ----^^^^^^^^^^^^^^^^^^
   |     |
   |     returns a value referencing data owned by the current function
   |     `path` is borrowed here

error[E0597]: `path` does not live long enough
  --> src/main.rs:18:27
   |
14 | fn find_database3<'a, P>(path: P) -> Option<&'a Path>
   |                   -- lifetime `'a` defined here
...
18 |     let _path: &'a Path = path.as_ref();
   |                --------   ^^^^ borrowed value does not live long enough
   |                |
   |                type annotation requires that `path` is borrowed for `'a`
19 |     _path.parent()
20 | }
   | - `path` dropped here while still borrowed
use std::path::Path;

fn main() {
    let path_str: &str = "root/path";
    let path_string: String = path_str.to_string();
    let path_path: &Path = &Path::new(path_str);

    let root = find_database1(path_path);
    println!("{:?}", root);

    find_database2(path_str);
    find_database2(path_string);
    let root = find_database2(path_path);
    println!("{:?}", root);
}

Link to Playground

Upvotes: 1

Views: 1580

Answers (1)

pretzelhammer
pretzelhammer

Reputation: 15135

Path::parent has this signature:

fn parent(&self) -> Option<&Path>;

So the returned result holds a reference to some data owned by the caller. You can't call parent() on a String and then drop the String because that invalidates the reference returned by parent(). You can make your function work if you loosen the requirement from taking Strings and accept &Strings instead. Example:

use std::path::Path;

// takes &str, &String, or &Path
fn find_database2<'a, P>(path: &'a P) -> Option<&'a Path>
    where P: 'a + ?Sized + AsRef<Path>,
{
    path.as_ref().parent()
}

fn main() {
    let path_str: &str = "root/path";
    let path_string: String = path_str.to_string();
    let path_path: &Path = &Path::new(path_str);

    find_database2(path_str); // &str
    find_database2(&path_string); // &String
    let root = find_database2(path_path); // &Path
    println!("{:?}", root);
}

playground

On the other hand if you really want to accept Strings you can convert the Option<&Path> to a Option<PathBuf> inside the function body. This works because PathBuf is the owned version of Path:

use std::path::{Path, PathBuf};

// takes &str, &String, String, &Path, or PathBuf
fn find_database2<'a, P>(path: P) -> Option<PathBuf>
    where P: 'a + AsRef<Path>,
{
    path.as_ref().parent().map(|path| {
        let mut path_buf = PathBuf::new();
        path_buf.push(path);
        path_buf
    })
}

fn main() {
    let path_str: &str = "root/path";
    let path_string: String = path_str.to_string();
    let path_path: &Path = &Path::new(path_str);

    find_database2(path_str); // &str
    find_database2(&path_string); // &String
    find_database2(path_string); // String
    let root = find_database2(path_path); // &Path
    println!("{:?}", root);
}

playground

Upvotes: 3

Related Questions