5

I'm trying to overload the right shift operator (>>) in rust to implement a Maybe bind.

enum Maybe<T> {
    Nothing,
    Just(T)
}

/// Maybe bind. For example:
///
/// ```
/// Just(1) >> |x| Just(1 + x) >> |x| Just(x * 2)
/// ```
impl<'a, T, U> Shr<|&T|: 'a -> Maybe<U>, Maybe<U>> for Maybe<T> {
    fn shr(&self, f: &|&T| -> Maybe<U>) -> Maybe<U> {
        match *self {
            Nothing => Nothing,
            Just(ref x) => (*f)(x)
        }
    }
}

fn main() {}

However, I'm running into an error trying to invoke the closure:

<anon>:15:28: 15:32 error: closure invocation in a `&` reference
<anon>:15             Just(ref x) => (*f)(x)
                                     ^~~~
error: aborting due to previous error
playpen: application terminated with error code 101
Program ended.

Why is it erroneous to invoke a borrowed closure, and how can I resolve the issue and implement the bind?

I found a similar question on stackoverflow, but rust has changed enough since then so that it no longer works.

Community
  • 1
  • 1
mwhittaker
  • 1,745
  • 15
  • 18
  • 1
    Are you aware that this is exactly what `Option.and_then` does (though it consumes `self` and so is able to pass the object by value rather than by reference, which is distinctly superior)? – Chris Morgan Jul 02 '14 at 04:01

3 Answers3

5

Overloading the shift-right operator is not a good idea here because of various limitations it imposes on you. It takes everything by reference, whereas what you want is to take everything by value.

It is not possible to call a closure through an immutable reference; you must have a mutable reference to call it, because it may mutate its environment.

The solution in the time of the question you referred to was to use &fn(&A) -> B, which was an immutable closure; at present we do not have that type; |&A| -> B is parallel to &mut fn(&A) -> B from that time, which simply can’t work because it is being done through an immutable reference. The sensible thing, of course, is to take self by value and have the function as |A| -> B; this is what Option.and_then, which is exactly what you are trying to implement, does.

In short, what you are trying to do is currently impossible, though it might become possible again at some point in the future. Use a regular method instead of trying to overload the operator.

Or just use Option, already there:

Some(1i).and_then(|x| Some(1 + x))
        .and_then(|x| Some(x * 2))
Chris Morgan
  • 86,207
  • 24
  • 208
  • 215
  • Thank you! I had a hunch that overloading the operator was currently impossible but just wanted to confirm. I also hadn't seen `and_then` before. I will definitely add it to my arsenal. – mwhittaker Jul 02 '14 at 14:52
  • It’s worthwhile looking through all the different methods available for `Option`—it’s quite a range! – Chris Morgan Jul 02 '14 at 23:05
4

This is possible nowadays. Shr takes by value, and there are unboxed closures:

use std::ops::Shr;
use Maybe::{Nothing,Just};

#[derive(Debug)]
enum Maybe<T> {
    Nothing,
    Just(T)
}

impl<T, U, F> Shr<F> for Maybe<T>
    where F: FnOnce(T) -> Maybe<U>
{
    type Output = Maybe<U>;

    fn shr(self, f: F) -> Maybe<U> {
        match self {
            Nothing => Nothing,
            Just(x) => f(x)
        }
    }
}

fn main() {
    let a = Just(1u8);
    let b = a >> |v: u8| Just(v + 1);
    println!("{:?}", b)
}
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
3

After giving up on operator overloading and after tinkering with rust's macros a bit, I figured out how to implement some nice syntactic sugar for chaining Option maps and binds. The code can be found in this gist and is included here for convenience:

#![feature(macro_rules)] 

macro_rules! map(
    ()                                    => ({});
    ($a:expr)                             => ($a);
    ($a:expr -> $b:expr)                  => ($a.map($b));
    ($a:expr -> $b:expr -> $($c:expr)->*) => (map!($a.map($b) -> $($c)->*));
)

macro_rules! flatbind(
    ()                                    => ({});
    ($a:expr)                             => ($a);
    ($a:expr -> $b:expr)                  => ($a.and_then($b));
    ($a:expr -> $b:expr -> $($c:expr)->*) => (flatbind!($a.and_then($b) -> $($c)->*));
)

macro_rules! bind(
    () => ({});
    ($a:expr) => ($a);
    ($a:expr -> |$var:ident| $body:expr) => ($a.and_then(|$var| $body));
    ($a:expr -> |$var:ident| $body:expr -> $(|$vars:ident| $bodies:expr)->*) => ($a.and_then(|$var| {bind!($body -> $(|$vars| $bodies)->*)}));
)

fn main() {
    // Equivalent rust code:
    // Some("12345")
    // .map(|s| s.to_string())
    // .map(|s| s.len())
    // .map(|l| l * l)
    let o = map!(
        Some("12345")     ->
        |s| s.to_string() ->
        |s| s.len()       ->
        |l| l * l
    );
    assert!(o == Some(25));

    // Equivalent rust code:
    // Some("12345")
    // .and_then(|s| Some(s.to_string()))
    // .and_then(|s| Some(s.len()))
    // .and_then(|l| Some(l * l))
    let o = flatbind!(
        Some("12345")           ->
        |s| Some(s.to_string()) ->
        |s| Some(s.len())       ->
        |l| Some(l * l)
    );
    assert!(o == Some(25));

    // Equivalent OCaml code:
    // Some 3 >>= fun x ->
    // Some 4 >>= fun y ->
    // Some 5 >>= fun z ->
    // Some(z*z - x*x - y*y)
    //
    // Equivalent rust code:
    // Some(3i).and_then( |x| {
    //     Some(4i).and_then |y| {
    //         Some(5i).and_then |z| {
    //             Some(z*z - x*x - y*y)
    //         }
    //     }
    // })
    let o = bind!(
        Some(3i) -> |x| 
        Some(4i) -> |y| 
        Some(5i) -> |z| {
            assert!(x == 3i);
            assert!(y == 4i);
            assert!(z == 5i);
            Some(z*z - x*x - y*y)
        }
    );
    assert!(o == Some(0));
}
mwhittaker
  • 1,745
  • 15
  • 18