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

Split shared structs across multiple modules/namespaces #1301

Closed
phil-opp opened this issue Jan 4, 2024 · 5 comments
Closed

Split shared structs across multiple modules/namespaces #1301

phil-opp opened this issue Jan 4, 2024 · 5 comments

Comments

@phil-opp
Copy link
Contributor

phil-opp commented Jan 4, 2024

Hi, I want to create multiple shared structs in different namespaces. Some of the structs have fields that reference other structs. For example:

mod foo {
    pub struct Foo {
        foo: i32,
    }
}

mod bar {
    pub struct Bar {
        bar: super::foo::Foo,
    }
}

Is it possible to implement this using cxx? I tried the following:

  • Separate #[cxx::bridge] attributes for the two modules with different namespace arguments
    • This requires referencing the foo module from bar. However:
      • use statements are not allowed
      • non-local type names such as super::foo::Foo are not allowed either
      • reusing binding types apparently only works for extern "C++" types, not shared types
  • Single #[cxx::bridge] module with submodules
    • Put both foo and bar into a common module, e.g. named ffi
    • Add a #[cxx::bridge] attribute to the ffi module
    • This approach does not work either because submodules inside bridge modules are not supported (see also Submodules inside ffi definition #928)

Is there perhaps another way to achieve this? Thanks!

@dtolnay
Copy link
Owner

dtolnay commented Jan 5, 2024

I am not entirely sure what you mean by different namespaces, but if it's just the C++ namespace that needs to be different, then like this:

#[cxx::bridge]
mod ffi {
    #[namespace = "foo"]
    struct Foo {
        i: i32,
    }

    #[namespace = "bar"]
    struct Bar {
        foo: Foo,
    }
}

If you additionally want them in different Rust modules, you can do:

pub mod foo {
    pub use crate::ffi::foo::Foo;
}

pub mod bar {
    pub use crate::ffi::bar::Bar;
}

or, like this:

#[cxx::bridge]
mod foo {
    #[namespace = "foo"]
    struct Foo {
        i: i32,
    }
}

// in a different file
#[cxx::bridge]
mod bar {
    #[namespace = "foo"]
    extern "C++" {
        type Foo = crate::foo::Foo;
    }

    #[namespace = "bar"]
    struct Bar {
        foo: Foo,
    }
}

@phil-opp
Copy link
Contributor Author

phil-opp commented Jan 5, 2024

Thanks a lot for the quick reply! I need different Rust modules too because some of the structs might have identical names.

If you additionally want them in different Rust modules, you can do:

pub mod foo {
    pub use crate::ffi::foo::Foo;
}

pub mod bar {
    pub use crate::ffi::bar::Bar;
}

This would require submodule declarations within the ffi module, wouldn't it? E.g. something like this:

#[cxx::bridge]
mod ffi {
    mod foo {
        pub struct Foo {
            foo: i32,
        }
    }

    mod bar {
        pub struct Bar {
            bar: super::foo::Foo,
        }
    }
}

Unfortunately, this does result in "unsupported item" errors for both the mod foo and mod bar declaration.


or, like this:

#[cxx::bridge]
mod foo {
    #[namespace = "foo"]
    struct Foo {
        i: i32,
    }
}

// in a different file
#[cxx::bridge]
mod bar {
    #[namespace = "foo"]
    extern "C++" {
        type Foo = crate::foo::Foo;
    }

    #[namespace = "bar"]
    struct Bar {
        foo: Foo,
    }
}

This works, thanks a lot! I didn't know that #[namespace] attributes are supported on extern "C++" blocks too.

@phil-opp phil-opp closed this as completed Jan 5, 2024
@dtolnay
Copy link
Owner

dtolnay commented Jan 5, 2024

This would require submodule declarations within the ffi module, wouldn't it?

I meant that you would put this outside of mod ffi to organize the public Rust API. mod ffi would then be private.

@dtolnay
Copy link
Owner

dtolnay commented Jan 5, 2024

some of the structs might have identical names.

I am sure you figured this out but for the benefit of anyone else reading: this means you would need to use cxx_name to deconflict them.

#[cxx::bridge]
mod bar {
    #[namespace = "foo"]
    extern "C++" {
        #[cxx_name = "Foo"]
        type FooFoo = crate::foo::Foo;
    }

    #[namespace = "bar"]
    struct Foo {
        foo: FooFoo,
    }
}

@phil-opp
Copy link
Contributor Author

phil-opp commented Jan 5, 2024

Thanks!

I ended up with with the following, maybe this is useful for someone else too:

#[cxx::bridge]
mod ffi {
    #[namespace = "namespace"]
    #[cxx_name = "Name"]
    struct namespace__Name {
        field: other_namespace__OtherName,
    }
    #[namespace = "other_namespace"]
    #[cxx_name = "OtherName"]
    struct other_namespace__OtherName {
        field: i32,
    }
}

pub mod namespace {
    pub use crate::ffi::namespace__Name as Name;
}
pub mod other_namespace {
    pub use crate::ffi::other_namespace__OtherName as OtherName;
}

So I prefix every struct name in the ffi module with its namespace name to avoid name conflicts. For the C++ API, i use the namespace and cxx_name attributes to recreate the namespace and original name. For the Rust API, I use re-exports as suggested above.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants