Shared Mutable Variables and Discrete Access

Hi, I'm working on some networking code where I need to manually manage buffers, and I find myself creating small helper functions to help with bookkeeping and other minutiae, but I keep running into two similar issues around lifetimes and mutability.

The first one is around shared mutable access to a variable, idx in this example. read_u32 and read_u16 both require mutable access to idx, but only one function is ever called at a time. Is there a way to let the compiler know that the two functions wont ever execute concurrently?

fn read_buf() -> (u32, u16) {
    let buf = vec![0u8; 32];
    let mut idx = 0;

    let mut read_u32 = || {
        idx += 4;
        u32::from_be_bytes(buf[idx - 4..idx].try_into().unwrap())
    };

    let mut read_u16 = || {
        idx += 2;
        u16::from_be_bytes(buf[idx - 2..idx].try_into().unwrap())
    };

    // error[E0499]: cannot borrow `idx` as mutable more than once at a time
    let (a, _b, c) = (read_u32(), read_u32(), read_u16());
    (a, c)
}

The second example involes owning a buffer and handing out immutable references to that buffer. I'm not sure if I'm approaching this the right way with Generators, but I'm not sure how to say the lifetime of the yielded &str should be shorter than the next call to Generator::resume.

#![feature(generators, generator_trait)]
use std::{fmt::Write, ops::Generator};

fn buf_generator<'a, 'gen>() -> impl Generator<&'a str, Yield = &'gen str> + 'gen {
    let mut buf = String::new();

    let gen = move |mut url: &str| loop {
        buf.clear();
        write!(&mut buf, "{url}?some=params");

        // error[E0626]: borrow may still be in use when generator yields
        url = yield &buf[..];
    };

    gen
}

Same thing as buf_generator, but as a regular re-entrant function

fn buf_builder
use std::fmt::Write;

fn buf_builder<'gen>() -> impl FnMut(&'_ str) -> &'gen str {
    let mut buf = String::new();

    let builder = move |url: &str| {
        buf.clear();
        write!(&mut buf, "{url}?some=params");

        // error: captured variable cannot escape `FnMut` closure body
        return &buf[..];
    };

    builder
}

No. The usual way to handle this is to put buf and idx in a struct and make read_u32 and read_u16 be methods on that struct (or more generally: functions which take a reference to the mutable state rather than closing over a reference to the mutable state — they don't have to be methods specifically).

I'm not sure how to say the lifetime of the yielded &str should be shorter than the next call to Generator::resume.

I'm not familiar with the generators feature, but that looks impossible — this is the classic GAT “lending iterator” pattern which can only be done if the trait you are using (here Generator) was defined to allow it using GATs.

There's two potential ways to handle this.

The first is to invert the control and use an "internal iterator" instead of an external iterator. This is a bit more messy than usual due to having an input.

pub fn buf<'u>(mut url: &'u str, mut cb: impl FnMut(&str) -> ControlFlow<(), &'u str>) {
    let mut buf = String::new();
    loop {
        buf.clear();
        write!(&mut buf, "{url}?some=params").unwrap();
        match cb(&buf) {
            ControlFlow::Continue(next) => url = next,
            ControlFlow::Break(()) => break,
        }
    }
}

The second is to just write it as a struct.

use std::fmt::Write;

struct Buf {
    buf: String,
}

impl Buf {
    fn next(&'_ mut self, url: &str) -> &'_ str {
        self.buf.clear();
        write!(&mut self.buf, "{url}?some=params").unwrap();
        return &self.buf;
    }
}

To abstract over these structs requires generic associated types which just became available on stable.

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.