Propose new operators to reserve / include for edition 2018

Note that "foo"_string and 1234567890_big are still reserved, there's no need to introduce ident#tt now.

I think that the obvious and extremely compelling operators exist and adding new operators would only benefit specific domains. While I can see the convenience (I do like =~ above, but the regex crate is outside stdlib), operator overloading quickly becomes very confusing.

Even though I’m against adding new operators in general, I also think there is a compelling case for allowing libraries to define their own domain-specific operators and the traits that correspond to them.

4 Likes

I think that the obvious and extremely compelling operators exist and adding new operators would only benefit specific domains.

I'm pretty much with you (even though I suggested =~ above), I really don't think there is much use for a lot of esoteric operators. Even something like =~ seems of extremely limited utility.

It might be helpful to list first chars (or sequences of chars) that can't possibly be used as operators or operator prefixes given the current grammar. Then we could limit discussion to which remaining chars or sequence of chars could reasonably be used for operators without completely breaking the current grammar. From that, we might be able to spot some combinations/sequences that suggest useful operators to reserve.

My preference would be to pretty much stick to operators that have a mathematical meaning (though that is open to interpretation obviously). Another possibility would be to consider a single character or opening/closing characters that can be used to delimit custom operators. For example, all "Custom Operators" would be of the form:

  • ~OP~ or just ~OP

where OP could be any sequence of characters [A-Za-z!@#$%^&*()-_+={}|;:'"<>,.?/`] or something like that (not sure if we'd want OP to be permitted to be that arbitrary or not.

So, you could possibly define operators like:

  • Floor Function: ~_~ or ~floor~
  • Ceiling Function: ~^~ or ~ceil~
  • Determinant (Matrix): ~||~ or ~det~
  • Dot Product: ~.~ or ~dotp~
  • Cross Product: ~x~ or ~xprod~
  • Factorial: ~!~ or ~fact~
  • Sequence Sum: ~++~ (aka Sum elements of an iterator) or ~sum~
  • Sequence Product: ~**~ or ~prod~

You get the idea.

It would be nice to be able to have some syntax for pre-fix vs post-fix vs infix operators. Something like:

  • Pre-fix Operator: ~OP>~
  • Post-fix Operator: ~<OP~
  • In-fix Operator: ~OP~

Of the above, I actually would prefer the spelled out versions so they are kind of "infix" function calls rather that operators perse (although that's all operators are after all). I would find ~xprod~ much more readable than ~x~ for example, and not really that much more to type. Also, this would allow easy mapping to custom traits (or easier). So ~xprod~ would be the trait, "Op_xprod" with the function "xprod" or something along those lines.

Honestly though, I really don't see this being useful. I'm having a real hard time seeing how this doesn't result in unreadable code and would just be a mess. Perhaps someone smarter than me can see how to make something useful out of this idea.

Another issue with custom operators is how to usefully handle precedence. One idea would be to have a set of pre-defined "precedence groups" that correspond to the base/current operator precedence levels. No new levels could be defined. A customer operator/trait could be assigned a precedence of one of the predefined levels (through annotation/attribute). Again, though, I have a hard time imagining how this wouldn't be a confusing mess.

EDIT: On further thought, since operators are just short-hand notation for in-fix/pre-fix/post-fix function calls, we could generalize as follows (assuming ~OP~ could work with the grammar OK without serious ambiguities)

For any function impl (including trait impl funcs) 'fn' we would have

  • fn( a, b )
    • a ~fn~ b (in-fix binary)
  • fn( a )
    • ~fn>~a (pre-fix op)
    • a~<fn~ (post-fix op)
  • fn( a, b, c )
    • a ~fn?~ b ~:~ c (ternary in-fix op)

How useful? Questionable.

2 Likes

I’d like to revive the old idea for an operator for parameter conversion (couldn’t find the thread/issue where it was originally hinted at): fn do_something_with_a_path(path: ~&Path) means fn do_something_with_a_path(path: &impl AsRef<Path>) + an implicit as_ref() call conceptually in the function body, but monomorphised in a way that expands the as_ref() call on the call site or in a separate shim, to prevent monomorphization bloat.

Edit: To possibly make it more useful, maybe it would be nice to support into() for owned values and as_ref() for references.

Edit 2: Found the original idea, it was mentioned as a future extension in the Conversion Trait RFC by @aturon : https://github.com/rust-lang/rfcs/blob/master/text/0529-conversion-traits.md#an-aside-codifying-the-generics-pattern-in-the-language

1 Like

Can we add ++ and maybe ++= as concatenation operators?

3 Likes

Swift’s wrapping operators: &+/&-/&*, etc.

wrapping_add() and casting to Wrapping are verbose and/or inconvenient.

5 Likes

As long as we’re talking about extending slices, lemme throw in ..+ and ..- for relative slicing: a[n ..+ 3] means elements n, n+1, and n+2, and a[n ..- 3] means either elements n-3, n-2, and n-1 or else n-2, n-1, and n (whichever is felt to be more often useful; I don’t have an intuition either way).

These would desugar as new constructors for the Range trait, not as entirely new traits.

The existing code most likely to be broken is constructs of the form v[1..-1], where the parse changes from v [ 1 .. -1 ] to v [ 1 ..- 1 ], but the discussion above about negative indices gives me the impression that they don’t do anything useful right now, so maybe it isn’t much of a risk.

2 Likes

Non-truncating as operator (converting only to a larger type). Perhaps as? Type or @Type.

It’s scary that foo as usize may or may not lose bits of precision, depending on compilation target, and there’s no warning about it. Most of the time it can’t be replaced with .into() without making things too vague for the type inference.

6 Likes

$() in patterns for matching against a value of an expression:

let x = 5; 

match y {
   $(x) => println!("it's 5"),
   $(x+1)...10 => println!("it's between 6 and 10"),
}

match Enum::Foo as u32 {
   $(Enum::Start as u32)...$(Enum::End as u32) 
}
1 Like

I like the idea, but then how to treat the same value in different clauses?

For example this:

let x=5;
let y=5;
match value{
  $(x) => println!("first"),
  $(y) => println!("second"),
  _ => println!("other"),
};
From:

let n = f64 <| 123;

TryFrom:

let ip = Ipv4Addr <? "127.0.0.1";

1 Like

In my opinion the unsafety of “as” was a design hole of the original Rust 1.0 design, and I believe it should be fixed. A safe language should offer reliable lossless conversions with default nice short syntax, and lossy only with a longer syntax :slight_smile:

3 Likes

A syntax to denote the length of a slice is very commonly useful (and once you can overload it, it's really handy for a numeric library).

1 Like

Note that x..-y parses today as (x..) - y, which you can make compile with impl Sub<Foo> for RangeFrom<Foo> -- see the bikeshed that was ..= syntax.

Edit: this is wrong; see below.

Something I wrote the other day:

assert!(a < x < b);

But I guess ternary operators are not so trivial to parse / desugar.

Edit: it’s not so much a ternary operator as a chained [comparison] operator, i.e. a <= b < c < d should also be valid.

2 Likes

It doesn't parse as (x) .. (-y) ? That surprises me. This is not something that's super consistent across languages, overall, but both R and Python have the precedence of the slice-construction operator be below unary + and -.

I guess that means x..a+b parses as (x..a) + b? Which is what many languages do, but IME almost never what a programmer wants.

You're completely right; what I said was wrong.

Curiously, it seems that a.. + b is actually a parse error, though (a..) + b works: Rust Playground

We could switch from .. to ..< for half open range and from ..= to ... for closed/inclusive range. IMO, Swift got this right.

1 Like

This was bikeshedded to death; let's please not reopen it.

6 Likes

I understand the sentiment and I’m not going to push for any changes to stable syntax. The only reason I feel the need to mention it is because changes to stable syntax were previously unthinkable. Now they’re not. Please note my careful wording: “could.”

EDIT: Apologies if this was discussed after the epoch RFC was accepted and I didn’t see it.