10

In Python, a function called os.path.join() allows concatenating multiple strings into one path using the path separator of the operating system. In Rust, there is only a function join() that appends a string or a path to an existing path. This problem can't be solved with a normal function as a normal function needs to have a fixed number of arguments.

I'm looking for a macro that takes an arbitrary number of strings and paths and returns the joined path.

3 Answers 3

9

There's a reasonably simple example in the documentation for PathBuf:

use std::path::PathBuf; let path: PathBuf = [r"C:\", "windows", "system32.dll"].iter().collect(); 
Sign up to request clarification or add additional context in comments.

2 Comments

This is nice. What is the r before "C:\"? I can see that without it the code doesn't compile, so I suppose it has to do with escaping the "\" character.
@PaulRazvanBerg It makes it a raw string literal. Which exactly as you said, makes it so you don't need to write two slashes "\\" in order to get a single slash.
5

Once you read past the macro syntax, it's not too bad. Basically, we take require at least two arguments, and the first one needs to be convertible to a PathBuf via Into. Each subsequent argument is pushed on the end, which accepts anything that can be turned into a reference to a Path.

macro_rules! build_from_paths { ($base:expr, $($segment:expr),+) => {{ let mut base: ::std::path::PathBuf = $base.into(); $( base.push($segment); )* base }} } fn main() { use std::{ ffi::OsStr, path::{Path, PathBuf}, }; let a = build_from_paths!("a", "b", "c"); println!("{:?}", a); let b = build_from_paths!(PathBuf::from("z"), OsStr::new("x"), Path::new("y")); println!("{:?}", b); } 

Comments

3

A normal function which takes an iterable (e.g. a slice) can solve the problem in many contexts:

use std::path::{Path, PathBuf}; fn join_all<P, Ps>(parts: Ps) -> PathBuf where Ps: IntoIterator<Item = P>, P: AsRef<Path>, { parts.into_iter().fold(PathBuf::new(), |mut acc, p| { acc.push(p); acc }) } fn main() { let parts = vec!["/usr", "bin", "man"]; println!("{:?}", join_all(&parts)); println!("{:?}", join_all(&["/etc", "passwd"])); } 

Playground

1 Comment

The tradeoff is that you cannot have multiple types in the single call.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.