Correct trait bound to simulate a partial function

The following function boxes a closure that takes &M as input:

pub fn accepts_m<M>(cl: impl FnMut(&M) + 'static) -> Box<dyn FnMut(&M)>
{
    Box::new(cl)
}

I want to write a function that wraps a closure that takes a subset M1 of &M as input. To the outside, the returned boxed closure must still accept &M as input. When called, the returned boxed closure attempts to convert &M into M1. If it succeeds, it calls the wrapped closure, passing M1 to it.
In essence, I am trying to simulate a partial function.

This is what I have came up with:

pub fn accepts_m_partial<M, M1>(mut cl: impl FnMut(M1) + 'static) -> Box<dyn FnMut(&M)>
    where for<'a> M1: TryFrom<&'a M>
{
    Box::new(move |m: &M| {
        if let Ok(m1) = m.try_into() {
            cl(m1);
        }
    })
}

Let's make a (somewhat) complicate example:

#[derive(Debug)]
enum Outer {
    A { a: String },
    B { b: u32 },
}

#[derive(Debug)]
enum Inner<'a> {
    A { a: &'a String }
}

impl<'a> TryFrom<&'a Outer> for Inner<'a> {
    type Error = ();

    fn try_from(value: &Outer) -> Result<Self, Self::Error> {
        match value {
            Outer::A { a } => Ok(Inner::A { a }),
            Outer::B { .. } => Err(()),
        }
    }
}

fn main() {
    let b1 = accepts_m_partial::<Outer, Inner>(move |m1: Inner| {
        match m1 {
            Inner::A { a } => {
                println!("{}", a);
                // mutate the environment
            }
        }
    });
}

Notice that Inner is a subset of Outer. Moreover, it contains a reference to the original String, to simulate the fact that cloning data in Inner may be prohibitively expensive.

Unfortunately, it fails to compile:

error[E0277]: the trait bound `for<'a> Inner<'_>: TryFrom<&'a Outer>` is not satisfied
  --> src/main.rs:39:41
   |
39 |     let b1 = accepts_m_partial::<Outer, Inner>(move |m1: Inner| {
   |                                         ^^^^^ the trait `for<'a> TryFrom<&'a Outer>` is not implemented for `Inner<'_>`
   |
   = help: the trait `TryFrom<&'a Outer>` is implemented for `Inner<'a>`
note: required by a bound in `accepts_m_partial`
  --> src/main.rs:7:23
   |
6  | pub fn accepts_m_partial<M, M1>(mut cl: impl FnMut(M1) + 'static) -> Box<dyn FnMut(&M)>
   |        ----------------- required by a bound in this function
7  |     where for<'a> M1: TryFrom<&'a M>
   |                       ^^^^^^^^^^^^^^ required by this bound in `accepts_m_partial`

I am having some difficulties overcoming the compilation error. Do you have any suggestions? Perhaps, the trait bound I am trying to impose is the wrong approach. However, you should still get the idea.

The compiler is sometimes not smart enough to reason about the functional equivalence of impl<'a> Trait<'a> for Type<'a> and for<'a> impl Trait<'a> for Type<'a>. (To be fair, in general they are not equivalent.)

As a first attempt, you can just resort to actual generics instead of HRTBs. This compiles (as well as its usage):

pub fn accepts_m_partial<'a, M, M1>(mut cl: impl FnMut(M1) + 'a) -> Box<dyn FnMut(&'a M) + 'a>
where
    M: 'a,
    M1: 'a,
    M1: TryFrom<&'a M>
{
    Box::new(move |m| {
        if let Ok(m1) = m.try_into() {
            cl(m1);
        }
    })
}

By the way, you don't need to box the return value. Just return impl FnMut(…), like this:

pub fn accepts_m_partial<'a, M, M1>(mut cl: impl FnMut(M1) + 'a) -> impl FnMut(&'a M) + 'a
where
    M: 'a,
    M1: 'a,
    M1: TryFrom<&'a M>
{
    move |m| {
        if let Ok(m1) = m.try_into() {
            cl(m1);
        }
    }
}

An even easier alternative is to just not require references in the signature at all:

pub fn accepts_m_partial<M, M1>(mut cl: impl FnMut(M1)) -> impl FnMut(M)
where
    M1: TryFrom<M>
{
    move |m| {
        if let Ok(m1) = m.try_into() {
            cl(m1);
        }
    }
}
4 Likes

Excellent answer, thank you.

I am aware that I can avoid boxing, but in my use case, I really have to do it.

Would it be also possible to have a version with a 'static lifetime?

pub fn accepts_m_partial<M, M1>(mut cl: impl FnMut(M1) + 'static) -> Box<dyn FnMut(&M) + 'static>
where
    ....

I'm not sure what you mean. Why wouldn't it be possible?

I would like to have an accepts_m_partial function that returns a Box<dyn FnMut(&M)> + 'static.
The one you have provided returns Box<dyn FnMut(&'a M) + 'a>.

Could you please suggest the modifications required to obtain a static version?
Admittedly, I am not too knowledgeable on advanced lifetimes (I will for sure study this part of Rust more).

Just decouple the two lifetimes.

(Naturally, maximum flexibility is attained when the degrees of freedom of the declared lifetimes isn't smaller than what the implementation can actually offer. Two independent things need two lifetime parameters.)

1 Like

Thanks for your patience, it's really useful and I am learning something new!

Your implementation is very flexible. However, suppose that I want the returned boxed closure to have a 'static lifetime by construction:

pub type Closure<M> = Box<dyn FnMut(&M) + 'static>;

pub fn accepts_m_partial<'a, 'b, M, M1>(mut cl: impl FnMut(M1) + 'b) -> Closure<M>

Is it still possible?

The motivation is that, by doing so, the Closure type does not need an additional lifetime annotation, which would infect the whole codebase. I simply need 'static closures.

I'm sorry, I don't really understand how my above example doesn't already provide that. Did you look at how it's actually used in main()? What you are asking for is the special case of specializing this function with 'b = 'static.

Of course, you could just leave off 'b-as-a-parameter and substitute the concrete 'static lifetime for all of its occurrences in the signature, but that's not really useful, because it's strictly less general with no benefit. (The only difference it could make is that you would get the same lifetime error in a different place.)

Apologies for the confusion, I admit have not explained myself good enough, omitting too many details.

In the library I am developing, closures are stored using the New Type Idiom (and are not a type alias, like I used to write until now):

pub struct Closure<M>(Box<dyn FnMut(&M) + 'static>);

impl<M> Deref for Closure<M> {
    type Target = Box<dyn FnMut(&M) + 'static>;

    fn deref(&self) -> &Self::Target {
        &self.0
    }
}

impl<M> DerefMut for Closure<M> {
    fn deref_mut(&mut self) -> &mut Self::Target {
        &mut self.0
    }
}

pub fn accepts_m<M>(cl: impl FnMut(&M) + 'static) -> Closure<M> {
    Closure(Box::new(cl))
}

Notice that the inner closure has 'static lifetime.

I realize now that the requirement to use the New Type Idiom makes the question and the requirement different. Sorry for that. :sweat:

Is it possible, under the New Type Idiom requirement, to implement accepts_m_partial?

No, it's exactly the same.

The link in my previous post does exactly that.

Incorporating the newtype only requires the obvious, local changes. It doesn't really impact the implementation except for literally wrapping the boxed closure in the newtype.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.