Would Non-Lexical Lifetimes (NLL) make this (async) code work?


#1

The code in question:

//! Echo server running on a single core microcontroller

fn main() {
    let Serial { mut tx, mut rx } = Serial::new().unwrap();

    // `bytes` is an infinite stream of bytes
    let mut bytes = rx.bytes();
    let mut pending_write: Option<Write> = None;
    loop {
        // If there's new data
        if let Async::Ready(byte) = bytes.poll() {
            // Drive any pending write to its completion because we are going
            // to ...
            pending_write.take().map(|w| w.wait());
            //* `Tx` loan ends here because `pending_write` is always `None` at
            //* this point

            // ... queue a new write
            pending_write = Some(tx.write(byte));
            //* ^ `Tx` loan starts here
        }

        // Check if the pending write completed
        if let Some(Async::Ready(())) =
            pending_write.as_mut().map(|w| w.poll())
        {
            // Done
            pending_write = None;
        }

        // Other "concurrent" tasks could be running here
    }
}

See the appendix for the signature of the involved methods and structs.

// are the original comments. //* are additional comments to explain why this code should work.

Right now this errors with:

error[E0499]: cannot borrow `tx` as mutable more than once at a time
  --> examples/aio-echo.rs:39:34
   |
39 |             pending_write = Some(tx.write(byte));
   |                                  ^^
   |                                  |
   |                                  second mutable borrow occurs here
   |                                  first mutable borrow occurs here
...
50 | }
   | - first borrow ends here

error: aborting due to previous error

because the compiler assumes that pending_write borrows tx for the whole scope of the loop. But, tx is actually only borrowed from pending_write = Some(tx.write(byte)) up to the next iteration’s pending_write.take().map(|w| w.wait()) thus tx is not being borrowed at the moment that pending_write = Some(tx.write(byte)) must be executed.

Will the planned NLL feature accept this code? AIUI, the borrow checker will gain the ability to keep track of loans that are smaller than the immediately outer scope but it seems that the borrow checker must also be aware that the None variant doesn’t borrow anything (I don’t know if it does that already).

Appendix

enum Async {
    NotReady,
    Ready(T),
}

trait Future {
    fn poll(&mut self) -> Async<Self::Item>;

    fn wait(mut self) -> Self::Item {
        loop {
            if let Async::Ready(item) = self.wait() {
                return item;
            }
        }
    }
}

/// Serial communication interface
struct Serial {
    /// Transmitter
    tx: Tx,
    /// Receiver
    rx: Rx,
}

/// Transmitter
struct Tx {
    _0: (),
}

impl Tx {
    /// Sends a byte through the transmitter
    ///
    /// This returns a future because the transmitter may be already in the
    /// process of sending one byte.
    fn write<'t>(&'t mut self, byte: u8) -> Write<'t> {
        // ..
    }
}

/// A transmitter write request
struct Write<'t> {
    _tx: PhantomData<&'t mut Tx>,
    byte: u8,
    done: bool,
}

impl<'t> Future for Write<'t> {
    // ..
}

cc @nikomatsakis