rust-lang / rust-lang/rust-bindgen

The API generated for objective-c categories is inconvenient and requires implementation knowledge

Open
#2,709 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
Rust
Stars
5.3k
Forks
829
Avg merge
1d 1h
Merged PRs (30d)
15

Description

Bindgen code from objective-c categories results in traits like CLASS_CATEGORY to be generated. This is not intuitive for a few reasons:

  • I didn't even realize that bindgen supported categories. I was allowlisting the classes I wanted and saw that methods were missing. I looked in the files and realized that those methods were in category blocks and just assumed that it was unsupported. Only later did I realize that I had to allowlist CLASS_CATEGORY (or more broadly CLASS_.*).
  • Category names (and contents!) are implementation details. They are used in the implementation to organize things but are not exposed in any way in normal objective-c APIs. Creating a separate rust API is an unfortunate mismatch for developers.
  • Because the categories are traits, you need to import them by name. I'd prefer not to use use my_crate_bindings::* to pull in all the category traits, so I need to know their names to import them.

This is a tricky problem to work around, but if there is some way to address some of these points it'd make the developer interaction with the generated code more similar to the interaction when using objective-c.

Input C/C++ Header
@interface Foo
-(void)method;
@end

@interface Foo (BarCategory)
-(void)categoryMethod;
@end
Bindgen Invocation
$ bindgen input.h --objc-extern-crate -- -x objective-c
Actual Results
/* automatically generated by rust-bindgen */

#![allow(
    dead_code,
    non_snake_case,
    non_camel_case_types,
    non_upper_case_globals
)]
#![cfg(target_os = "macos")]

#[macro_use]
extern crate objc;
#[allow(non_camel_case_types)]
pub type id = *mut objc::runtime::Object;
#[repr(transparent)]
#[derive(Clone, Copy)]
pub struct Foo(pub id);
impl std::ops::Deref for Foo {
    type Target = objc::runtime::Object;
    fn deref(&self) -> &Self::Target {
        unsafe { &*self.0 }
    }
}
unsafe impl objc::Message for Foo {}
impl Foo {
    pub fn alloc() -> Self {
        Self(unsafe { msg_send!(objc::class!(Foo), alloc) })
    }
}
impl IFoo for Foo {}
pub trait IFoo: Sized + std::ops::Deref {
    unsafe fn method(self)
    where
        <Self as std::ops::Deref>::Target: objc::Message + Sized,
    {
        msg_send!(self, method)
    }
}
impl Foo_BarCategory for Foo {}
pub trait Foo_BarCategory: Sized + std::ops::Deref {
    unsafe fn categoryMethod(self)
    where
        <Self as std::ops::Deref>::Target: objc::Message + Sized,
    {
        msg_send!(self, categoryMethod)
    }
}

And in code using the generated code:

use my_crate_bindings::{Foo, IFoo, Foo_BarCategory};

let f = Foo::alloc();
unsafe { f.categoryMethod() };
Expected Results

I'm not entirely sure the best approach, but some suggestions:

  • Maybe allowlist_item("Class") can include categories automatically? I figure this is somewhat unlikely given that the allowlist acts on the rust API.
  • Change all categories into impl Foo instead. This is how I expected bindgen to work, though it doesn't mesh well with inheritance (see #1779) and then the trait IFoo might seem weird. I don't think there's a strict need to keep them separate given that categories are invisible in the objective-c API and exist purely in syntax. Alternatively you could merge all included categories into the one trait IFoo (and then inheritance will work, at the cost of what I imagine would be a bit more juggling in the bindgen logic).
  • Put all category traits into a module (either as a whole for everything generated or for each class). For example, I would be much less hesitant to use my_crate_bindings::{Foo, FooCategories::*}. This avoids my needing to know the category names, though I still need to be aware of how bindgen implements categories.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with the bindgen invocation and Objective-C category example in this issue, then trace how allowlisting the class and its category traits affects the generated API. Done should be a decided API improvement that makes category methods discoverable and usable without requiring callers to know category trait names.

Written by the indexing model from the issue text.

Assessment

Tech stack
objective-c, rust
Domain
developer-experience, tooling
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.