Pre-Pre-RFC: / for Paths

I write Python and I’ve never had a problem sticking with os.path.join.

I’m pretty lukewarm on this. I’m fearful of the precedent it would set (or, more precisely, fearful of strengthening an existing precedent), and frankly, the utility seems marginal at best. Don’t get me wrong, it’s definitely cool, but that isn’t a compelling enough reason for me personally.

2 Likes

So I think concatting paths with / is awesome and I love the idea of a language that does that. But I think this feature of Swift is awesome: you can overload any character in the Mathematical Operators, Miscellaneous Symbols, and Dingbats blocks. I am an operator overloading radical.

I just don’t know if Rust loves operator overloading. The standard syntax of Rust is fairly complicated on its own without throwing in magic overloaded operators. I don’t think this feature feels very Rusty.

However, I definitely think that if this is included, the typing should absolutely be impl<P: AsRef<Path> Div<P, Output=PathBuf> for Path.

Hi, I’m a C++ developer working with code that uses boost filesystem. Actually, I’m working on such code right now on my second screen…

I really dislike the use of the / operator as a path concatenation in boost. I find it very annoying to work with. Having a division symbol out of nowhere is kind of perturbing. I hear the argument that / is the same symbol used for paths delimiters, but keep in mind that the full expressions will not look like filesystem paths at all. They will mostly look like normal lines of code with a division symbol thrown in!

Here is some real world code in my codebase:

current = fs::current_path() / "Untitled";
inCurrentDir = fileDirRelativeTo / absFile.filename().generic_string();
fs::path inCurrentDir(fileDirRelativeTo / relativeFile.filename());
2 Likes

I rather dislike these operator features. I’m not a fan in general, for all the usual reasons, but here are some Rust-specific reasons:

  • When I see +, /, etc., I don’t think “allocation”.
  • When I write a function taking a value implementing Add or Div, I’m not really planning on allowing strings or paths.

All in all, I’d be more in favor of a consistent concatenation operator that can be overloaded, and is always rather clear.

3 Likes

This example made something click: in the context of strings I associate / (divide by?) with split not with concatenation.

"a,b,c" / ',' == ["a", "b", "c"]

So I guess it's a :-1:

1 Like

I'm totally with the argument about taking a bound generic. My vote is now for don't do this. Use DSLs inside macros instead.

Not really, it makes the allocation explicit. Like when appending an element to a Vec: vector <- value; desugars to vector.push(value); after an inlining or two

I’m against further binary operator overloads like this, in particular because of the Path vs PathBuf asymmetry and confusion for users in &Path vs &PathBuf vs PathBuf.

Right now we allow String + &str but neither &str + String nor String + String, let’s not replicate that further until we have a solid solution.

1 Like

:-1: as well. path.join(other) is instantly understandable, where as path / other is not. If this were to be implemented, I'd have to add a lint denying this in my own crates, just for my sanity.

It's also quite confusing that someStr + "foo" mutates the original someStr. I would never have expected that, from any other language I've used.

It doesn't mutate it. It takes the String by value and returns a String. It's quite neat how that works out, to easily allow buffer reuse and O(n) string concat with +, but the details really are opaque to newcomers to rust.

We can't call it mutation since it moves the variable. Of course in the implementation, it takes the string, reuses it (pushes to the end) and returns it, but in Rust semantics it consumes (String, &str) and produces String.

This is really a strength of rust's built-in move semantics, but asymmetry also makes it confusing.

Yes, the move prevents the mutation from affecting much. Still, other languages’ usage of string concatenation make this seem like the expected: &str + &str -> String.

So we shouldn’t add / for parhs, and deprecate + for String instead?

Edit: I’ve added an issue to clippy to at least warn of the pitfall of Add::add for String consuming self.

Even if we don’t deprecate Add for String, the error message could perhaps be specialized for this case (see this example).

1 Like

i suggest that all naysayers should actually try it for a while.

it’s very intuitive and pretty to use, and since path operations are rarely intermingled with arithmetic, there’s also no confusion in practice.

i was initially sceptical in python, but home_path.join("foo", "bar") now feels clunky for me compared to home_path / "foo" / "bar"

I totally love the syntax and simplicity. But it just doesn’t feel correct to allow a path to be passed to fn test<T: Div>(t: T) -> <T as Div>::Output. Div definitely sends the wrong signal.

How about allowing multiple traits for the same operator? xD

@Ilogiq I’d be fine with the deprecation if not for the fact that it would be completely spurious and spiteful.

We’ve stabilized questionable things. Oh well. The world moves on.

2 Likes

I’m sorry for bringing up String addition, but it does demonstrate a problematic asymmetry. The bar is set, any further operators must do better than that.

We don’t actually need to deprecate it. I plan to add a lint to clippy that at least warns on x = x + ... for x: String, suggesting push_str(...) instead.

Perhaps the compiler can include it later on.

I feel we’re getting a bit of track here. I agree that using a Path (or PathBuf for that matter) in place of a Div trait object could lead to confusion.

However, this hasn’t stopped us from having Add on String, with the same consequences (except that Add is probably much more widely used than Div.

I suggest that we either follow through and (besides adding Div to Path) impl Zero for Add where the zero be an empty string (that way, we could sum over strings) or deprecate Add for String altogether. Anything else is a half-solution.

I instinctively strongly disagree with this line of argumentation, but for some time couldn’t figure out why exactly. Having though about this for a while I think the gist is the following:

(u32, +) is a semigroup, so is string concatenation (String, +). As a result supporting Add on strings makes some sense. Some (many?) algorithms generic over Add will actually work on strings, and provide a sensible result (cf. your own sum example. If I’m not mistaken a slightly adapted implementation of sum could actually get away without requiring Zero).

However, (u32, /) is not a semigroup. Algorithms generic over Div are unlikely to ever yield a sensible result applied to a Path. On the other hand joining Paths is a semigroup, which behaves almost identically to string concatenation.

As a result of that line of though, you might actually be able to convince me that Paths should have an operator overload for joining them, but that operator would have to be +, so it gets the same “benefits” that String does.

4 Likes

It's not a semigroup in Rust as it is now. There is no string type S where S + S is defined. (post higher up).

In the algebraic sense, of course you can use the Default trait in place of Zero -- it happens to fit.

2 Likes

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.