25

Do both the composition and pipe forward operators (like in other languages) exist in Rust? If so, what do they look like and one should one be preferred to the other? If one does not exist, why is this operator not needed?

Phlox Midas
  • 4,093
  • 4
  • 35
  • 56

4 Answers4

27

There is no such operator built-in, but it's not particularly hard to define:

use std::ops::Shr;

struct Wrapped<T>(T);

impl<A, B, F> Shr<F> for Wrapped<A>
where
    F: FnOnce(A) -> B,
{
    type Output = Wrapped<B>;

    fn shr(self, f: F) -> Wrapped<B> {
        Wrapped(f(self.0))
    }
}

fn main() {
    let string = Wrapped(1) >> (|x| x + 1) >> (|x| 2 * x) >> (|x: i32| x.to_string());
    println!("{}", string.0);
}
// prints `4`

The Wrapped new-type struct is purely to allow the Shr instance, because otherwise we would have to implement it on a generic (i.e. impl<A, B> Shr<...> for A) and that doesn't work.


Note that idiomatic Rust would call this the method map instead of using an operator. See Option::map for a canonical example.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
huon
  • 94,605
  • 21
  • 231
  • 225
  • Are the brackets around each step necessary? I'm not quite sure what the order of operations is here. What's "Shr"? Is that how you overload the "shift right" operator? Can we define a different one? Say `|>`? – mpen Mar 29 '14 at 00:20
  • @Mark no, you can't define new operators, and yes, the brackets are necessary because a closure parses as far as possible to make its body, e.g. `|&x| x + 1 >> |&x| 2 *x` would be parsed as `|&x| x + (1 >> (|&x| 2 * x))` and shifting `1` right by a closure makes no sense at all (and, as expected, doesn't type check). – huon Mar 29 '14 at 07:50
  • 1
    @dbaupp That makes me kind of sad. Syntax isn't quite as nice as I'd like. – mpen Mar 29 '14 at 09:32
  • @Mark It's a complete hack anyway. ;) – huon Mar 30 '14 at 08:10
  • This can no longer be done, because closures always take a mutable environment now; through a `&`-reference you cannot call a closure. Just to make sure I’m clear: there’s nothing wrong with the fundamental concept, it’ll work fine in methods in general, but the overloading of `>>` is now out of the question. – Chris Morgan Jul 02 '14 at 04:22
  • @ChrisMorgan will work fine when we get generalised/unboxed closures. – huon Jul 02 '14 at 06:28
  • I worded myself poorly; the “now” at the end was intended as “at present” rather than “now and forever more”. The upcoming closure work should make this work again. – Chris Morgan Jul 02 '14 at 07:30
  • You cannot "dereference" Wrapped to get the T anymore since 0.9. – kennytm Aug 04 '14 at 14:13
  • 2
    This doesn't compile anymore. 'self is no longer a special lifetime. Is there a way to do it with current rust? – diogovk Jan 22 '16 at 15:26
  • 1
    I added an answer with a version that compiles with 1.16.0 http://stackoverflow.com/a/43163837/545475 – Arnavion Apr 02 '17 at 01:55
  • 1
    (Now merged into this answer.) – Arnavion Apr 02 '17 at 09:26
4

These operators do not exist in Rust as far as I know. So far I haven't felt much of a need for them, and there is also an effort to keep the core Rust syntax fairly small. For example, Rust used to have explicit message passing operators, but these were removed.

You might be able to use operator overloading to come up with something similar if you want, or just write your own compose or pipe forward functions. I wouldn't be surprised if the Rust team were open to including these in the standard library.

Eric Holk
  • 1,346
  • 10
  • 7
3

While these operators don't exist on their own, Option::map provides an idiomatic analog.

fn do_some_stuff_to_a_number(x: Option<i32>) -> Option<String> {
    x.map(|x| x + 1).map(|x| 2 * x).map(|x: i32| x.to_string())
}

And without a function:

let string = Some(1)
    .map(|x| x + 1)
    .map(|x| 2 * x)
    .map(|x: i32| x.to_string())?;
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Brandon Dyer
  • 1,316
  • 12
  • 21
  • This is already addressed [by an existing answer](https://stackoverflow.com/a/17111630/155423): *Note that idiomatic Rust would call this the method `map` instead of using an operator. See `Option::map` for a canonical example.* – Shepmaster Jan 14 '20 at 18:15
  • 1
    @Shepmaster thank you, but none of the current answers provide an actual idiomatic example. I'm aware that the current accepted answer _mentions_ it, but doesn't provide any additional information, and also provides an example implementing an anti-pattern. – Brandon Dyer Jan 14 '20 at 18:23
  • 2
    This should be the accepted answer, unless idiomatic Rust nowadays is writing unidiomatic hacks in Rust just so you can write idiomatic Haskell in Rust. –  Jan 15 '20 at 13:12
1

actually, rust does have an operator rather similar it's called the dot operator however it doesn't work with free-standing functions which is the only major issue. ie. functions not defined inside an impl!.

you can write code like Option::map(Option::Some(1),|x| x+1) which is 'f(g(x)' style or code using dot, like Option::Some(1).map(|x| x+1) which is more f |> g(x) style.

dot operator allows you to chain methods as many times as you want. however there are limits for to make the compiler job easier.

5hammer
  • 69
  • 6