objc2 icon indicating copy to clipboard operation
objc2 copied to clipboard

Figure out naming scheme in framework crates

Open madsmtm opened this issue 3 years ago • 12 comments

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 }
  1. 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 }
  1. 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 avatar Nov 04 '22 00:11 madsmtm

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

madsmtm avatar Nov 04 '22 00:11 madsmtm

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 avatar Nov 04 '22 00:11 madsmtm

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

madsmtm avatar Nov 05 '22 00:11 madsmtm

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?

martial-plains avatar Nov 11 '22 17:11 martial-plains

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()).

madsmtm avatar Nov 11 '22 22:11 madsmtm

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

martial-plains avatar Nov 11 '22 22:11 martial-plains

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

  • Enum: mod ns_accessibility { struct AnnotationPosition(NSInteger) }
  • Struct: struct NSDirectionalEdgeInsets { fields* }
  • Typedef: mod ns_bitmap_image_rep { struct PropertyKey(NSString) }

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 avatar Jan 06 '23 15:01 madsmtm

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 avatar May 26 '23 08:05 madsmtm

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

madsmtm avatar Jun 20 '23 21:06 madsmtm

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:

  • Framework: objc2-foundation
  • Class: struct NSObject
  • Protocol:
    • struct NSCopying
    • struct NSObjectProtocol (renamed to not conflict with NSObject)
  • Category:
    • In same framework: impl Foo {} (inherent implementation)
    • If category on NSObject: trait NSNibAwaking (emit category name as-is)
    • Manually special-case categories that are defined multiple times: trait NSAffineTransformAdditions (AppKitAdditions)
    • Categories without a name: Disallow.
    • Otherwise: trait NSAccessibility
  • Method:
    • #[method(someMethod:withExtraParam:)] fn someMethod_withExtraParam(arg: i32, extra: u32)
    • Strip names according to mostly the same rules as Swift
      • Returning error: #[method(aMethod:error:)] fn aMethod(arg: i32) -> Result<(), Id<NSError>>
      • Completion handlers after #279, #[method(aMethodWithCompletionHandler:)] async fn aMethod()
    • Add _class when class methods conflict with instance methods.
  • Enum: struct NSAccessibilityAnnotationPosition(NSInteger)
  • Enum constant: impl NSAccessibilityAnnotationPosition { const FullRange: Self = ... } (the hope is that they will one day be closer to real enums)
  • Struct: struct NSDirectionalEdgeInsets { fields* }
  • Constant:
    • On NS_TYPED_EXTENSIBLE_ENUM, when possible: impl NSModalResponse { const Stop: NSModalResponse = ... }
    • Otherwise: const NSModalResponseStop: NSModalResponse = ...
  • Typedef: type NSBitmapImageRepPropertyKey = NSString
  • Static: extern "C" { static NSImageCurrentFrameDuration: NSBitmapImageRepPropertyKey }
    • If, in the future, Rust gives us safe external statics and/or associated statics, then we can consider using those.
    • But creating a helper function is not an option, since that will loose the static-ness of the value.
  • Function: extern "C" { fn NSBitsPerPixelFromDepth(depth: NSWindowDepth) -> NSInteger }

madsmtm avatar Jun 02 '24 22:06 madsmtm

I've implemented the better enum case prefix stripping in https://github.com/madsmtm/objc2/commit/a305d6f7779675524b65dc4e89866cb5c44f134f.

madsmtm avatar Jan 05 '25 01:01 madsmtm

Have opened https://github.com/madsmtm/objc2/issues/736 for CoreFoundation-like functions.

madsmtm avatar Apr 17 '25 12:04 madsmtm