RefMut::filter_map and confusing lifetime error

If I try to compile this, the get_tic method produces an error that I don't understand.

use std::cell::{RefCell, RefMut};
use std::rc::Rc;

#[derive(Default)]
struct Window {
    widget: Option<Rc<RefCell<dyn Widget>>>
}

trait Widget {
    fn as_text_input_client(&mut self) -> Option<&mut dyn TextInputClient> { None }
}
trait TextInputClient {
    fn insert_text(&mut self, text: &str);
}

impl Window {
    fn get_tic(&mut self) -> Option<RefMut<'_, dyn TextInputClient>> {
        let wid = self.widget.as_ref()?;
        RefMut::filter_map(wid.borrow_mut(), |b| {
            b.as_text_input_client()
        }).ok()
    }

    fn insert_text(&mut self) {
        if let Some(mut tic) = self.get_tic() {
            tic.insert_text("hi");
        }
    }
}

Error:

error: lifetime may not live long enough
  --> learning/question1.rs:22:13
   |
21 |         RefMut::filter_map(wid.borrow_mut(), |b| {
   |                                               -- return type of closure is Option<&mut (dyn TextInputClient + '2)>
   |                                               |
   |                                               has type `&'1 mut dyn Widget`
22 |             b.as_text_input_client()
   |             ^^^^^^^^^^^^^^^^^^^^^^^^ returning this value requires that `'1` must outlive `'2`
   |
   = note: requirement occurs because of a mutable reference to `dyn TextInputClient`
   = note: mutable references are invariant over their type parameter
   = help: see <https://doc.rust-lang.org/nomicon/subtyping.html> for more information about variance

From what I know about lifetimes, the expanded version of as_text_input_client should look like:

fn as_text_input_client<'a>(&'a mut self) -> Option<&'a mut dyn TextInputClient>

And that looks okay, but apparently it is creating another lifetime '2 and using it: (dyn TextInputClient + '2). What is that?

the signature of RefMut::map_filter() cannot support type in the form of &'a mut dyn Trait + 'a, because the argument b is of type &'a T where 'a is a higher ranked binder of the function.

change to &dyn Trait + 'static should make it compile:

trait Widget {
-    fn as_text_input_client(&mut self) -> Option<&mut dyn TextInputClient> { None }
+    fn as_text_input_client(&mut self) -> Option<&mut (dyn TextInputClient + 'static)> { None }
}

-fn get_tic(&mut self) -> Option<RefMut<'_, dyn TextInputClient>> {
+fn get_tic(&mut self) -> Option<RefMut<'_, dyn TextInputClient + 'static>> {
    let wid = self.widget.as_ref()?;
    RefMut::filter_map(wid.borrow_mut(), |b| {
        b.as_text_input_client()
    }).ok()
}

note, in theory, this signature is more restrictive for the implementors, for example, with this signature, you cannot implement for types with lifetime parameters like this:

struct MyWidget<'b>(&'b i32);

impl<'b> TextInputClient for MyWidget<'b> { ... }

impl<'b> Widget for MyWidget<'b> {
    fn as_text_input_client(&mut self) -> Option<&mut (dyn TextInputClient + 'static)> {
        // error: lifetime `'b` must outlive `'static`
        // ---- vvvv ----
        Some(self)
    }
}

however, whether this is a problem depends on your actual use case.

2 Likes

Thanks for the response, though I will need to study something before I can understand it. For example, I don't know what "higher ranked binder of the function" means. I see some pages and blogs about higher ranked trait bounds, so I will read those and see if I can understand them.

The filter_map method looks like so:

pub fn filter_map<U: ?Sized, F>(mut orig: RefMut<'b, T>, f: F) 
    -> Result<RefMut<'b, U>, Self>
where
    F: FnOnce(&mut T) -> Option<&mut U>,

// Without elision:
where F: for<'a> FnOnce(&'a mut T) -> Option<&'a mut U>

And as_text_input_client looks like so:

fn as_text_input_client(&mut self) -> Option<&mut dyn TextInputClient>

If we remove all lifetime elision and Self aliases, at the call site we have

for<'a> 
  Fn(&'a mut (dyn Widget + 'static)) -> Option<&'a (dyn TextInputClient + 'a)>

And if you try to line this up with the where bound you'll get

for<'a> 
  Fn(&'a mut (dyn Widget + 'static)) -> Option<&'a (dyn TextInputClient + 'a)>
//           |___________   ______|                |_________   ____________|
//                       | |                                 | |
F: for<'a> FnOnce(&'a mut T) ->                Option<&'a mut U>

You can have T = dyn Widget + 'static, so that's fine.

But you can't have U = dyn TextInputClient + 'a, because 'a is different for every input lifetime. 'a doesn't exist outside of the for<'a>, where U is defined. U can't contain 'a.

@nerditation's fix allowed for U = dyn TextInputClient + 'static instead.


An alternative to requiring + 'static in the trait is to require all lifetimes that the implementor is valid for:

trait Widget {
    fn as_text_input_client<'s>(&mut self)
        -> Option<&mut (dyn TextInputClient + 's)>
    where
        Self: 's,
    {
        None
    }
}

If you only have 'static implementors, this won't matter. But it's more flexible if you have things like MyWidget<'b>.

(dyn Widget + 'static: 'static so it will still be a fix for the OP; one can let 's = 'static. For MyWidget<'b>, one could let 's = 'b.)

1 Like

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.