How to correctly implement the Reader Monad?

I tried to implement the Reader Monad in Rust:

pub struct Reader<'a, R, T>(Box<dyn Fn(R) -> T + 'a>);

impl<'a, R, T> Reader<'a, R, T> {
    pub fn new(f: impl Fn(R) -> T + 'a) -> Reader<'a, R, T> {
        Self(Box::new(f))
    }

    pub fn run(self, r: R) -> T {
        self.0(r)
    }

    pub fn then<U>(self, f: impl Fn(T) -> U + 'a) -> Reader<'a, R, U> {
        Reader::new(|r: R| f(self.0(r)))
    }
}

The compiler complains that:

error[E0309]: the parameter type `R` may not live long enough
  --> src/reader.rs:13:9
   |
3  | impl<'a, R, T> Reader<'a, R, T> {
   |          - help: consider adding an explicit lifetime bound...: `R: 'a`
...
13 |         Reader::new(|r: R| f(self.0(r)))
   |         ^^^^^^^^^^^ ...so that the type `[closure@src/reader.rs:13:21: 13:40]` will meet its required lifetime bounds...
   |
note: ...that is required by this bound
  --> src/reader.rs:4:37
   |
4  |     pub fn new(f: impl Fn(R) -> T + 'a) -> Reader<'a, R, T> {
   |                                     ^^

I don't understand why R may not live long enough...

Suppose 'a is 'static. This implies that R must live for at least 'static
Now R itself may not live for 'static if say it is also a closure (closures are the most common types that may not have a 'static lifetime).
Hence, you need to specify the bound.

1 Like

Let's say the lifetime of the closure |r: R| f(self.0(r)) (in the method then) is 'a, and why r: R needs lifetime constraints related with 'a?

r: R is just a parameter of the closure, why the lifetime of a parameter has something to do with the corresponding closure?

AFAICT the only problem here is that rustc sees the type Reader<'a, R, T> mention the type R, so it convervatively assumes that fulfilling Reader<'a, R, T>: 'a requires R: 'a.[1] Since the self: Reader<'a, R, T> value is captured in the closure, this becomes relevant / a problem.

There isn’t really any way to avoid adding a R: 'a bound here, but it technically shouldn’t really be able prevent any use-cases either, I think. So you’ll need R: 'a (and of course also T: 'a, but that bound is truly necessary Edit2: on a late second thought, perhaps that “fact” is more debatable than I initially thought, too) on the then method.


  1. The same thing also already applies for the type dyn Fn(R) -> T + 'a. Even though it “claims” to implement 'a, you only get dyn Fn(R) -> T + 'a : 'a if R: 'a and T: 'a also holds. It’s not a problem of the struct or the box.

    For soundness, AFAICT this restriction would only be necessary if the Fn trait actually had any method returning a value of type R; for traits with only methods accepting R in the method arguments, the restriction is probably an over-restriction whose only use I can think of off the top of my head would be to keep backwards compatibility in the possibility to add such methods to the trait in the future. In case of Fn, this possibility would be rather nonsensical, but this is talking about the general case.

    Edit: At least I thought this would be necessary if any method returns R. I’m actually not so sure about this anymore. ↩︎

5 Likes

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.