Infix functions


#1

Note: I am not proposing this seriously because I don’t know how much commonly useful this is, so think of this more like some musings…

Has someone suggested this very simple idea?

#[infix]
fn add(x: u32, y: u32) -> u32 {
    x + y
}

#[infix] means the function must be used with infix syntax, and it must take two arguments and return something (it must return no bang and no empty tuple):

let z = 10 add 20;

In Haskell to do the same you just add back ticks before and after the name of any regular function:

let z = 10 `add` 20

But it avoids the problems with un-searcheable new Haskell operators, and it helps avoid some parentheses. And I’ve specified a “must use as infix” because I think it goes better with the general strictness and uniformity of Rust code.

Idea adapted from NeedAWaifu:
https://www.reddit.com/r/rust/comments/46o7wi/on_the_syntax_of_logical_and_bitwise_operators/d06swi0


#2

Maybe you should try submitting this to Rust RFC.


#3

An attribute on the function is not as clear as just allowing any binary function to be called infix by introducing a new operator, for example like this:

let sum = a \add b;

Where \ for example could be the infix call operator.

Instead of adding user-defined operators, we simply allow functions to be operators. This has some benefits: Functions already know how to use traits for their arguments, functions already work well with scoping and use.

Free functions are better than using methods here, since coercions apply to the first parameter of a free function, unlike the receiver of a method.

At the same time it allows libraries to define operator-like functions.

The main contention is around the possible need to define fixity for such functions.


#4

I don’t see why we’d need to prefix the infix with \ or surround it with ` not just allow arbitrary a infix b expressions like Kotlin? I can’t think of any ambiguities.

Note: I’m actually not a fan of this proposal as it introduces a pretty much arbitrary style choice (to use an infix or not to use an infix).


#5

I’d recommend against that sort of syntax because it greatly increases the parser complexity – if you don’t know whether an identifier is an operator or not, you can’t build parse trees for any expression containing that identifier. You’d avoid the worst of it because Rust has neither a Haskell-style layout rule[1] or context-sensitive lexing[2], but IMO it’d still be a major regression in Rust’s “you can parse a Rust source file as a document without resolving imports” story, which would hurt for tool support.

ML has this, but ML is also much stricter about declaration before use, which helps.

[1] Haskell 98’s layout rule was specified in terms of contrafactual syntax errors, which made the correct parsing of statements like do 1 == 2 == 3 very… interesting. Combine this with user-defined operator associativity and scoping, and I’ve suspected for a while that it might be NP-complete to parse.

[2] When the Perl 5 parser sees ident /, if ident has been declared as a nullary function, the / will be division and the lexer will expect tokens; but if ident has not been seen or was declared to take arguments, the / is the beginning of a regex literal and the lexer interprets what follows as a regular expression.


#6

I guess you refer to @bluss answer above.


#7

I think infix operator must defined using trait similar to https://doc.rust-lang.org/std/ops/trait.Add.html.

maybe like this:

    #[infix('post')]
    pub trait PostRequest<RHS = Self> {
        type Output;
        fn post(self, rhs: RHS) -> Self::Output;
    }

then we can abused it like this:

fn main(){
   use some_web_frameworks:ops:PostRequest;
   ...

   let m = router::url("/post/add") post |req| {
       req.send("Hello World")
   };

   ... 

}


#8

To be clear, I think custom infix operators are largely a really bad idea because of the issues with parsing, precedence, and fixity.

That said…

In a toy language I made a while ago, I defined both postfix and infix invocation forms: (.name) and (.name.), with tightest-binding precedence and left fixity (IIRC). Worked well, but wasn’t that useful, and I ended up needed to parenthesise anything non-trivial anyway.

If you wanted to do it nicely, you need something that can be parsed without context and gives you custom precedence. Really, any solution that doesn’t provide those two properties is a non-starter as far as I’m concerned. To whit: custom operators can be a sequence of symbolic token trees inside parens or brackets, and the precedence and fixity are copied from the leftmost token. Just to make sure we don’t stomp on any useful syntax, let’s also require in-set .s like the idea above. Bind them to implementations by adding an #[operator="..."] attribute for traits.

So, let’s say you want element-wise addition: [.+.]: this has the same precedence and fixity as +. Dot product might be [.*+.] so that it functions like multiplication.

But that’s really rather ugly and heading into “is this really worth it?” territory, given that you can fake custom infix operators using methods and just parenthesising both sides ((a).op(b)), so… yeah, not really a big fan.