kevlarr
kevlarr

Reputation: 1162

How to pass functions for traits implemented with lifetimes?

Rust beginner here. I have this "binary calculator" that uses a couple of types..

pub enum Bit { Off, On };
pub struct Binary([Bit; 64]);

Ignoring everything else that is implemented for them, I overloaded the operators for the Binary like so...

impl Add for Binary {
    type Output = Binary;

    /// Basic implementation of full addition circuit
    fn add(self, other: Binary) -> Binary {
        ...
    }
}

... Div, Sub, and Mul

... where each operator consumes the Binarys passed to them. I was then able to define a set of public functions that took care of converting, calling, and printing everything how I wanted to...

pub fn add(x: i64, y: i64) -> Calc {
    execute(Binary::add, x, y)
}

pub fn subtract(x: i64, y: i64) -> Calc {
    execute(Binary::sub, x, y)
}

...

fn execute(f: fn(Binary, Binary) -> Binary, x: i64, y: i64) -> Calc {
    let bx = Binary::from_int(x);
    println!("{:?}\n{}\n", bx, x);

    let by = Binary::from_int(y);
    println!("{:?}\n{}\n", by, y);

    let result = f(bx, by);
    println!("{:?}", result);

    result.to_int()
}

problem

This worked, but the operations consumed the Binarys, which I didn't actually want. So instead, I implemented the traits using references instead...

impl<'a, 'b> Add<&'b Binary> for &'a Binary {
    type Output = Binary;

    /// Basic implementation of full addition circuit
    fn add(self, other: &'b Binary) -> Binary {
        ...
    }
}

Now, though, I cannot figure out how to pass those functions to execute as I did before. For example, execute(Binary::div, x, y) is giving the following error.

error[E0277]: cannot divide `types::binary::Binary` by `_`
  --> src/lib.rs:20:13
   |
20 |     execute(Binary::div, x, y)
   |             ^^^^^^^^^^^ no implementation for `types::binary::Binary / _`
   |
   = help: the trait `std::ops::Div<_>` is not implemented for
    `types::binary::Binary`

How can I pass that specific implementation with lifetimes? I assume that I need to update the signature for execute too, like...

fn execute<'a, 'b>(f: fn(&'a Binary, &'b Binary) -> Binary, ...

But I wind up also seeing...

error[E0308]: mismatched types
  --> src/lib.rs:20:13
   |
20 |     execute(Binary::div, x, y)
   |             ^^^^^^^^^^^ expected reference, found struct `types::binary::Binary`
   |
   = note: expected type `fn(&types::binary::Binary, &types::binary::Binary) -> types::binary::Binary`
          found type `fn(types::binary::Binary, _) -> <types::binary::Binary as std::ops::Div<_>>::Output {<types::binary::Binary as std::ops::Div<_>>::div}`

Being a complete beginner, I was able to follow all the error messages that got me to the "working" point (where operations consumed the values), but now I'm a bit out of my league, I think.

Upvotes: 0

Views: 91

Answers (1)

phimuemue
phimuemue

Reputation: 36071

I made an exemplary implementation for addition (I made some assumptions regarding the return type of execute and others, you'd have to adapt this if my assumptions are wrong):

use std::ops::Add;

#[derive(Debug)]
pub enum Bit { Off, On }
#[derive(Debug)]
pub struct Binary([Bit; 32]);

impl Binary {
    fn to_int(&self) -> i64 {unimplemented!()}
    fn from_int(n: i64) -> Self {unimplemented!()}
}

impl<'a, 'b> Add<&'b Binary> for &'a Binary {
    type Output = Binary;
    fn add(self, other: &'b Binary) -> Binary {
        unimplemented!()
    }
}

pub fn add(x: i64, y: i64) -> i64 {
   execute(|a, b| a+b, x, y)
}

fn execute(f: fn(&Binary, &Binary) -> Binary, x: i64, y: i64) -> i64 {
    let bx = Binary::from_int(x);
    println!("{:?}\n{}\n", bx, x);

    let by = Binary::from_int(y);
    println!("{:?}\n{}\n", by, y);

    let result = f(&bx, &by);
    println!("{:?}", result);

    result.to_int()
}

Note that within execute, you'd have to call f(&bx, &by) (i.e. borrow them instead of consuming).

However: I wondered why you chose to have fn(&Binary, &Binary) -> Binary as argument type instead of making execute generic over F, constraining F to be a callable:

fn execute<F>(f: F, x: i64, y: i64) -> i64
    where
        F: Fn(&Binary, &Binary) -> Binary,
{
    let bx = Binary::from_int(x);
    println!("{:?}\n{}\n", bx, x);

    let by = Binary::from_int(y);
    println!("{:?}\n{}\n", by, y);

    let result = f(&bx, &by);
    println!("{:?}", result);

    result.to_int()
}

This way, you are a bit more flexible (you can e.g. pass closures capturing variables in their scope).

Upvotes: 1

Related Questions