Hi everyone,
While playing around with Rust, I think I just ended up on a nice use case for associated type constructors, which currently do not exist (although there's a proposal to add them). So I'm looking for the nicest workaround that today's Rust will give me, if anyone feels like helping.
Here is the design which I am trying to express:
- Let there be two actors A and B, with a communication channel in between
- The communication channel can take many forms: shared memory, network connection, message-passing library... My goal in this project is to abstract this diversity away.
- For the use cases which I have in mind, the proper abstraction is distributed shared memory. I want to give A and B the illusion that they share memory (with relaxed consistency semantics) even if they don't.
- I want the abstraction to have as low a runtime overhead as possible. If A and B know in advance how they want to communicate, the compiler should generate optimal code for that communication channel.
From a code point of view, my current interface design is somewhat similar to this:
// Shared data block abstraction, somewhat inspired by RefCell
trait SharedData<T> {
// Get read access to the latest available copy of the shared data.
// The access is guaranteed by the implementation to be data-race-free.
type DataRef: Deref<Target=T> + Drop;
fn borrow(&self) -> DataRef;
// Get write access to the shared data. Changes will be atomically
// propagated to clients once the reference wrapper gets out of scope.
type DataRefMut: DerefMut<Target=T> + Drop;
fn borrow_mut(&mut self) -> DataRefMut;
}
// A communication channel between A and B. Can be unsynchronized shared
// memory (for coroutines), synchronized shared memory (for threads),
// UNIX sockets or MPI (for distributed processes)... That's abstracted away!
trait CommunicationChannel {
// Shared data block implementation that is optimal for the underlying channel
type SharedDataImpl<T>: SharedData<T>; // THIS WON'T COMPILE
// Create a new shared data block with some initial value
fn new_shared_data<T>(&mut self, init: T) -> SharedDataImpl<T>;
}
This is a highly simplified version of the interface which I actually have in mind. In particular, it lacks...
- Several important generic bounds needed for implementation
- Better synchronization mechanisms than polling
- A mechanism to forbid concurrent writes
But the minimized code is enough to show the problem, which is that I cannot declare the SharedDataImpl associated type constructor in today's Rust.
This interface could be made legal by an appropriate version of the associated type constructor RFC proposal ( https://github.com/rust-lang/rfcs/pull/1598 ). For now, people on this Github issue have mostly focused on genericity over lifetimes, whereas I want to be generic over types. Perhaps I should submit some feedback on why I think that genericity over types is also useful. Or not: looking at the RFC, the author seems to already have this in mind.
In meantime, I have thought about two workarounds:
- Use the new
impl SharedData<T>
syntax. I don't think this is allowed on a trait function. In addition, this is a nightly feature, and it does not quite match the semantics that I want to express in my trait (where the function return type will always be the same for any given T). - Give up on full static polymorphism for now, and return a
Box<SharedData<T>>
. Will require some unnecessary dynamic memory allocation and a vtable, and has the same issue as above, but at least I think it would work on stable.
Can someone more knowledgeable about Rust than me think about a smarter way to do this that I have overlooked?