metal-rs
metal-rs copied to clipboard
RFC: Send+Sync on metal objects
Given that all the interaction is done via message sending, I don't see why Send+Sync wouldn't be the case?
Proposal
- Have
foreign_obj_typeforcefully implementSend+Sync - Mark all the methods (non-static, corresponding to ObjC messages) with
&mut self - Mark atomic property get/set accessors with
&self - Mark non-atomic properties with
&mut self.
Clarification
Atomicity of properties in ObjC guarantees no visible partial writes. While Apple docs say this is not thread safe, they mean data-races in fact, and it's perfectly thread-safe in Rust terms (of memory safety).
@gfx-rs/metallists Comments? Questions? Objections? This is an important thing for us to decide.
Also pinging @SSheldon, the author of objc bindings in Rust
Sorry, I realized why this isn't going to work :disappointed: . &mut self means nothing if we are able to have multiple independent rust objects corresponding to the same ObjC object. There has to be something our Rust objects share between each other, e.g. all being Arc<RwLock<ObjcClassRef>> but with Arc replaced by something that uses native ObjC runtime counters.
Current approach
A bit of context here, that's what we currently have for ObjC bindings of Metal:
- a
SomeClassReftype with actual methods (immutable!) for properties and ObjC messages. It's a type-system thing in Rust that doesn't affect any ObjC reference counts. Type system guarantees that the object is alive for the lifetime of our XXXRef struct. - a
SomeClasstype that represent a strong reference to ObjC object. Cloning it is akin cloning anArc. One can borrow it and getSomeClassReftemporarily.
Analysis
The main problem for getting any sort of type assistance here is that SomeClass objects don't share anything with each other, so we don't know (even at run-time) if those point to the same underlying ObjC object...
So here comes the main idea and restriction: let's enforce a guarantee that there is a single Rust object backing a single ObjC object. On Rust side, there is no point to mess with ObjC reference counters. Instead, we can use the regular Arc and locking primitives. As long as ObjC runtime is concerned, we only have one object.
Unresolved Problems
Any methods that return new objects to use (newXXX) would just return SomeClass by value, and the client code would then be responsible for wrapping it into sharing structures, if needed. The destructor of SomeClass would then release the only ObjC count.
What's not clear, however, is what should be done about getters that return existing objects. e.g.
let obj1 = msg_send![parent.get_some_object];
let obj2 = msg_send![parent.get_some_object];
An important missing piece of this puzzle is to how we can uniquely track ObjC objects, and @jrmuizel suggested to put the locking internals into the associated data for fast lookups (by our wrappers around ObjC objects, e.g. struct Device {...}).
Having objects be uniquely owned by a SomeClass in rust would allow you to take the most advantage of rust's type system; I tried something a little like this with UIKit, but really struggled with ensuring that uniqueness. I haven't worked with Metal, maybe it's better suited? In UIKit, it seems like everything has a reference to everything... 😛
I like the idea of using associated types, that could make it a lot easier to verify that the uniqueness isn't being violated and could make this all much more doable.