rust-lang / rfcs

RFCs for changes to Rust
https://rust-lang.github.io/rfcs/
Apache License 2.0
5.91k stars 1.57k forks source link

Custom operators #818

Closed iluuu1994 closed 7 years ago

iluuu1994 commented 9 years ago

Original issue

There's some controversy over custom operators and them being overused in cases that don't really make sense and how they can be more confusing than helpful. I personally love them and think that they, when used wisely, can really make certain APIs much more appealing.

Rust allows you to overload a few basic operators using traits defined in the standard library. That's great but you're very limited to what you can actually do with them.

I'd like to point you to Swift here. They have solved this problem rather nicely. You can implement operator functions and define some properties on them, like:

Here's how Rust could look defining a custom null coalescing operator:

op ?? {
    assoc: left,
    prec: 100,
}

infx fn ??<T>(l: Option<T>, r: T) -> T {
    match l {
        Some(s) => s,
        None => r,
    }
}

I haven't given too much thought to the example above, it's probably suboptimal and doesn't play too well with the existing syntax. So it's just an example.

Basically, I'm interested in hearing what other people would think of more generic operator overloading functionality in Rust.

As I said before:

Btw Rust is super awesome :D :metal:

Fantastic work guys

glaebhoerl commented 9 years ago

It would make sense to title this issue "custom operators" or "user-defined operators" or something of that nature instead of "operator overloading".

cameron-martin commented 8 years ago

Is this being considered at all?

KalitaAlexey commented 8 years ago

@cameron-martin Could you provide example when you need custom operator and simple function couldn't help you? I am against custom operators because it is complex.

Centril commented 8 years ago

The main problem with custom operators, which is quickly experienced if you've written any haskell is that the symbols included in the operator identifier does not usually inform the meaning of what the operator does, unless it is picked very judiciously. Standard operators used in mathematics are so often used that everyone knows their meaning instantly. A function which has its identifier written in English conveys the meaning of what it does much more clearly.

Instead of adding custom operators, another beneficial idea would be to allow infix calls to functions like so:

a `dot` b

to convey scalar product of two vectors a and b.

Which is converted to

a.dot( b )

or, depending on the function:

dot( a, b )

This also has the benefit of being similar to another language (haskell).

ticki commented 8 years ago

I have a strong dislike for arbitrary user-defined operators. Essentially, you end up with each library providing an entirely different language. People quickly abuse this kind of stuff because it "seems nice at first", but it gets a mess for the library user. Parsing it is pretty much impossible too.

It is worth noting that Rust already got operator overloading, which covers the majority of cases.

Fantastic work guys

... and gals.

alilleybrinker commented 8 years ago

As an example of what sort of excess can occur with user-defined operators, look at Haskell's Lens library. It's a fantastically useful library with an complex and opaque sub-language of special operators in it. Attempting to read Lens code without the operator reference open is a task only for the library's most experienced users. Rust already has complex syntax. I don't think more complexity is a good idea.

The notion of infix operators is interesting, though I am not sure how much they get you over methods. My general instinct says to leave the syntactical complexity alone unless you have a good reason.

davesque commented 7 years ago

I'll have to put in my vote against this idea. Building on @AndrewBrinker's comment, I feel Haskell has demonstrated exactly why this kind of feature usually doesn't belong in a language. The idiom of custom operator use in Haskell is a barrier to entry, hands down. I've often wondered to myself what a language with exactly the same features as Haskell (but minus the annoying and opaque syntax) would look like and if it wouldn't gain broader acceptance. If this kind of thing ever does find its way into Rust, it would have to be in the most sparing and sober way possible.

ticki commented 7 years ago

Haskell went through so much pain, with the only advantage of having a mostly useless feature. In fact, it tend to be abused and makes everything a mess.

The infix function is more sane, but I'm not sure if it is needed enough to be worth adding.

burdges commented 7 years ago

I think custom syntax can help with mathematics, well crypto crates love #![allow(non_snake_case)], but custom operators rarely help much.

Instead I'd look more towards the units crate which uses wrapper structs heavily. I think more important tools include:

  1. It'd rock if side effects could be exposed well enough by a bignum library that DivRem or similar can be optimized correctly from separate / and % calls. You do not want those all those pretty binary operators killing performance!

  2. We do not make up many so many operators in mathematics, but instead dance between several different objects in which we envision an element living. Arguably that perception shift corresponds best to applying wrapper structs that change how existing operators apply. I'd think painless delegation would let map change these wrapper structs more easily.

  3. A good REPL would let people experience using Rust in a more dynamic way.

davesque commented 7 years ago

@ticki @Centril While I don't like custom operators, I actually agree that infix invocation could be useful. I've even enjoyed using languages that also have prefix and suffix invocation (as with Haskell's $ operator and Mathematica's @ and // operators). But I think it would need to be an experimental feature and would need to go through an extensive trial period to prove that abuse of it would not become idiomatic. Since Rust (IMHO) already has a lot of syntax, it's a bit risky introducing features like this.

glaebhoerl commented 7 years ago

Method syntax is infix invocation.

iluuu1994 commented 7 years ago

I'm closing this RFC as most people seem to be aganist this idea. I admit that in general the usefulness of this feature is limited. I was writing parser combinators which is one of the few times custom operators could be useful.

typesanitizer commented 6 years ago

Just ran into this issue right now. I'm working on an application and I wanted to have a trait called PanickingAdd which panics on overflow in both debug and release mode (I'm working with relatively small numbers). It would be nice if I could write a +! instead of panicking_add everywhere. Or +? instead of checked_add. Sure I could overload + for my own types to always panic on overflow but then the semantics would not be consistent with that of the standard library (panic in debug and wrap in release).

Addressing some of the points made here:

  1. Purescript has a solution where operators are only allowed as aliases for functions. This solves the problem of "I want to use this library's API but I don't like their use of operators".

  2. The lens library in Haskell uses a lot of operators: Not everyone likes this and people who don't like to use them will not use them.

  3. I feel that there are two related but distinct sides to the discussion here -- libraries and applications.

    1. In commonly used library crates, it would be strange if there were a large number of arcane operators. However, judicious use can be a net positive. For example, Conduit has 1 main operator .| that describes pipelines, analogous to the shell pipe. (It has other operators too but they're deprecated).
    2. In applications, if you're writing any kind of EDSL, not having custom operators with user-defined fixities can make writing simple code unnecessarily verbose.
  4. Operators can serve as a barrier to entry - Perhaps one can adopt a policy that the standard library and nursery crates do not add operators without RFCs on a case-by-case basis.

blackhole89 commented 5 years ago

To add another voice in favour and a use case that is not parser combinators, I have been involved in a number of ML-family projects that make extensive use of a "pipeline" operator |> (left-associative, low precedence, and having type t->(t->s)->s). This allows expressing code that, in C-family idiom, would take a shape like fn4(a,b,c,fn3(d,fn2(e,f,fn1(g,h,input)))) in the arguably much more legible form input |> fn1 g h |> fn2 e f |> fn3 d |> fn4 a b c.

Of course, its utility is greatly diminished when there is no good language support for binding all but one of a function's arguments (in the ML family, this is just achieved by ubiquitous currying and careful API design putting the argument that most likely wants to be "piped through" last), but I think it is a great example of a case where custom operators enable API design that palpably improves code quality.

(Regarding the lens counterexample: The lenses library is admittedly terrible in that regard, but I'm not sure if this is really a consequence of custom operators rather than the intrinsic awkwardness of implementing lenses in a language like Haskell. Would an alternative version that only uses normal functions actually be better?)

SOF3 commented 5 years ago

A point not mentioned here is that macro-by-example (macro_rules!) can cover the case for specific context. If a general purpose crate is introducing a new operator, it is modifying the language and should not be done in a crate (especially considering how likely it is for multiple such crates to collide). On the other hand, if a discipline-specific crate wants to introduce a new operator, its relevant code is usually restricted within specific blocks. So for crates that want to provide an API akin o a specific area, a preprocessing macro would be nice. (For example, someone wrote a crate that replaces Rust's {} with off-side syntax, but everyone knows how bad such a drastic change to the language would be like, but it totally makes sense to do this in a specific block if for whatever valid reason someone wants to write inline python code).

@blackhole89's concern about the |> operator sounds like something that can be alternatively done in an and_then syntax (especially if they are futures).

nicowilliams commented 4 years ago

In Haskell, custom infix operators are used everywhere. It's true, after a while there are so many it's hard to keep track of them mnemonically, but still, being able to add new operators is often incredibly useful. As long as it is clear what crate/module a custom operator comes from, readers can find the docs and figure out the semantics of any unusual, non-obvious custom operators.

I strongly recommend reopening this RFC.

SOF3 commented 4 years ago

In Haskell, custom infix operators are used everywhere. It's true, after a while there are so many it's hard to keep track of them mnemonically, but still, being able to add new operators is often incredibly useful. As long as it is clear what crate/module a custom operator comes from, readers can find the docs and figure out the semantics of any unusual, non-obvious custom operators.

I strongly recommend reopening this RFC.

Do you have a particular use case not mentioned above? If no new point is made, we would end up repeating the conversation above.

00shiv commented 3 years ago

I strongly recommend re-opening this RFC too. In specialized fields custom operators are well-understood within that community already. Replacing these with long names would reduce readability. Yes, newbies will have to learn this new "language" but that is true if you look at any highly specialized field. Language designers should not circumscribe this needlessly. If a particular user wants to give an "English name" to a particular operator nothing in Rust prevents them from doing so. But the opposite is not true currently.

SOF3 commented 3 years ago

I strongly recommend re-opening this RFC too. In specialized fields custom operators are well-understood within that community already. Replacing these with long names would reduce readability. Yes, newbies will have to learn this new "language" but that is true if you look at any highly specialized field. Language designers should not circumscribe this needlessly. If a particular user wants to give an "English name" to a particular operator nothing in Rust prevents them from doing so. But the opposite is not true currently.

How do macros not solve your problem? Please read the discussion above. In particular:

If a discipline-specific crate wants to introduce a new operator, its relevant code is usually restricted within specific blocks. So for crates that want to provide an API akin o a specific area, a preprocessing macro would be nice. (For example, someone wrote a crate that replaces Rust's {} with off-side syntax, but everyone knows how bad such a drastic change to the language would be like, but it totally makes sense to do this in a specific block if for whatever valid reason someone wants to write inline python code).

martial-plains commented 1 year ago

How about having the custom operators be implemented using traits just like the operators in currently in Rust? That way, if there is any collision between crates you can go back to the trait method that the custom operator was basically syntax sugar for.

For example:

#[doc(alias = "|>")]
pub trait ForwardPipe<Rhs = Self> {
    /// The resulting type after applying the `|>` operator.
    type Output;

    /// Performs the `|>` operation.
    ///
    /// # Example
    ///
    /// ```
    /// fn print(message: &str) {
    ///     println!("{}", message);
    /// }
    ///
    /// // "Hello World" will be passed as a parameter to the print function
    /// "Hello World" |> print;
    ///
    /// // "Goodbye World" will be passed as a parameter to the print function
    /// ForwardPipe::forward_pipe("Goodbye World");
    /// ```
    #[must_use]
    fn forward_pipe(self, rhs: Rhs) -> Self::Output {
    ...
    }
}

Also, with this solution you can see the documentation on how to use the operator if the crate implemented it. So there there would be meaning to the operator than just a symbol. Named functions are great and easy to understand. They should be first consideration when having custom operators in a language but in some cases symbols may be even better.

SOF3 commented 1 year ago

maybe we could implement something equivalent if #![custom_attribute] on modules was possible

#![binary_op(PartialEq::ne = <>)]

fn main() {
    println("{}", 1 <> 2); // true
}
EarthCitizen commented 1 year ago

With the ability to override existing operators, *, +, etc., can become whatever you want it to be. In this case, you would still need to refer to the documentation of the crate to understand what this does. So, I am not sure that needing to read documentation to understand the operators in a crate is a compelling argument against custom operators. Nor does limiting to a known set of operators prevent unclear code and "messiness".

SOF3 commented 1 year ago

@EarthCitizen the impact of overriding existing operators only propagates to the resultant type and inference of the operator, but custom operators are way more complex than that, involving arbitrary precedence and associativity rules, unclear unary/binary-ness, etc., which can cause way more problems than just reading the documentation, especially for editors that are unable to resolve the custom operators (and yes, even rust-analyzer still constantly runs into macro resolution issues for me all the time).