0

I'm trying to understand how I can attach pointers to objects (frames) which are allocated during Foo::new(), to some other objects (frame_descriptors) in the same block.

It seems like there are issues both with using the frames variable twice, and with returning the frames. I'm not sure I completely follow what the problem is/how to resolve it.

Would anyone be willing to offer a short explanation or guidance? I'd be very grateful.

const PAGE_SIZE: usize = 4096;
const NUM_FRAMES: usize = 1000;

pub struct Frame {
    pub data: [u8; PAGE_SIZE],
}

impl Default for Frame {
    fn default() -> Self {
        Self { data: [0; PAGE_SIZE] }
    }
}

pub struct FrameDescriptor<'bp> {
    pub frame: &'bp mut Frame,
    pub is_dirty: bool,
}

impl Default for FrameDescriptor<'_> {
    fn default() -> Self {
        let frame_ptr: *mut Frame = std::ptr::null_mut();
        let frame_ref: &mut Frame = unsafe { &mut *frame_ptr };
        Self { frame: frame_ref, is_dirty: false }
    }
}

pub struct BufferPool<'bp> {
    pub frames: Box<[Frame; NUM_FRAMES]>,
    pub frame_descriptors: Box<[FrameDescriptor<'bp>; NUM_FRAMES]>,
}

// ----> ISSUE OCCURS HERE, IN NEW <-----
impl BufferPool<'_> {
    pub fn new() -> Self {
        let mut frames: Box<[Frame; NUM_FRAMES]> =
            Box::new(core::array::from_fn(|_| Default::default()));

        let mut frame_descriptors: Box<[FrameDescriptor; NUM_FRAMES]> =
            Box::new(core::array::from_fn(|_| Default::default()));

        for i in 0..NUM_FRAMES {
            frame_descriptors[i].frame = &mut frames[i];
        }

        Self { frames, frame_descriptors }
    }
}

Here are the compiler errors on Nightly Rust, as of 12/11/2022:

error[E0499]: cannot borrow `frames[_]` as mutable more than once at a time
   --> src/main.rs:197:42
    |
189 |       pub fn new() -> Self {
    |                       ---- return type is BufferPool<'1>
...
197 |               frame_descriptors[i].frame = &mut frames[i];
    |                                            ^^^^^^^^^^^^^^ `frames[_]` was mutably borrowed here in the previous iteration of the loop
...
200 | /         Self {
201 | |             frames,
202 | |             frame_descriptors,
203 | |             free_list: (0..BUF_POOL_NUM_FRAMES).collect(),
...   |
206 | |             clock_hand: 0,
207 | |         }
    | |_________- returning this value requires that `frames[_]` is borrowed for `'1`

error[E0515]: cannot return value referencing local data `frames[_]`
   --> src/main.rs:200:9
    |
197 |               frame_descriptors[i].frame = &mut frames[i];
    |                                            -------------- `frames[_]` is borrowed here
...
200 | /         Self {
201 | |             frames,
202 | |             frame_descriptors,
203 | |             free_list: (0..BUF_POOL_NUM_FRAMES).collect(),
...   |
206 | |             clock_hand: 0,
207 | |         }
    | |_________^ returns a value referencing data owned by the current function

error[E0505]: cannot move out of `frames` because it is borrowed
   --> src/main.rs:201:13
    |
189 |       pub fn new() -> Self {
    |                       ---- return type is BufferPool<'1>
...
197 |               frame_descriptors[i].frame = &mut frames[i];
    |                                            -------------- borrow of `frames[_]` occurs here
...
200 | /         Self {
201 | |             frames,
    | |             ^^^^^^ move out of `frames` occurs here
202 | |             frame_descriptors,
203 | |             free_list: (0..BUF_POOL_NUM_FRAMES).collect(),
...   |
206 | |             clock_hand: 0,
207 | |         }
    | |_________- returning this value requires that `frames[_]` is borrowed for `'1`
Gavin Ray
  • 595
  • 1
  • 3
  • 10
  • Does this answer your question? [Why can't I store a value and a reference to that value in the same struct?](https://stackoverflow.com/questions/32300132/why-cant-i-store-a-value-and-a-reference-to-that-value-in-the-same-struct) – Finomnis Dec 11 '22 at 17:01
  • I read that and it had a very good explanation of stack addresses, but it didn't offer an explanation of how to solve the problem. It said "Don't do it, or use these crates". Also, my example uses heap-allocated memory, which won't suffer from address changes so the error doesn't make sense to me -- the value isn't owned by the current function, it's on the heap? – Gavin Ray Dec 11 '22 at 17:08
  • Ownership has nothing to do with where it lives. Values on the heap also have to be owned by someone, as they have to be destroyed at some point. Having values on the heap doesn't guarantee that they don't change address, either. You can simply move them back to the stack, or on another heap address. – Finomnis Dec 11 '22 at 17:13
  • *"didn't offer an explanation of how to solve the problem"* - the problem is that there is no simple solution. Paradigms like this simply don't work with Rust, solving it requires a re-thinking of the solution itself. It's especially hard for programmers used to other languages, because they first have to un-learn the patterns from those languages. – Finomnis Dec 11 '22 at 17:16
  • The basic paradigm of Rust is to have absolutely zero undefined behaviour. The entire rest kind of the language was kind of developed to achieve that goal. Borrowing rules are one of those. For `Box`, for example, if you have `let x = Box::new(...)`, the object is on the heap. Through `let y = *x`, you can move it back to the stack. There's nothing stopping you, and all internal references would break. Dangling references are undefined behaviour, so Rust prevents you from creating self-referential structures in the first place. – Finomnis Dec 11 '22 at 17:19
  • If you can't think of another way to solve your problem, then references are simply the wrong tool. What you actually want is `Rc`, a reference counting smart pointer. – Finomnis Dec 11 '22 at 17:22
  • I once wrote quite an extensive answer about this entire topic, maybe it helps you as well: https://stackoverflow.com/questions/73340544/rust-how-to-have-multiple-mutable-references-to-a-stack-allocated-object/73344290#73344290 – Finomnis Dec 11 '22 at 17:24
  • In your special case, however, I'd simply not use references. I don't see the advantage of using references here. The data structure is a vector, and your references could easily be replaced by `usize` indices. That's one of the ways this problem usually gets tackled. – Finomnis Dec 11 '22 at 17:28
  • I super appreciate the thoughtfulness in your replies and the additional resources! I'll explore other designs, but my curiosity also won't let me sleep until I figure out a way to get this to compile. I have it nearly working almost!! https://play.rust-lang.org/?version=nightly&mode=debug&edition=2021&gist=590bd4239ec2313eb32c4e4b5d1fcfac – Gavin Ray Dec 11 '22 at 17:41
  • 1
    I'm afraid that I must tell you that it won't work. There's an entire video about this that explains it in much detail: https://www.youtube.com/watch?v=xNrglKGi-7o – Finomnis Dec 11 '22 at 17:43

2 Answers2

1

The simple answer is: You can't. You need to rework your object structure.

In Rust, every object is trivially movable via memcpy. That means, it's impossible to create self-referential data structures, as they can't be moved without creating dangling pointers, which is undefined behaviour. Rust has a zero-undefined-behaviour tolerance.

That said, there is ways around it. Let me give you some feedback for your code:

  • Never use unsafe to create invalid references. This is called unsound. Please read this article about soundness before using unsafe.
  • The idea of storing a nullpointer in a reference is probably ported over from C/C++. Rust has the Option type for that instead.
  • Ask yourself: Why do you need the reference in the first place? In your case, I don't think it adds any information. The frames and frame_descriptors have an identical count, so it would be easy to match them through indices.

For more information, see Why can't I store a value and a reference to that value in the same struct?.

While I usually don't link against external references, this Youtube video shows the problem very in-depth and hands on. I highly recommend it.

Finomnis
  • 18,094
  • 1
  • 20
  • 27
0

Here's a working solution if you're alright with it not being during ::new() and are okay with using a Option to represent "initialized or not"

(This technically still does not answer the question, which I think should be achievable through some usage of Pin<> and advanced techniques, so I will not mark it as the answer)

#[derive(Copy, Clone)]
struct Frame {
    data: [u8; 4096],
}

#[derive(Copy, Clone)]
struct FrameRef<'pool> {
    frame: &'pool Frame,
    dirty: bool,
}

struct Pool<'pool> {
    frames: Box<[Frame; 100]>,
    refs: Box<[Option<FrameRef<'pool>>; 100]>,
}

impl<'pool> Pool<'pool> {
    fn new() -> Pool<'static> {
        Pool {
            frames: Box::new([Frame { data: [0; 4096] }; 100]),
            refs: Box::new([None; 100]),
        }
    }

    fn init(&'pool mut self) {
        for i in 0..100 {
            self.refs[i] = Some(FrameRef {
                frame: &self.frames[i],
                dirty: false,
            });
        }
    }
}
Gavin Ray
  • 595
  • 1
  • 3
  • 10
  • Does it also work if you are trying to use it? :) – Finomnis Dec 11 '22 at 18:15
  • Also yes, I agree that what you are trying to do **is** achievable with `unsafe` and `Pin`, and does get used like that. But it requires advance Rust knowledge to be done soundly. But yes, it is of course possible. Unsafe Rust is basically C ;) I just don't usually recommend that in SO posts, because it is usually done way prematurely, and usually there are better solutions. – Finomnis Dec 11 '22 at 18:16
  • I personally have wittnessed occasions where people did go down that Route initially, but had to scrap it and redo it without using unsafe, as too many bug reports of crashes came in. I (together with the `tokio` team) rewrote a piece of unsafe code to be purely safe in `tokio`, for exactly that reason: https://github.com/tokio-rs/tokio/issues/4639 – Finomnis Dec 11 '22 at 18:20
  • About your solution: As mentioned in the video I linked, yes, this is a way to create such a struct. But it's **very** awkward to use. It breaks the borrow checker at every possible ocation. For example: https://play.rust-lang.org/?version=nightly&mode=debug&edition=2021&gist=fed99b607876400ec426a7bce3534456 – Finomnis Dec 11 '22 at 18:39
  • It does not work when I try to use it; Complaints about "cannot borrow as mutable twice" -- I assumed this was me being a Rust noob though and that there was a way around it haha – Gavin Ray Dec 11 '22 at 18:50
  • Another sidenote: `Box::new([...; 100])` doesn't actually behave how you might think. It creates the `[...; 100]` object **on the stack** and then moves it to the heap. This is actually a shortcoming of Rust, in my opinion. Currently the only way to create arrays directly on the heap is to use `vec![...; 100]` instead. I hope this changes some day, but it works for now. That said, there are external crates that implement this. There is also an RFC to add this to Rust: https://doc.rust-lang.org/beta/unstable-book/language-features/box-syntax.html – Finomnis Dec 11 '22 at 22:01