Repository navigation
Description
I was once convinced that we could restrict mutation to &mut self methods, but after having battled with AppKit and seen how Swift does it, I'm beginning to doubt that this is the best way.
NSString is immutable while NSMutableString is, naturally, mutable. Since our current implementation only allows mutation of NSMutableString through &mut self, it is safe to make both of these Send + Sync; all is well and Rusty.
Now, remember that &NSMutableString -> &NSString and Id<NSMutableString, O> -> Id<NSString, O> is safe - so if we were to make mutation of NSMutableString possible through &self, not only would NSMutableString be !Sync, but so would NSString! (They could still be Send, that would simply allow moving them via. Id<T, Owned> to a different thread - quite similar to std::cell::Cell)
That's the primary downside: Our objects can no longer be shared across threads. Downsides that usually apply to Rust code (aliasing optimizations, ...) are void in our case, since NSString is already UnsafeCell.
On the other hand, if we were to remove mutability we could:
- Make
NSArray<T>,NSDictionary<K, V>and such collection types simpler - Since most frameworks use interior mutability and/or are not thread safe anyhow, the usage of the Foundation framework would match the usage of these.
- It would be possible for users to inherit
NSString, without them having to ensure that their object wasSync Id::retaincould be made safe(r?)- automatic bindings would be simpler
- Using
NSMutableStringinside adeclare_class!that is not meant to be thread-safe anyhow is easier (e.g. means we won't have to use&mutinwinit)
So... Yeah, will think about this a bit, but I think we may have to sacrifice being able to use Objective-C classes across threads (exactly the same compromise Swift does, their String is Sendable but NSString is not).
Activity
madsmtm commentedon Sep 12, 2022
rust-windowing/winit#2457 highlighted an issue: We'd have to return
Id<WinitView, Owned>if we wanted to makeaccepts_first_mousemutable after the fact - but you can very rarely have an owned view (because other code may hold references to it, unknown to you), so this would be a footgun.madsmtm commentedon Sep 12, 2022
Instead, making it
Id<WinitView, Shared>and then containaccepts_first_mouse: Cell<bool>would be more correct.Note that this is not currently possible because the layout of
Cellis not guaranteed, will request this fromstdat some point.madsmtm commentedon Oct 5, 2022
Even ignoring thread safety, a problem arises with methods that return references to internal data. For example, the following fails at compile-time because
set_bytesrequires&mut self, but if we were to change things to no longer require mutable pointers, it would cause UB:Similarly for
-[NSString UTF8String], other methods annotated withNS_RETURNS_INNER_POINTER, and probably also the collection types - perhaps also when iterating?Swift "solves" this by just returning
UnsafeRawPointer. And when iterating overArray, they make a local copy; when iterating overNSMutableArray, they don't mark theaddmethod as mutating, so memory safety is quite easy to violate:So yeah, I'm quite conflicted as to how we should do mutability; perhaps things will be clearer once I've done more of #264, and get to see if there are parts other than Foundation that would benefit from mutability.
madsmtm commentedon Nov 3, 2022
Another solution: Just keep every mutating method
unsafe. Or maybe add two variants, onefn push(&mut self, item: &T)and oneunsafe fn push_unchecked(&self, item: &T)?madsmtm commentedon Nov 10, 2022
Would maybe make sense to take the
Ownershipparameter fromNSCopyingand move it toClassType?notgull commentedon Dec 17, 2022
Truth be told, I'm fine with having a standard class that's
!Send/!Syncand then having an escape-hatchunsafeadaptor that isSend/Sync. In the wide variety of cases, these types end up being thread-unsafe anyhow.madsmtm commentedon Dec 21, 2022
Yeah if it was only thread safety that was the issue, then I would agree, but as noted in #265 (comment), that still won't allow us to make mutating methods like
addObject:safemadsmtm commentedon Jan 18, 2023
An update:
Cellwill soon beEncode, and as such usable in instance variables - which should allow usingNSMutableStringin non-Syncclasses.More importantly, Swift recently implemented a better concept of sendability, see
NS_SWIFT_SENDABLEandNS_SWIFT_UI_ACTORinFoundation/NSObjCRuntime.h.Using these attributes, we can automatically mark
NSError,NSDate,NSFileHandleand so onSend + Sync(not sure aboutNSLockand subclasses, seems like that shouldn't be either? Or at least notSend).But, even more importantly, we can automatically mark
NSView,NSWindow, ... as "needing aMainThreadMarkerto be created". That is, for every function/method, do an algorithm like:(Note here the difference between
includes_ns_swift_ui_actorandis_ns_swift_ui_actor;includes_returns true forNSArray<NSView>orOption<NSView>, whileis_doesn't).So e.g.
NSView::new()would becomeNSView::new(_mtm: MainThreadMarker), whileNSWindow::view(&self)would stay as-is.madsmtm commentedon Jan 23, 2023
A bit unsure where the
MainThreadMarkerargument is most convenient to put? As the first argument (afterself, if present), or as the last one?E.g. the difference between these two:
(Oh, fabulous contexts and capabilities, how you could relieve me of all my troubles with but the smallest of effort)!
EDIT: Parts of this is redundant since
mtm.alloc(), the rest is moved to #359 (comment)madsmtm commentedon Apr 21, 2023
The resolution after #419 is that we'll end up making most classes use interior mutability, but keep mutability in certain core classes like
NSMutableStringandNSMutableArray<T>, since that will allow us to do safe interior pointers and safe iteration.Relating to the issues noted in the top comment, I believe most of them are addressed:
Tracked in #316, also completed by the linked PR.
Interior mutability is the default setting for
icrate, we only override it for a few select cases.Not sure one could do that anyhow, I think Objective-C itself may assume that
NSStringis thread-safe? In any case, not a big downside.Tracked in #399,
obj.retain()is safe and available for interior-mutable types.Since the type ownership is no longer on the
Id, this problem is mitigated.You can probably do
Cell<Id<NSMutableString>>or similar to get that functionality? Or maybeRefCell<Id<NSMutableString>>. In any case, one can always make a custom helper class likeNSMutableStringInteriorMutableinstead.So the only remaining things in this is the main thread safety stuff that is not really related to this issue, I'll track that in in #359 instead.