Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add rc::Unknown and rc::MaybeOwnership #87

Closed
madsmtm opened this issue Dec 10, 2021 · 11 comments
Closed

Add rc::Unknown and rc::MaybeOwnership #87

madsmtm opened this issue Dec 10, 2021 · 11 comments
Labels
enhancement New feature or request help wanted Extra attention is needed

Comments

@madsmtm
Copy link
Owner

madsmtm commented Dec 10, 2021

Rough sketch:

When generating bindings to Objective-C classes automatically (e.g. with bindgen, see #85), it would be nice to return a reference counting pointer (e.g. an Id). Sadly, however, it is not possible to know the ownership of that without inspection of the entire class; even handing out immutable references can easily trigger undefined behaviour (e.g. if there was a Id<T, Owned> somewhere else in the program).

Therefore it would be beneficial to add a reference-counting pointer that does not guarantee any form of ownership, like the StrongPtr removed in e08e87d. See also upstream SSheldon/rust-objc#24.

This can probably be accomplished by adding a rc::Unknown, similar to rc::Owned and rc::Shared.
On initial inspection, you would just implement rc::Ownership for rc::Unknown, but that makes all high-level code unable to deal with the "normal" case where you want to know the ownership, so we should consider adding rc::MaybeOwnership as well to deal with this case.

Example code changes:

pub trait MaybeOwnership: private::Sealed + 'static {}
pub trait Ownership: MaybeOwnership {}

impl MaybeOwnership for Unknown {}
impl MaybeOwnership for Owned {}
impl MaybeOwnership for Shared {}
impl Ownership for Owned {}
impl Ownership for Shared {}


pub struct Id<T, O: MaybeOwnership> { ... }

impl<T: Message> Id<T, Unknown> {
    pub unsafe fn into_shared(self) -> Id<T, Shared> { ... }
    pub unsafe fn into_owned(self) -> Id<T, Owned> { ... }
}

// Still only `Owned` and `Shared` are `Deref`!
impl<T, O: Ownership> Deref for Id<T, O> { ... }

// `Unknown` can also be sent messages (since Objective-C doesn't care about ownership)
unsafe impl<T: Message, O: MaybeOwnership> MessageReceiver for Id<T, O> { ... }
@madsmtm madsmtm added enhancement New feature or request help wanted Extra attention is needed labels Dec 10, 2021
@madsmtm
Copy link
Owner Author

madsmtm commented Dec 10, 2021

Not that it matters much, but this could be accomplished without breaking changes.

@madsmtm
Copy link
Owner Author

madsmtm commented Mar 1, 2022

What about naming? rc::MaybeOwnership is a bit weird, though I don't really have any ideas for a better name. rc::Unknown is probably fine.

And which requirements are imposed by Id<T, Unknown>? Of course the pointer must point to a valid object (non-null, aligned, dereferenceable, points to an actual allocated object, so on), but should we also require:

  1. The type T to be of the correct type (e.g. T = Object would always be correct, but T = NSData would not be for an object that is actually NSString)?
    • If we don't, <Id<T, Unknown>>::cast::<U> would be safe (Id<T, Unknown> would effectively be the old StrongPtr).
    • If we do, functions could work with Id<T, Unknown> (e.g. take parameter &Id<MyObject, O: MaybeOwnership>) and have that just work, and Id::into_owned / Id::into_shared would be easier to work with.
  2. That the object is initialized?

@madsmtm

This comment was marked as outdated.

@madsmtm madsmtm added this to the objc2 v0.3 milestone Apr 2, 2022
@madsmtm
Copy link
Owner Author

madsmtm commented Apr 3, 2022

rc::IdState could be another name for rc::MaybeOwnership? Especially if we add more a thing like Id<T, Allocated> (correct type and allocated, initialization missing, ownership unknown).

@madsmtm
Copy link
Owner Author

madsmtm commented Apr 3, 2022

Thinking about it again, there are three orthogonal pieces of information that we might know, and want to encode this knowledge:

  1. Initialized
  2. Type
  3. Ownership

With that, rc::Unknown probably does make sense, along with Allocated<T> as implemented in #172.

Initialized Type Ownership Result
No Unknown Unknown Id<Allocated<Object>, Unknown>
No Unknown Known Id<Allocated<Object>, O>
No Known Unknown Id<Allocated<T>, Unknown>
No Known Known Id<Allocated<T>, O>
Yes Unknown Unknown Id<Object, Unknown>
Yes Unknown Known Id<Object, O>
Yes Known Unknown Id<T, Unknown>
Yes Known Known Id<T, O>

Only remaining question is: How important it actually is to know you don't know the ownership, vs. just using Shared.

@madsmtm
Copy link
Owner Author

madsmtm commented May 25, 2022

Another name for MaybeOwnership: PartialOwnership?

@madsmtm
Copy link
Owner Author

madsmtm commented Jun 7, 2022

Reconsidering: MaybeUninit could probably work instead of us having to define a new type Allocated (which would be nice, less concepts to teach the user)!

Reasoning: The data is already guaranteed to be allocated when you hold a MaybeUninit or a pointer to such (since it has the same size), and while an object may be partially initialized after calling alloc (e.g. the parts of it that handle retain/release semantics), this is basically still just the same as MaybeUninit::zeroed:

let obj: Id<Allocated<T>, Owned> = msg_send_id![cls, alloc];
// Is effectively the same as
let item: Box<MaybeUninit<T>> = Box::new_zeroed();

But then again, the data is not truly maybe-uninitialized, parts of it is guaranteed to be valid (e.g. valid isa pointer and so on), which is not the case for MaybeUninit (and hence it could never be ?Sized).

Also, if we want to have special methods, a new struct is better.

@madsmtm
Copy link
Owner Author

madsmtm commented Jun 24, 2022

Still uncertain whether Id<T, Unknown> is really that important...?

Since T should contain UnsafeCell in any case (we don't know what it's doing on the Objective-C side), Id<T, Shared> may actually be just as safe? Of course, it still wouldn't be thread safe, but e.g. Object is already !Send + !Sync, so that shouldn't be an issue.

One thing though: Object::ivar(&self) -> &T is dangerous, because the instance variable may be modified, e.g. as in the following example:

let obj: Id<Object, Shared> = ...;

// Get immutable reference to ivar
let ivar: &i32 = obj.ivar("ivar");

// Modify `ivar` through shared reference to `obj`
let _: () = msg_send![obj, setIvar: *ivar];

// UB
println!("{}", *ivar);

So if we want to "bless" Id<T, Shared> as "always safe" (depending on T ofc.), then we have to fix this!

EDIT: Fixed by better safety docs, see #182

This was referenced Jun 25, 2022
@madsmtm madsmtm removed this from the objc2 v0.3 milestone Jul 6, 2022
@madsmtm
Copy link
Owner Author

madsmtm commented Aug 2, 2022

I don't think Id<T, Unknown> is that interesting, since &NSObject is basically NonNull<NSObject> because of UnsafeCell, but awaiting resolution on #235 before deciding.

EDIT: That issue will have very significant implications on the entire API if we can't fix it otherwise, but whether Id<T, Unknown> is useful can always be determined later.

@madsmtm
Copy link
Owner Author

madsmtm commented Sep 23, 2022

I've removed ownership from Allocated in #272, since it isn't actually interesting to know. See the PR for details.

@madsmtm
Copy link
Owner Author

madsmtm commented Nov 3, 2022

In #264 I'll probably be using Id<T, Shared> and &T by default everywhere, since T contains UnsafeCell and all methods are unsafe anyhow.

At this point I really doubt Id<T, Unknown> will be of any use over just Id<T, Shared> if you just ensure that methods on T are written properly, so I'll close this for now.

@madsmtm madsmtm closed this as completed Nov 3, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request help wanted Extra attention is needed
Projects
None yet
Development

Successfully merging a pull request may close this issue.

1 participant