[workaround found] "cannot infer an appropriate lifetime" error borrowing from a mutable field


#1

I am getting a “cannot infer an appropriate lifetime due to conflicting requirements” error. Here is an example. It may not be minimal but it mirrors the structure of my actual code pretty closely.

I only sort of see the conflict and unfortunately this particular message doesn’t provide more specific information. I believe it has something to do with using Vec/slices in Ex::mutate since if I remove all the slices and change the Vec to just a reference, everything compiles fine. I think it has to do with how Borrow works with &mut self too, since if I simply use get instead of get_mut, everything compiles fine.

I looked at mutability affects lifetime inference which sounds very similar but has no actionable resolution.

Given the error message, my working theory is that the compiler is trying to assign the lifetime of the references in the Vec to the references in the s parameter of Ex::mutate. But I don’t see why it should assign those, nor why the lifetimes must be the same (if indeed they should). My intuition says they should be allowed to be unrelated since I only need to hash the input and nothing else.

If I make the compiler-suggested change and annotate fn mutate(&mut self, &[&'a str]), the error moves up a level to Container::mutate where it again wants me to annotate the parameter. Making that changes pushes the error another level to Library::func, which in my actual code is third-party and cannot be changed without failing to implement the library’s trait. It is the library which actually owns the “root” &str from which all further slices are taken.

I can’t change the library code, and I’d like my Ex::mutate signature to look basically the same (slices with references in parameters). I have tried adding various explicit lifetimes and bounds but have yet to find a version that works.

So my questions are:

  1. What exactly is going on with the lifetimes?
  2. Is there any way to express to the compiler that there is no conflict?
  3. If not, is there an alternate construction that can support the same basic API? I haven’t been able to find one.

#2

HashMap::get_mut takes an argument of type &Q where K: Borrow<Q>.

In this case, K = Vec<&'a str>, and the only available Borrow<Q> impls for this type are for Q = [&'a str] or Vec<&'a str>.

That’s why the compiler insists that you pass in a slice of &'a str even though, as you note, it shouldn’t be necessary. I’m not sure how best to work around this issue.


#3

But HashMap::get also takes the same &Q where K: Borrow<Q> and has the same available impls yet that compiles just fine. As far as I can tell the only difference between the two is &self for get and &mut self for get_mut. So what is it about mutability that causes the failure?

Maybe it’s like the last comment in the thread I linked said: the compiler can never figure out a lifetime, it just doesn’t matter in the immutable case. So why does it matter in the mutable case? I assume there’s a corner case I don’t know about.

It’d be great if the error message provided more information about what the conflict is. Is there a way I can get more detail? Maybe some -Z flags that can help?


#4

I’m not sure this is helping, but I did get your example to compile by using the following changes (an experiment based on the above comments):

First I added a custom type wrapping the Vec<&str>:

#[derive( Debug, PartialEq, Eq, Hash )]
struct ExKeys<'a> {
    keys: Vec<&'a str>,
}

Then I used that in the Ex definition:

struct Ex<'a> {
    sr: HashMap<ExKeys<'a>, Owned>,
}

And then I added a Borrow definition with separate lifetimes:

impl<'a, 'b> Borrow<[&'a str]>
    for ExKeys<'b>
    where 'b: 'a {

    fn borrow(&self) -> &[&'a str] { self.keys.borrow() }
}

Note sure it will hold up to full usage though.

Edit: Thought I’d add a full playrust example.


#5

Thanks phaylon! I managed to adapt that to my real code. I was thinking about a potential newtype + borrow solution on my own but couldn’t quite get there. I hadn’t made the leap to using two lifetime parameters on the Borrow impl. Much appreciated!

I’d still love to know what causes this and potentially have it fixed but I’m ecstatic to have a workaround.


#6

I can’t say why the &mut self vs. &self makes a difference.

The (hopefully) solution is based on @mbrubeck’s comment above, that the generic impl Borrow<[T]> for Vec<T> will have the borrow implementation share the lifetimes of things in the Vec via the T parameter. The custom type Borrow implementation has separate generic lifetimes for both the references in the stored keys and the key that is passed in.

At least I hope that’s reasonably correct :slight_smile:


#7

I think the difference between get and get_mut is related to the fact that &T is variant over T, while &mut T is invariant over T.

Slightly longer explanation: Supposed you have two lifetimes, one a subset of the other, 'long: 'short. If you have a function that takes &Vec<&'short T> then it you can safely pass it a &Vec<&'long T> instead, because all it can do is read the references in it, for a subset of the time when they are valid anyway. So Rust will happily coerce from the latter type to the former.

But if some function takes &mut Vec<&'short T> then it is not always safe to pass it an &'mut Vec<&'long T> instead. Because one thing the function could do is push a new &'short T into the vector. After the function returns, the Vec<&'long T> still exists, but now contains a reference that’s not valid for 'long. That’s why Rust won’t coerce the types in this case.


#8

I’ve been thinking on this over the weekend and decided the variance angle almost adds up for me but I’m still unclear on the details. Specifically, it is the parameter to Borrow which needs variance/coercion on its lifetime, and that parameter is an immutable borrow, so there should be no issue coercing lifetimes, right? With the explicit (and apparently safe) implementation of Borrow for ExKeys, everything works. But none of the mutability aspects have changed as far as I can tell - the mutable HashMap is still borrowing its key immutably.