-
Notifications
You must be signed in to change notification settings - Fork 51
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
declare_class!
: Safer initializers
#438
Comments
Interestingly, Objective-C recommends setting custom instance variables after calling the superclass' initializer, while Swift requires you to set them before (as part of their nice two-phase initialization scheme). I can think of ways to express either in Rust: // Types in this example put after `let` statements for clarity; they would be inferred in real life.
//
// Note also the `?` after the `msg_send_id`, which is possible since the initializer always (?) returns an Option
declare_class!(
struct MyClass {
ivar1: IvarEncode<i32>,
ivar2: IvarDrop<Id<NSString>>,
}
// Now also generates:
// struct MyClassIvars {
// ivar1: i32,
// ivar2: Id<NSString>,
// }
//
// struct MyClassPartialInit;
unsafe impl ClassType for MyClass {
type Super = NSObject;
type Mutability = InteriorMutable;
const NAME: &'static str ="MyClass";
}
unsafe impl MyClass {
// Objective-C style
#[method_id(init:withArg:)]
fn init(this: Allocated<Self>, arg: i32) -> Option<Id<Self>> {
let this: MyClassPartialInit = unsafe { msg_send_id![super(this), init] }?;
let this: Id<Self> = this.set(MyClassIvars {
ivar1: arg,
ivar2: NSString::new(),
});
// Calling methods on `this` is... Maybe allowed here?
// Such methods may be overwritten in subclasses?
Some(this)
}
// Swift style
#[method_id(init:withArg:)]
fn init(this: Allocated<Self>, arg: i32) -> Option<Id<Self>> {
let this: MyClassPartialInit = this.set(MyClassIvars {
ivar1: arg,
ivar2: NSString::new(),
});
let this: Id<Self> = unsafe { msg_send_id![super(this), init] }?;
// Calling methods on `this` is allowed here
// Such methods may be overwritten in subclasses
Some(this)
}
}
); Unsure which approach would be best. Note that in any case, by introducing a few helper types, we avoid the need to have a compiler to do the complex "only access instance variables once initialized" logic that Swift does. |
Would also be nice to allow calling other initalizers, e.g. delegate to a designated initializer: declare_class!(
// ...
unsafe impl MyClass {
fn designated(self: Allocated<Self>) -> Option<Id<Self>> { ... }
fn convenience(self: Allocated<Self>) -> Option<Id<Self>> {
printing!("convenience initializer");
self.designated()
}
}
); But since we also want to call |
Partial initialization could be perhaps done with a This would require a new (though possibly internal) trait for declared classes, so that the And also how to set it actually, since it needs to access the ivar offset. |
Safety-wise, we must require the users that declare ivars to override all designated initializers |
The benefit of Swift's initialization scheme is that after the super initializer has been called, it is always safe to call methods on the object, no matter if those have been overwritten in a subclass, and now potentially refers to the subclass' instance variables. The benefit of Objective-C's initialization scheme is that classes that return something else from a super initializer will still work as expected. But perhaps this is only a problem when calling initializers outside of the implementation? E.g. |
If both the class and the ivars implement |
I think moving forward I'll be using Swift's initialization scheme, even if it may end up being slightly less efficient in certain cases. I'd still like to create some test cases for Objective-C classes that override |
Okay, so going from As an optimization, I guess we could omit setting the drop flag when the ivars are initialized, and just always only set it when the entire class is? This would leak in the case of unwinds in the super initializer, but perhaps that's fine? I'm wondering if we could somehow store a drop flag on the stack instead, perhaps inside the |
We may need functionality to unsafely access the instance variables, and convert the I'm currently looking at EDIT: |
Turns out, they don't, and accesses to the object afterwards fails horribly, so I don't think we'll try to do anything different here (yet). |
Use something similar to Swift's rules for initializers, which we may be able to with a combination of types and a
proc-macro
, to verify that initializers only access the things that they are allowed to.Should also somehow make ivars safe to declare, by statically ensuring that they are initialized - i.e. this issue ties in heavily with #414).
See also the Objective-C docs on initialization and a bit more about designated initializers.
The text was updated successfully, but these errors were encountered: