Representing type dependencies

Hi, I am thinking about how to represent a repeating pattern I face with types. The pattern when dealing with ffi / hardware / c lib is often as following:

  • when connecting to the device, you get a connection handle
  • the connection handle typically is Send but not Sync and needs a Drop to close the connection
  • There are symbols on the device that you can read and write to. To do this, you need to get a symbol handle by using the connection. These also have a Drop, which needs a connection handle.
  • The symbol represents builtin types like f32 or structs

Basically you want several types safe Symbol handles, which all need a connection handle.

Lets say Con is the connection type. I have seen these in libraries:

struct SymbolHandle<'con, T> {
    con: &'con Con
    // marker T
}

or

struct SymbolHandle<T> {
    con: Rc<Con>
    // marker T
}

or

struct SymbolHandle<T> {
    con: Arc<Mutex<Con>>
    // marker T
}

I don't really like them, because they are either not Send or need a Mutex. Is there a way to implement this in a library, so that a user of the library can create a type safe "environment" type that is send? Maybe by using just one handle for everything, that one can register the symbols?

Thanks

After creating the symbol handle, is it safe to Send the SymbolHandle<T> to another thread? AFAICT, it is not, given that your Con is Send but not Sync, and that manipulating the SymbolHandle<T> from another thread may cause contentions on Con, which needs to be resolved.

1 Like

One general solution to this problem, where you have a single resource which you want to operate on from multiple threads (that is, using handles that are Send), is the “actor pattern”. Create a channel whose sender is cloned to each SymbolHandle and whose receiver belongs to a thread (or async task), the actor, that receives commands from the channel and manipulates the connection handle as commanded.

This provides serialization (that is, all operations on the connection happen in sequence) similar to a Mutex but without actually using one — no thread is ever blocked on waiting for another thread’s command to execute, unless it wants to be. Furthermore, the connection will always be dropped on the actor thread rather than the thread that drops the last handle as it would be with Arc.

However, this is most useful for cases where the shared resource (connection) is not only not Sync, but not Send; it's not clear that it benefits your use case.

Can you say what specifically you’re hoping to gain by not using a Mutex?

2 Likes

This is dependent on the actual device, but often Send + !Sync means that the access needs to be sequential, so Arc should be fine. Sadly, documentation is often not very clear about this.

I am aware of the actor pattern, but this usually introduces quite some overhead (especially if want a result/error back), and I use it as a "last resort". I have never used it on the "library side" though.
I guess, if it is allowed to use the symbolhandle from different threads then Arc<Mutex> is fine. If not, it seems like message handling / actors is necessary.

Thanks