[E0502] How can I avoid borrowing both mutably and immutably?

In adding a new argument to a function (event_log.publish, below), I’ve created a mutable/immutable borrow problem for myself, which I haven’t been able to solve.

Based on advice offered on similar questions here and on Stack Overflow, I tried moving the self.event_log reference out to a variable at the top of the session.publish_to_event_log function, but that just gets me the same error, this time on the match get_publish_plugin(&self) line.

How can I restructure my code to avoid this?

use lazycell::LazyCell;

fn get_publish_plugin<'a, 'b>(session : &'a Session) -> Result<Option<&'b Publish>, ()> {
    Ok(session.hooks.get(session)?
        .events
        .as_ref()
        .and_then(|events| events.publish.as_ref()))
}

struct EventHooks {
    publish : Option<Publish>,
}

struct EventLog {}

impl EventLog {
    fn publish(&mut self, _plugin : Option<&Publish>) {
        println!("Launch some external command");
    }
}

struct HookConfig {
    events: Option<EventHooks>,
}

struct LazyHookConfig {
    settings: LazyCell<HookConfig>,
}

impl LazyHookConfig {
    pub fn get(&self, session : &Session) -> Result<&HookConfig, ()> {
        self.settings.try_borrow_with(|| Ok(HookConfig {
            events: Some(EventHooks {
                publish: Some(Publish {}),
            }),
        }))
    }
}

struct Publish {}

struct Session {
    hooks: LazyHookConfig,
    event_log : EventLog,
}

impl Session {
    fn publish_to_event_log(&mut self) {
        match get_publish_plugin(&self) {
            Ok(plugin) => {
                self.event_log.publish(plugin);
            }
            Err(_) => {
                eprintln!("Some error");
            }
        }
    }
}

(Playground)

Errors:

   Compiling playground v0.0.1 (/playground)
error[E0495]: cannot infer an appropriate lifetime for autoref due to conflicting requirements
 --> src/lib.rs:4:22
  |
4 |     Ok(session.hooks.get(session)?
  |                      ^^^
  |
note: first, the lifetime cannot outlive the lifetime 'a as defined on the function body at 3:23...
 --> src/lib.rs:3:23
  |
3 | fn get_publish_plugin<'a, 'b>(session : &'a Session) -> Result<Option<&'b Publish>, ()> {
  |                       ^^
note: ...so that reference does not outlive borrowed content
 --> src/lib.rs:4:8
  |
4 |     Ok(session.hooks.get(session)?
  |        ^^^^^^^^^^^^^
note: but, the lifetime must be valid for the lifetime 'b as defined on the function body at 3:27...
 --> src/lib.rs:3:27
  |
3 | fn get_publish_plugin<'a, 'b>(session : &'a Session) -> Result<Option<&'b Publish>, ()> {
  |                           ^^
  = note: ...so that the expression is assignable:
          expected std::result::Result<std::option::Option<&'b Publish>, _>
             found std::result::Result<std::option::Option<&Publish>, _>

error: aborting due to previous error

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

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

Got it to compile with the following change:

fn get_publish_plugin<'a,'b>(_session : &'a Session) -> Result<Option<&'b Publish>, ()> {
    Ok(Some(&Publish {}))
}

As far as I understand this (I am not experienced at all), without specifying lifetimes, it would have been equivalent to

fn get_publish_plugin<'a>(_session : &'a Session) -> Result<Option<&'a Publish>, ()>

meaning that the Publish reference had the same lifetime as the Session one, preventing it to be released when you need the mutable reference…

@rams3s, thanks for the help! That does indeed get my limited example here to compile, but in the full code I simplified it from, it causes an E0495 (which doesn’t even show up in the error index!) inside get_publish_plugin, saying “cannot infer an appropriate lifetime for autoref due to conflicting requirements”.

Any other ideas? I’ll try to get the example above working with the real contents of that function.

In the solution given by @rams3s, that 'b could just as well be 'static, because it’s a lifetime that’s totally disconnected from the inputs.

If in your real code, the Plugin needs to borrow from the Session, then they will have to have connected lifetimes, and unfortunately this does block the entire Session object from mutability.

If that Plugin is at least distinct from EventLog member of the Session, you could return both:

fn get_publish_plugin<'a>(session: &'a mut Session)
    -> Result<(&'a mut EventObject, Option<&'a Publish>), ()>

… which is the same as with implicit lifetimes:

fn get_publish_plugin(session: &mut Session)
    -> Result<Option<(&mut EventObject, &Publish)>, ()>

Could also be Result<(&mut EventObject, Option<&Publish>), ()> if you want.

2 Likes

@cuviper, the Plugin does indeed come from the Session's EventLog. I’ve updated the code and playground in my original post with the contents of get_publish_plugin and surrounding structs.