madsmtm / objc2

Bindings to Apple's frameworks in Rust
https://docs.rs/objc2/
MIT License
378 stars 43 forks source link

Figure out naming scheme in `icrate` #284

Open madsmtm opened 2 years ago

madsmtm commented 2 years ago

icrate (or whatever it'll end up being named) is nearing an initial version, see https://github.com/madsmtm/objc2/pull/264.

After that, we need to figure out how we should handle name translation for enums, method names, and so on. There are a few options:

  1. Use Objective-C naming scheme:
    • Framework: Foundation
    • Class: struct NSObject
    • Protocol: struct NSCopying
    • Category: trait NSAccessibility; impl NSAccessibility for NSObject; (or perhaps NSObject_NSAccessibility?)
    • Method: fn someMethod(arg: i32), fn someMethod_withExtraParam(arg: i32, extra: u32)
    • Method returning error: fn aMethod_error(arg: i32) -> Result<(), Id<NSError>>
    • Method with callback: async fn aMethodWithCompletionHandler(f: &Block<(), ()>), see also #279
    • Enum: struct NSAccessibilityAnnotationPosition(NSInteger)
    • Enum constant: const NSAccessibilityAnnotationPositionFullRange: NSAccessibilityAnnotationPosition = ...
    • Struct: struct NSDirectionalEdgeInsets { fields* }
    • Constant: const NSModalResponseStop: NSModalResponse = 1000
    • Typedef: struct NSBitmapImageRepPropertyKey(NSString)
    • Static: extern "C" { static NSImageCurrentFrameDuration: NSBitmapImageRepPropertyKey }
    • Function: extern "C" { fn NSBitsPerPixelFromDepth(depth: NSWindowDepth) -> NSInteger }
  2. Use a scheme similar to Swift's
    • Framework: Foundation
    • Class: Same, sometimes renamed?
    • Protocol: Same
    • Category: Same
    • Method: fn some(method: i32), fn some_with(method: i32, extraParam: u32) (name is significantly stripped)
    • Method returning error: fn a(arg: i32) -> Result<(), Id<NSError>> (error postfix is removed)
    • Method with callback: async fn a() (WithCompletionHandler stripped)
    • Enum: struct NSAccessibilityAnnotationPosition(NSInteger), Swift usually creates a nested items like NSAccessibility::AnnotationPosition, but that is not possible in Rust
    • Enum constant: impl NSAccessibilityAnnotationPosition { const fullRange: Self = ... }
    • Struct: struct NSDirectionalEdgeInsets { fields* }
    • Constant: impl NSModalResponse { const stop: Self = 1000 }
    • Typedef: struct NSBitmapImageRepPropertyKey(NSString)
    • Static: impl NSBitmapImageRepPropertyKey { fn currentFrameDuration() -> &'static Self }, associated statics are not possible in Rust yet so we must create a new function that can return it (also if we want to make it safe)
    • Function: impl NSWindowDepth { fn bitsPerPixel() -> NSInteger }
  3. Come up with a custom, "Rusty" scheme based on option 2
    • Framework: foundation
    • Class: Same
    • Protocol: Same
    • Category: Same
    • Method: fn some_long_method_name(method: i32), method name is made always lowercase (note: difficult now to tell which selector it corresponds to)
    • Method returning error: Same
    • Method with callback: Same
    • Enum: Same
    • Enum constant: impl NSAccessibilityAnnotationPosition { const FULL_RANGE: Self = ... }, constant is uppercase
    • Struct: Same
    • Constant: impl NSModalResponse { const STOP: Self = 1000 }, constant is uppercase
    • Typedef: Same
    • Static: impl NSBitmapImageRepPropertyKey { fn current_frame_duration() -> &'static Self }, function is made lowercase, in the future if we get associated statics then the name would change to be uppercase
    • Function: impl NSWindowDepth { fn bits_per_pixel() -> NSInteger }, function is made lowercase

I like option 1 since it'll mean that things are more "searchable"/"grepable". This point also somewhat goes for option 2, though a bit less so, since Swift is newer. The downside to both of these is of course that the names don't really match Rust's naming conventions.

Implementation wise: Option 1 is already implemented, so that's easy. Option 2 has seen a lot of work already by Apple/Swift developers, and they've even created the .apinotes system to help with edge-cases. Option 3 would be quite easy on top of 2, but would require a bit extra work if we want to tweak names further afterwards.

madsmtm commented 2 years ago

Also discussed previously in the metal crate: https://github.com/gfx-rs/metal-rs/issues/190

madsmtm commented 2 years ago

I think we should stick with Objective-C naming at the very least until we can autogenerate documentation based on https://developer.apple.com/, since it's just much more discoverable that way - but I can be persuaded otherwise!

madsmtm commented 2 years ago

The windows crate is also a good place to look to for how this is done elsewhere.

martial-plains commented 2 years ago

So far going through the Apple's documentation I've only came across one instance of where an instance method and type method has had the same name. The NSObject protocol has an instance method called class while the NSObject interface has the type method class.

What do you think about having suffixes for methods like ip_ for instance property and tp_ for type property?

madsmtm commented 2 years ago

instance method and type method

I'm a bit unsure about your terminology here; do you mean "instance method" and "protocol method"?


In general my opinion is that adding prefixes or suffixes is unnecessary - to the user, it doesn't really matter if something is a method or a property, or defined on a class or on a protocol, the way they're used is exactly the same.

Take for example +[NSThread isMainThread] and +[NSThread isMultiThreaded]: Here, the former is a property and the latter is a method, but I'd have no way of guessing which was which.

Similarly, I don't think we need to differentiate between class and instance methods, since their usage is different enough from Rust that it is already quite clear: NSThread::new() calls a class method, while obj.name() calls an instance method.

Of course, duplicate names do occur in such a way that we must change (e.g. NSThread has both +[NSThread isMainThread] and -[NSThread isMainThread]), but I think they are rare enough that we can do it on a case-by-case basis (so e.g. have NSThread::class_is_main_thread() and thread.is_main_thread()).

martial-plains commented 2 years ago

I understand where your coming from and you made a good point :)

madsmtm commented 1 year ago

Another possibility is to namespace enums, structs and typedefs behind modules (when it makes sense):

I considered namespacing the same was as Swift, e.g. mod NSBitmapImageRep { struct PropertyKey(NSString); }, but we can't do that since it'd conflict with the NSBitmapImageRep class (again, no way to make associated types on structs).

Though perhaps we could then do mod NSBitmapImageRep { struct Class(...); } for classes instead?

madsmtm commented 1 year ago

cidre uses the following scheme:

mod ns {
    struct Array<T>; // NSArray
    struct String; // NSString
    struct Number; // NSNumber
}

// Usage
use cidre::ns;

let array: ns::Array<ns::String> = ns::Array::new();

Which works pretty good for Foundation, but I'm not sure how well it extends to e.g. AppKit?

madsmtm commented 1 year ago

I've pushed some of the objc2::runtime types towards a more Swifty naming scheme in https://github.com/madsmtm/objc2/pull/463.

madsmtm commented 5 months ago

I've pondered this for a while now, but at this point I feel like sticking close to Objective-C for method and function names is the correct approach, even though it's less "Rusty".

That said, we still need to do some more work on the enum case prefix stripping, eliminating needless words in methods and on category naming.

Currently, I think the naming scheme is going to be: