Resolved: Implementing a handle table

I am trying to write software which is, in spirit, closest to writing a system call handler for a virtual machine emulator I'm writing. It's a RISC-V emulator, and the idea is that ECALL invokes services in the emulator, rather than dispatching to supervisor- or machine-mode. Hence, the emulator is user-mode only. But that's not the important part. This just sets the stage for my question.

In the emulator proper, I have a handle table implementation, which is itself just a vector of HandleTableEntry-ies. A HandleTableEntry is defined like so:

data HandleTableEntry = Option<Rc<RefCell<&dyn Manageable>>>;

The Manageable trait is defined something like so:

trait Manageable {
    fn close(&mut self, em: &mut EmState);
    // .... other stuff here, not important ....
}

When the "user-mode" code makes an e-call to close a handle, I would like to invoke the close method in the handle table. Here's how I do it so far:

let which = em.cpu.xr[10];  // register a0 = handle to close
let option_resource = em.handle_table[which];
match option_resource {
    Some(rc_refcell_obj) => {
        let obj = rc_refcell_obj.clone();
        let mut obj = obj.borrow_mut();
        obj.close(em);
    }
    None => (), // ignore
}
em.handle_table[which] = None;

Note that this is one of many, many, many attempts to get this program logic working. However, nothing I do here is able to make this code compile. The errors vary, depending on my specific selection of code. In this case, it complains about obj not being able to be borrowed mutably. But I get different errors if I structure this code differently.

Regardless, the errors I'm receiving is not important. What is important is that I'm clearly writing this code with a completely incorrect pattern. People are writing OSes in Rust complete with user-spaces, so somehow implementing a file descriptor table-like thing has to be possible. But, I just can't figure out how to do this. Can anyone please offer advice on how to best structure the code so I can have a proper handle table implementation?

BTW: the most updated code in its proper context is here: ~vertigo/incubator (master): vm-ecosystem/vmos/src/main.rs - sourcehut git . Ignore the comments preceding the highlighted block. As per custom in software development, the comments are misleading and worse than wrong in this case (it describes yesterday's attempt at solving the problem I'm running into, and I forgot to change the comments before pushing. Sorry.)

Thanks in advance.

I just have a couple quick comments.

One thing that pops out is the & here -- it should be removed like this:

type HandleTableEntry = Option<Rc<RefCell<dyn Manageable>>>;

And likewise in other places where you're using RefCell<&dyn ...>.

A dyn Xxx is a dynamically sized type. The Rc is the "pointer" type, or the allocated block containing the dynamically sized type, so you probably don't want a reference type inside it. Rc gives you a static lifetime and allows sharing, but by putting a reference inside it you then need a lifetime parameter, and that may be causing some of your difficulties. By removing that inner reference, you can remove the lifetime parameter (as above), and in all other places that use this type.

My second comment is a more general one. If you're relatively new to Rust, I suggest using an enum instead of trait objects (dyn Xxx). A enum is limited to a fixed set of variants at compile time, but I assume in the emulator you do have a fixed set of entry types that implement Manageable.

The problem you had above with trait objects is just one of many that you may run into. An enum is easier to work with and has less limitations. You should be able to simplify, and it's possible that you won't need Rc or RefCell at all, although it is too soon to say for sure.

Let's say the enum is called HandleTableEntry. Although the type in each variant of this enum implements the same trait (Manageable), to call one of Manageable's functions in an arbitrary entry you'll have to match on it and manually call that function for each variant's type (each entry that implements Manageable). If that is too onerous, the enum_dispatch crate can generate this match statement for you in a very convenient way.

3 Likes

I tried this originally; this won't compile because dyn T is an unsized type, and vectors need a sized type as its inner component. &dyn T is guaranteed to be sized.

I thought about this as well, but the combinatorial explosion in boilerplate code this produces is untenable in the long run. I've used trait objects before (I've also written my own simple GUI implementation with them), and they've not given me problems before. Also, looking at the errors I'm receiving while compiling, nothing seems to be pointing towards trait objects as the cause of the issues I'm running into.

To qualify my Rust experience to date, I'm not new to Rust. But, I'm not a grey-beard with it either. This is why I think the problem has more to do with the shape of my code and less to do with specific choice of types.

Thanks.

The size of Rc is fixed because it is a pointer type, which is why it can be stored in a Vec, whether its heap-allocated contents are sized or not. So I'm not sure why it didn't work for you and I think it's worth exploring. The following works (playground), so perhaps you can try to see where this went wrong in your actual implementation and post the error.

It may be that you encountered the Sized error at a different place. For example, a plain RefCell<dyn Manageable> (without the Rc) won't work because RefCell is not a pointer type, it is just a wrapper.

use std::cell::RefCell;
use std::rc::Rc;

trait Manageable {
    fn do_something(&self);
}

type HandleTableEntry = Option<Rc<RefCell<dyn Manageable>>>;

struct EntryType {}
impl Manageable for EntryType {
    fn do_something(&self) {
        println!("do_something");
    }
}

fn main() {
    let mut handle_table: Vec<HandleTableEntry> = Vec::new();
    handle_table.push(Some(Rc::new(RefCell::new(EntryType {}))));

    handle_table.iter().for_each(|entry| {
        if let Some(entry) = entry {
            RefCell::borrow(entry).do_something();
        }
    });
}

I encourage you to actually compile my code and see for yourself. You'll see that I'm not giving wrong information.

That said, your playground example is not doing exactly what my code is doing, because you're borrowing immutably everywhere. I need to borrow mutably.

I have since found the issue, and it's the dumbest mistake possible (as it always is).

I had:

type HandleTableEntry<'emstate> = Option<Rc<RefCell<&'emstate dyn Manageable>>>;

it should read:

type HandleTableEntry<'emstate> = Option<Rc<RefCell<&'emstate mut dyn Manageable>>>;

Of course I can't borrow the inner-most type mutably unless it itself is declared mutable!

Issue resolved.

Many thanks for taking the time to help!

I was responding to what you said about a problem due to an unsized type inside an Rc. Just trying to suggest a simplification. :slight_smile:

That said, your playground example is not doing exactly what my code is doing, because you're borrowing immutably everywhere. I need to borrow mutably.

FWIW, it is possible to get rid of the reference in &dyn and its associated lifetime, and still access it mutably by changing the RefCell::borrow to RefCell:borrow_mut. (modified playground)

I just noticed that you're using a later version of Rust than I am. I bet that might have something to do with it. I'm on 1.76 myself.

Is this the line where you get the error? ~vertigo/incubator (master): vm-ecosystem/vmos/src/main.rs - sourcehut git

A difference between your code and the example from @jumpnbrownweasel is that you coerce the program instance to dyn Manageable before it's inside the Rc. That would fail if the inner reference is removed without changing anything else, since RefCell<dyn Manageable> is unsized and can't be stored as-is in a variable.

Assuming that's where the error is from, of course.

1 Like

No; as of the most recent commit, the errors I receive are found here. ~vertigo/incubator (master): vm-ecosystem/vmos/src/emul_state.rs - sourcehut git and ~vertigo/incubator (master): vm-ecosystem/vmos/src/emul_state.rs - sourcehut git .

Someone else (on Mastodon) contributed code should resolve the 173-176 error, but I can't yet figure out exactly why it works. So I've been engaging him there. But that then leaves the SDL error to deal with. I'll post a separate thread topic when I've reached the end of my investigations.

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.