Ownership when handling errors

Hi all,

I'm very new to rust and of course, I'm stuck with an ownership issue, what I want to achieve is very simple, I have a function which waits on an IMAP inbox using the IDLE command and when something changes it shows a desktop notification.

My issue is that sometimes the IDLE fails because of the server resetting the connection and I of course try to reconnect to handle this error.

fn notify_idle(config: Arc<Box<Config>>, mbox: &str) -> Result<(), String> {
    let mut imap_session = connect(&config)?;

    imap_session.select(&mbox).map_err(|err| err.to_string())?;

    list_unseen(&mut imap_session, mbox);

    if !config.wait_idle {
        imap_session.logout().map_err(|err| err.to_string())?;
        Ok(())
    } else {
        loop {
            info!("Waiting for new messages on maibox {}", mbox);

            let mut idle = match imap_session.idle() {
                Ok(i) => i,
                Err(e) => {
                    error!("{}", e);

                    let mut idle;
                    loop {
                        imap_session = connect(&config)?;
                        imap_session.select(&mbox).map_err(|err| err.to_string())?;
                        idle = imap_session.idle();
                        if idle.is_ok() {
                            break;
                        }
                    }
                    idle.unwrap()
                }
            };

            idle.set_keepalive(Duration::from_secs(60) * 15);
            idle.wait_keepalive().map_err(|err| err.to_string())?;

            list_unseen(&mut imap_session, mbox);

            thread::sleep(Duration::from_secs(1));
        }
    }
}

rustc complains because I'm obviously violating the ownership rules but I really don't know how to solve this in a idiomatic way. Any help please?

error[E0506]: cannot assign to `imap_session` because it is borrowed
   --> src/main.rs:117:25
    |
110 |             let mut idle = match imap_session.idle() {
    |                                  -------------------
    |                                  |
    |                                  borrow of `imap_session` occurs here
    |                                  a temporary with access to the borrow is created here ...
...
117 |                         imap_session = connect(&config)?;
    |                         ^^^^^^^^^^^^ assignment to borrowed `imap_session` occurs here
...
126 |             };
    |              - ... and the borrow might be used here, when that temporary is dropped and runs the destructor for type `std::result::Result<imap::extensions::idle::Handle<'_, native_tls::TlsStream<std::net::TcpStream>>, imap::error::Error>`

error[E0499]: cannot borrow `imap_session` as mutable more than once at a time
   --> src/main.rs:118:25
    |
110 |             let mut idle = match imap_session.idle() {
    |                                  -------------------
    |                                  |
    |                                  first mutable borrow occurs here
    |                                  a temporary with access to the first borrow is created here ...
...
118 |                         imap_session.select(&mbox).map_err(|err| err.to_string())?;
    |                         ^^^^^^^^^^^^ second mutable borrow occurs here
...
126 |             };
    |              - ... and the first borrow might be used here, when that temporary is dropped and runs the destructor for type `std::result::Result<imap::extensions::idle::Handle<'_, native_tls::TlsStream<std::net::TcpStream>>, imap::error::Error>`

You could restructure the code to reassign imap_session outside the borrow:

fn notify_idle(config: Arc<Box<Config>>, mbox: &str) -> Result<(), String> {
    'connect: loop {
        let mut imap_session = connect(&config)?;

        imap_session.select(&mbox).map_err(|err| err.to_string())?;

        list_unseen(&mut imap_session, mbox);

        if !config.wait_idle {
            imap_session.logout().map_err(|err| err.to_string())?;
            return Ok(());
        }
        loop {
            info!("Waiting for new messages on maibox {}", mbox);

            let mut idle = match imap_session.idle() {
                Ok(i) => i,
                Err(e) => {
                    error!("{}", e);
                    contiue 'connect;
                }
            };

            idle.set_keepalive(Duration::from_secs(60) * 15);
            idle.wait_keepalive().map_err(|err| err.to_string())?;

            list_unseen(&mut imap_session, mbox);

            thread::sleep(Duration::from_secs(1));
        }
    }
}
1 Like

Oh wow, never seen this syntax, is this like a goto ?

It is continue but you can specify which loop you want to continue (only if you are inside it).
Also works with break.

This is a bit subtle and there are several ways to go around it. I think that this should also work:

loop {
    info!("Waiting for new messages on maibox {}", mbox);

    let mut new_imap_session = None;

    let mut idle = match imap_session.idle() {
        Ok(i) => i,
        Err(e) => {
            error!("{}", e);

            loop {
                new_imap_session = Some(connect(&config)?);
                let imap_session_ref = new_imap_session.as_mut().unwrap();
                imap_session_ref.select(&mbox).map_err(|err| err.to_string())?;
                if let Some(idle) = imap_session_ref.idle() {
                    break idle;
                }
            }
        }
    };

    idle.set_keepalive(Duration::from_secs(60) * 15);
    idle.wait_keepalive().map_err(|err| err.to_string())?;

    list_unseen(&mut imap_session, mbox);

    drop(idle);
    if let Some(new_imap_session) = new_imap_session {
        imap_session = new_imap_session;
    }

    thread::sleep(Duration::from_secs(1));
}

Now that I wrote it, I'll post it too, but @s3bk's solution is probably better.

Thank you both! :slight_smile:

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.