Propose new operators to reserve / include for edition 2018

I’m currently in the process of doing a review of all the keywords we might want to reserve in edition 2018. However, so far, there has not been much consideration of new operators we might want to introduce in the edition. Therefore, I’d like to hear from y’all on what new operators you would like to see included in the language.

Propose anything you like, no idea is too crazy. We will do a review of the proposals later and keep the stuff that needs most fixing and makes most sense as operators.

A few notes:

  • Operators desugar into traits in Rust. a + b becomes Add::add(a, b) for example. Please try to include a trait in your proposal.
  • Please include an example of the usage that makes the semantics reasonably clear, this will be necessary for a fair review :wink:
  • Operators should be general in the sense that they should benefit the community as a whole and many Rust libraries and programs.
  • Proposing domain specific operators is OK but think of how it can be generalized to more domains.

PS: To keep the thread somewhat overviewable, try to not debate specific proposals here too deeply :wink: This thread is more for collecting operator suggestions – if you want to delve into a specific proposal a lot and discuss pros and cons, I recommend you to open a dedicated thread for that.

1 Like

@varkor proposed the %% operator here: (Mathematical) modulo operator

5 Likes

Just a note that if a specific operator for Euclidean modulo was added, it needs to have an accompanying division operator. It was pointed out that %% wasn’t the best choice because // could not be used as the corresponding division operator. Some other variation on % would be reasonable, though.

1 Like

Ideas? ^,-

Perhaps include in-line Regex operators ala Perl:

  • Trait (RegexMatch)

    • =~ (matches RegEx) RegexMatch::Matches( a, b ) where a is string, b is regex
    • !~ (does not match RegEx) RegexMatch:NotMatches( a, b )
  • Trait: (QuoteRegex)

    • r(regex) (an in-line quoted Regex) QuoteRegex::QuotedRegex( regex ) -> Regex

This probably requires some more thought, and I’m not even sure the r`` quoting syntax could reasonably qualify as an operator.

1 Like

Python 3 has the @ operator used for matrix multiplication, etc. But I am not sure it’s necessary in Rust.

Some old languages have a max/min operator, that allow to express “a = max(a, b);” in a shorter way. But I don’t think this is needed in Rust.

An operator that could be useful in Rust is similar to the old apply() function of Python, that spats a tuple as arguments of a function, currently in Python you use a star:

def foo(x, y): pass
args = (1, 2)
foo(*args)

Other operators that could be useful in Rust is the function composition (like the . (dot) of Haskell), or the operators like |> of F#.

But in the end the operator that I miss mostly in Rust is similar to the $ of D language that denotes the length of an array:

my_arr[.. $ - 2]

This was discussed in past in more detail, for Rust you could use # instead of $. Rust should offer a way to overload it for library-defined tensor-like data structures (generally each dimension has a different length).

1 Like

I’d love to see a function composition operator, but I’m unsure of how to map it directly to a trait in a way that would be flexible enough. Perhaps someone with more trait experience could comment one way or another on the feasibility of this kind of operator.

1 Like

This can be done without an operator by simply allowing negative indexing, like Python.

Negative indexing is not efficient. D $ operator solves the problem.

3 Likes

In Rust an operator for function composition is not practical, Rust has too many details that cause friction.

Uh, what? How does syntax affect efficiency?

Not exactly a new operator, but there are frequent discussions about reviewing the design of various existing operators, including the Index traits, so that they can return arbitrary proxy objects instead of being forced into returning references to concrete values.

Might not be sufficiently fleshed out to make the 2018 deadline, however.

2 Likes

PS: To keep the thread somewhat overviewable, try to not delve too deep into specific proposals here :wink: This thread is more for collecting operator suggestions – if you want to delve into a specific proposal a lot and discuss pros and cons, I recommend you to open a dedicated thread for that.

2 Likes

Some parts of D language are well designed. Syntax here denotes a different semantics compared to negative indexing. If you allow negative indexing to denote from-the-end you have to pay tests for every indexing operation.

Having a better Index trait could be good…

Could we do something like:

trait Apply<T, U, V> where T: FnOnce(U) -> V {
  fn apply<T, U, V>(f: T, arg: U) -> V {
    f(arg)
  }
}

and then have a |> b desugar to Apply::apply(b, a)?

3 Likes

But what is the semantic difference between […-2] and [..-2]? Don't they both mean "get the length of the array, and subtract 2"? (I'm double checking some D documentation and so far everything says it's just sugar; doesn’t seem to get magical unless opDollar is overridden to do magic)

I’m aware this is technically a tangent, but this isn’t about debating the pros and cons of $. It’s about “why does this have anything to do with operators?”. As currently written the suggestion doesn’t even make sense to me, and making sense is definitely “necessary for a fair review”.


EDIT: After the response below it does make sense to me. The detail I was missing is that Python-like indexing needs an additional runtime branch just to determine if a[b] is doing positive indexing or negative indexing, and having distinct syntax for negative indexing lets us avoid that. Tangent resolved.

This part of the D design isn’t stupid, it’s good for a system language, and better than Python.

Let’s assume you have an array (here a slice) arr, you don’t know its length at compile-time, and you want to return the eighth-to-last item of the slice.

In Python you write something that approximatively looks like:

>>> arr = [10 * i for i in range(12)]
>>> arr
[0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110]
>>> arr[-8]
40

In Rust you currently could write:

fn foo(arr: &[u32]) -> u32 {
     arr[arr.len() - 8]
}

fn main() {
    let arr: Vec<_> = (0 .. 12).map(|i| 10 * i).collect();
    println!("{:?}", arr);
    println!("{:?}", foo(&arr));
}

My proposal is to allow to write something like:

fn foo(arr: &[u32]) -> u32 {
     arr[# - 8]
}

I think you are suggesting to not use that syntax and to write instead:

fn foo(arr: &[u32]) -> u32 {
     arr[-8]
}

How can the Rust language implement this? In general the index is not a compile-time constant. So the function that implements the [] indexing must test if the input index value is negative, and in that case add to it the length of the array/slice. If it’s positive it must not add the length. And then it has to use this resul to index the memory. So if we use the Python syntax we have to pay one if test for each access to the array. The D syntax avoids such run-time cost.

With a friend we created ShedSkin, it was a Python->C++ compiler, it has a switch to tell the compiler that you will not use negative indexes in your Python code, to increase performance… :slight_smile: So it’s a significant problem, that D avoids.

Personally I would reserve a set of checked arithmetic operators +?, -?, *?, /?, %? with their traits : TryAdd, TrySub, TryMul, TryDiv, TryMod

For instance with TryAdd :

pub trait TryAdd<RHS = Self> {
    type Output;
    type Error;
    fn try_add(self, rhs: RHS) -> Result<Self::Output,Self::Error>;
}

value1 +? value2 would be a sugar to value1.try_add(value2)?.

These operators would be especially useful, if constant generics improve to support the whole Pi type proposal so Rust could provide ranged integer types. “Classic arithmetic operators” would be compile time checked to never overflow or divide by zero, while “Try arithmetic operators” would be available when a runtime check is necessary.

9 Likes

Rather than any specific operators, can we look at the restrictions that would require us to reserve them in the first place? For example, the raw identifiers RFC found out that we couldn’t do ident#catch because of macros. Maybe we can force whitespace in macros in these situations in 2018? (And thus make all raw-string-like constructs reserved to open the possibility space for things like string#"foo" or big#1234567890987654321 in future – though I’m not actually proposing either of those at this time.)

One concrete thought: Ord::cmp(&a, &b) is a big jump from a < b. Maybe we can get the spaceship operator <=> for that? (Dunno if it should be cmp or partial_cmp, though…)

6 Likes