Storing a PathBuf field, from a impl AsRef<Path> function parameter

Let's say I have a struct called App, and this struct has a constructor similar to:

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

pub struct App {
  file_path: PathBuf,
  // other fields
}

impl App {
  pub fn new(file_path: impl AsRef<Path>) -> Self {
    // do some work with the path, such as opening with std::fs::File::open(..) and 
    // deserializing into a local type.
    
    let file_path = {
      let mut file_buf = PathBuf::new();
      file_buf.push(file_path);
      file_buf
    };

    Self {
      file_path,
      // other fields which are derived from work done on the file
    }
  }
}

(Please ignore that I'm not wrapping the return in a Result<..>, I'll do proper error handling in the real implementation.)

The idea here is, I want to know the path where I got the data, so that I can save back to that file at a later point.

The above code does work, but I fell I can do better.

  1. Can I create the PathBuf directly from the generic impl AsRef<Path> without that clunky interim step? or..
  2. Is there someway to leverage cow or similar?

I don't mind allocating at this stage, but would still prefer a more idiomatic solution, if one exists?

How would y'all implement this?

let file_path = file_path.as_ref().to_owned();

This is using ToOwned, so you could sort of say that it is using part of Cow.

The idiomatic solution is for your constructor to take the types it actually needs, and let the caller convert. This minimizes the number of unnecessary conversions or allocations.

If you really need to be able to conveniently pass &Paths too, another option is

pub fn new(file_path: impl Into<PathBuf>) -> Self {

which will allow both &Path and PathBuf.

However, you should generally think twice before making any parameter of this sort generic, because it makes type inference work less well. For example, if you have a concretely typed parameter, fn new(file_path: PathBuf) then users can write this kind of code:

fn get_value_from_config<T: Deserialize>(...) -> T {...}

let app = App::new(get_value_from_config(...));

but when the parameter is generic, no matter what trait you use for it, nothing can resolve T = PathBuf without a type annotation.

5 Likes