Pre-Pre-RFC: / for Paths

Python implements __div__ on Path objects, so that you can say basePath / subdir (if basePath is a Path and subdir is a Path or str).

Despite my usual reservations against overloaded operators, I’d propose adding an impl Div for Path to borrow this feature.

Rationale: It reads nice, and since Path types being intermingled with mathematical operations is highly unlikely, the risk of confusion is minimal.

Is this something you’d like to see?

8 Likes

I think cases like list<T> + T or path/div make sense, but mostly as an extension. I.e. a separate crate with syntax sugar.

boost also overloads this: http://www.boost.org/doc/libs/1_58_0/libs/filesystem/doc/tutorial.html#Class%20path-iterators-etc

I found it very readable as long as variable names were long enough to not sound like mathematical identifiers.

Mad idea: a lint that enforces at least 4 characters for variables of type Path, PathBuf or references to either

Yes!

What makes this different to not overloading + for Strings, which seems to have been shot down multiple times (including just before Rust 1.0, I believe)? For the record, I do like this idea though.

I'm not sure this would work well in practice - for example, having a PathBuf called tmp for a temporary file seems pretty reasonable.

You could always call it temp :smile:

On a more serious note, looking at functions that handle IO, they are usually not very arithmetic-heavy anyway.

I don’t think a lint will be necessary.

This exact thing was just being discussed in this reddit thread. My conclusion is that some people are just picky about what ops overloads should be valid.

Two valid critique I saw in a linked RFC for a new Convert op were here and here. Other valid alternatives would be having a dedicated concat operator which would probably replace both + and this proposed / (it would also avoid the controversy these overloads bring).

Problematically, a concat operator doesn’t seem to be universally popular. Some don’t see the difference between + and concat. Others don’t want an extra operator (maybe it’s not important enough). Also += overloading seems to be another alternative for some reason but I don’t really see how it’s any different from +.

As a useless data point, just the other day I saw a tweet expressing absolute horror at this feature in Boost :slight_smile:

we could also abuse placement new: root_path <- "usr" <- "local" <- "bin" <- "something"

Misusing placement new is an advanced kind of evil, which we should forgo if a more primitive evil (misusing Div) suffices.

Also I have yet to see a filesystem where <- denotes the path separator, whereas / is common on Unices and Windows’ \ (which is already used for both string escapes and line continuation) looks similar enough. Also both Python and Java accept / instead of \ even on Windows.

I would expect every addition operation to be commutative, i.e. "asd" + "fgh" should be equivalent to "fgh" + "asd". This obviously doesn't work for strings and lists (but does for sets).

I'm not saying that this must hold for programming languages, but it might be nice. (Division for paths works surprisingly well from a mathematical standpoint.)

This is cute, but I think having operators do what they say is more important. I like the idea, but don’t think it’s appropriate for Rust.

Honestly, if I need to concat a bunch of paths, I just write a variadic macro to do it. Not quite as succinct, but plenty workable.

2 Likes

In my opinion, the real difference is that we see a lot more code that mixes string handling and arithmetic, whereas this is much less common with path handling – e.g. how often do you see code like prefix + (x + y).to_string() + suffix vs. code like prepath / &(a / b ).to_string() / sufpath`?

The Windows API itself accepts / as a separator for non-UNC paths.

That is quite nice, but I think this would require RHS = Self? Given that Add can be implemented otherwise, I'm not sure this is the best guideline (and isn't backwards-compatible, because there's a stable Add<&str> for String implementation).

No, it would only require that RHS also implements Add with an RHS that matches our Self and returns the same result.

1 Like

If there’s already stable str + str, path / path doesn’t seem like a big stretch.

1 Like

I’ve just tried to naïvely implement this in a crate, but run against the orphan rule. So to build this, I’d need to wrap std::path::Path in a struct ext::path::Path (that has much of the same interface, but also implements Div).

Also PathBuf. Even then, it wouldn’t be compatible with std::path::Path. So this exercise would be rather futile.

This seems a bit too magical to me. Maybe I’m just a kermudgeon, though.

2 Likes

Magical? Given that we have + for String, / for Path is positively pedestrian.

If it comes to RFC, I’ll note that pythoneers and people who used Boost will feel right at home, while others may think “Neat” and move on.

One drawback is that it might mislead people to try and be clever with operator overloading.

Personally I’m very hesitant when it comes to overloading operators for anything, but their original purpose. I particularly dislike abusing operator overloading to build mini DSLs, which I’d argue this is a case of.

Furthermore, if we are doing such overloading, I find it hard to argue for having different operators to append data, depending on the type. I can sort of get on board with “Adding/appending a string to another one”, and the operator being + (i.e. addition). However, why is “Adding/appending a path to another one” represented by a different operator / (i.e. division) instead? The answer is obviously that this looks familiar, but from a completely different context than Rust programming.

I’d prefer if we encapsulated such DSLs within macros, e.g.:

// Usage: path_join!(a/b/c)
macro_rules! path_join {
    ( $first:ident / $( $path:ident )/ * ) => {
        {
            let mut tmp: PathBuf = $first.into();
            $(
                tmp = tmp.join($path);
            )*
            tmp
        }
    }
}

I fully admit that this only works for the simplest cases though.