Error when generic return type contains reference

I have a function which returns a type that comes from a closure which is passed in. When that type is a reference or contains a reference, I get an error and I'm not sure how to fix it.

Simplified code:

fn call<F, T>(slice: &[u8], f: F) -> T
where
    F: FnOnce(&[u8]) -> T,
{
    f(slice)
}

fn foo(x: &[u8]) -> usize {
    x.len()
}

fn bar(x: &[u8]) -> &[u8] {
    &x[1..]
}

fn main() {
    let slice = [0u8; 10];
    //let res = call(&slice, foo); // OK
    let res = call(&slice, bar); // ERROR
    println!("{:?}", res);
}

(Playground)

Errors:

   Compiling playground v0.0.1 (/playground)
error[E0271]: type mismatch resolving `for<'r> <for<'s> fn(&'s [u8]) -> &'s [u8] {bar} as std::ops::FnOnce<(&'r [u8],)>>::Output == _`
  --> src/main.rs:19:15
   |
1  | fn call<F, T>(slice: &[u8], f: F) -> T
   |    ---- required by a bound in this
2  | where
3  |     F: FnOnce(&[u8]) -> T,
   |                         - required by this bound in `call`
...
19 |     let res = call(&slice, bar); // ERROR: type mismatch resolving `for<'r> <for<'s> fn(&'s [u8]) -> &'s [u8] {foo1} as std::ops::FnOnce<(&'r [u8],)>>::Output == _`
   |               ^^^^ expected bound lifetime parameter, found concrete lifetime

error: aborting due to previous error

For more information about this error, try `rustc --explain E0271`.
error: could not compile `playground`.

To learn more, run the command again with --verbose.

This can be solved by introducing lifetime bounds to call which constrain T to 'a

fn call<'a, F, T: 'a>(slice: &'a [u8], f: F) -> T
where
    F: FnOnce(&'a [u8]) -> T,
{
    f(slice)
}
1 Like

Indeed, this solves the simplified problem nicely and easily :+1:

Unfortunately in the real problem I'm still stuck. I guess I simplified too much.

In my real problem, slice is not an input parameter. Instead it is borrowed, in a loop, from another parameter, a file reader object which uses a kind of growing/sliding buffer mechanism.

When I add the T: 'a constraint, now the reader is borrowed for 'a, and can't be in a loop where anything borrows mutably from the reader.

basically:

loop {
  let slice = reader.get_buf(); // <-- reader is borrowed immutably for 'a here.
  let res = try_parse(slice)?; // (try_parse was called f in the simplified example)
  if let Some(r) = res {
    //  parse success
    reader.consume_buf(r.consumed);
    return Some(r.value);
  }
  // grow the buffer, read more data, try parsing again on next iteration
  if 0 == reader.read_more()? {
    return None;
  }
}

playground link with the example changed: https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=68b145d66ec40bb6131f5f17dda7165d

I think this is the sort of issue Polonius is intended to resolve. The issue as I understand it is that when the parse fails, the slice can be dropped before the next loop, but when the parse succeeds, it needs to stay borrowed for 'a, and the current borrow checker isn't able to do this kind of reasoning so it just considers it as borrowed for 'a.

The workaround suggested in the RFC seems to work for this example: move the borrow into the conditional block and do an unconditional return after it. This probably won't translate as easily into your real code, but hopefully it'll guide you in the right direction.

loop {
    if x == 3 {
        let res = f(o.get_buf());
        return res;
    }
    x += 1;
}
1 Like

The issue as I understand it is that when the parse fails, the slice can be dropped before the next loop, but when the parse succeeds, it needs to stay borrowed for 'a , and the current borrow checker isn't able to do this kind of reasoning so it just considers it as borrowed for 'a .

Yes, exactly. That's a perfect description.

In my case I'm not sure it's possible to make the return unconditional. Maybe I'm just not thinking outside the box enough here but it seems like it's fundamentally conditional?

I suppose it would be possible to have the parsing return indexes into the buffer instead of references, then use the indexes to build references only inside the if(success) block. Can't really make this generic though.. not really a usable solution.

Maybe I just have to wait a year for Polonius to be ready. Thanks for that link btw, very interesting,

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.