# relative-path
[](https://github.com/udoprog/relative-path)
[](https://crates.io/crates/relative-path)
[](https://docs.rs/relative-path)
[](https://github.com/udoprog/relative-path/actions?query=branch%3Amain)
Portable relative UTF-8 paths for Rust.
This crate provides a module analogous to [`std::path`], with the following
characteristics:
* The path separator is set to a fixed character (`/`), regardless of
platform.
* Relative paths cannot represent a path in the filesystem without first
specifying *what they are relative to* using functions such as [`to_path`]
and [`to_logical_path`].
* Relative paths are always guaranteed to be valid UTF-8 strings.
On top of this we support many operations that guarantee the same behavior
across platforms.
For more utilities to manipulate relative paths, see the
[`relative-path-utils` crate].
## Usage
Add `relative-path` to your `Cargo.toml`:
```toml
relative-path = "1.9.2"
```
Start using relative paths:
```rust
use serde::{Serialize, Deserialize};
use relative_path::RelativePath;
#[derive(Serialize, Deserialize)]
struct Manifest<'a> {
#[serde(borrow)]
source: &'a RelativePath,
}
```
## Serde Support
This library includes serde support that can be enabled with the `serde`
feature.
## Why is `std::path` a portability hazard?
Path representations differ across platforms.
* Windows permits using drive volumes (multiple roots) as a prefix (e.g.
`"c:\"`) and backslash (`\`) as a separator.
* Unix references absolute paths from a single root and uses forward slash
(`/`) as a separator.
If we use `PathBuf`, Storing paths in a manifest would allow our application
to build and run on one platform but potentially not others.
Consider the following data model and corresponding toml for a manifest:
```rust
use std::path::PathBuf;
use serde::{Serialize, Deserialize};
#[derive(Serialize, Deserialize)]
struct Manifest {
source: PathBuf,
}
```
```toml
source = "C:\\Users\\udoprog\\repo\\data\\source"
```
This will run for you (assuming `source` exists). So you go ahead and check
the manifest into git. The next day your Linux colleague calls you and
wonders what they have ever done to wrong you?
So what went wrong? Well two things. You forgot to make the `source`
relative, so anyone at the company which has a different username than you
won't be able to use it. So you go ahead and fix that:
```toml
source = "data\\source"
```
But there is still one problem! A backslash (`\`) is only a legal path
separator on Windows. Luckily you learn that forward slashes are supported
both on Windows *and* Linux. So you opt for:
```toml
source = "data/source"
```
Things are working now. So all is well... Right? Sure, but we can do better.
This crate provides types that work with *portable relative paths* (hence
the name). So by using [`RelativePath`] we can systematically help avoid
portability issues like the one above. Avoiding issues at the source is
preferably over spending 5 minutes of onboarding time on a theoretical
problem, hoping that your new hires will remember what to do if they ever
encounter it.
Using [`RelativePathBuf`] we can fix our data model like this:
```rust
use relative_path::RelativePathBuf;
use serde::{Serialize, Deserialize};
#[derive(Serialize, Deserialize)]
pub struct Manifest {
source: RelativePathBuf,
}
```
And where it's used:
```rust
use std::fs;
use std::env::current_dir;
let manifest: Manifest = todo!();
let root = current_dir()?;
let source = manifest.source.to_path(&root);
let content = fs::read(&source)?;
```
## Overview
Conversion to a platform-specific [`Path`] happens through the [`to_path`]
and [`to_logical_path`] functions. Where you are required to specify the
path that prefixes the relative path. This can come from a function such as
[`std::env::current_dir`].
```rust
use std::env::current_dir;
use std::path::Path;
use relative_path::RelativePath;
let root = current_dir()?;
// to_path unconditionally concatenates a relative path with its base:
let relative_path = RelativePath::new("../foo/./bar");
let full_path = relative_path.to_path(&root);
assert_eq!(full_path, root.join("..\\foo\\.\\bar"));
// to_logical_path tries to apply the logical operations that the relative
// path corresponds to:
let relative_path = RelativePath::new("../foo/./bar");
let full_path = relative_path.to_logical_path(&root);
// Replicate the operation performed by `to_logical_path`.
let mut parent = root.clone();
parent.pop();
assert_eq!(full_path, parent.join("foo\\bar"));
```
When two relative paths are compared to each other, their exact component
makeup determines equality.
```rust
use relative_path::RelativePath;
assert_ne!(
RelativePath::new("foo/bar/../baz"),
RelativePath::new("foo/baz")
);
```
Using platform-specific path separators to construct relative paths is not
supported.
Path separators from other platforms are simply treated as part of a
component:
```rust
use relative_path::RelativePath;
assert_ne!(
RelativePath::new("foo/bar"),
RelativePath::new("foo\\bar")
);
assert_eq!(1, RelativePath::new("foo\\bar").components().count());
assert_eq!(2, RelativePath::new("foo/bar").components().count());
```
To see if two relative paths are equivalent you can use [`normalize`]:
```rust
use relative_path::RelativePath;
assert_eq!(
RelativePath::new("foo/bar/../baz").normalize(),
RelativePath::new("foo/baz").normalize(),
);
```
## Additional portability notes
While relative paths avoid the most egregious portability issue, that
absolute paths will work equally unwell on all platforms. We cannot avoid
all. This section tries to document additional portability hazards that we
are aware of.
[`RelativePath`], similarly to [`Path`], makes no guarantees that its
constituent components make up legal file names. While components are
strictly separated by slashes, we can still store things in them which may
not be used as legal paths on all platforms.
* A `NUL` character is not permitted on unix platforms - this is a
terminator in C-based filesystem APIs. Slash (`/`) is also used as a path
separator.
* Windows has a number of [reserved characters and names][windows-reserved]
(like `CON`, `PRN`, and `AUX`) which cannot legally be part of a
filesystem component.
* Windows paths are [case-insensitive by default][windows-case]. So,
`Foo.txt` and `foo.txt` are the same files on windows. But they are
considered different paths on most unix systems.
A relative path that *accidentally* contains a platform-specific components
will largely result in a nonsensical paths being generated in the hope that
they will fail fast during development and testing.
```rust
use relative_path::{RelativePath, PathExt};
use std::path::Path;
if cfg!(windows) {
assert_eq!(
Path::new("foo\\c:\\bar\\baz"),
RelativePath::new("c:\\bar\\baz").to_path("foo")
);
}
if cfg!(unix) {
assert_eq!(
Path::new("foo/bar/baz"),
RelativePath::new("/bar/baz").to_path("foo")
);
}
assert_eq!(
Path::new("foo").relative_to("bar")?,
RelativePath::new("../foo"),
);
```
[`None`]: https://doc.rust-lang.org/std/option/enum.Option.html
[`normalize`]: https://docs.rs/relative-path/1/relative_path/struct.RelativePath.html#method.normalize
[`Path`]: https://doc.rust-lang.org/std/path/struct.Path.html
[`RelativePath`]: https://docs.rs/relative-path/1/relative_path/struct.RelativePath.html
[`RelativePathBuf`]: https://docs.rs/relative-path/1/relative_path/struct.RelativePathBuf.html
[`std::env::current_dir`]: https://doc.rust-lang.org/std/env/fn.current_dir.html
[`std::path`]: https://doc.rust-lang.org/std/path/index.html
[`to_logical_path`]: https://docs.rs/relative-path/1/relative_path/struct.RelativePath.html#method.to_logical_path
[`to_path`]: https://docs.rs/relative-path/1/relative_path/struct.RelativePath.html#method.to_path
[windows-reserved]: https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx
[windows-case]: https://learn.microsoft.com/en-us/windows/wsl/case-sensitivity
[`relative-path-utils` crate]: https://docs.rs/relative-path-utils