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

Extern "Rust" types that are nonlocal #496

Open
2xsaiko opened this issue Nov 22, 2020 · 5 comments
Open

Extern "Rust" types that are nonlocal #496

2xsaiko opened this issue Nov 22, 2020 · 5 comments

Comments

@2xsaiko
Copy link

2xsaiko commented Nov 22, 2020

The following worked prior to 1.0:

type DataSourcePrivate = Rc<DataSource>;

#[cxx::bridge(namespace = "mcrtlib::ffi")]
mod types {
    pub struct DataSource {
        pub inner: Box<DataSourcePrivate>,
    }

    extern "Rust" {
        type DataSourcePrivate;

        // ...
    }
}

However, now I just get a

error[E0117]: only traits defined in the current crate can be implemented for arbitrary types
  --> library/src/ffi.rs:85:9
   |
85 |         type DataSourcePrivate;
   |         ^^^^^-----------------
   |         |    |
   |         |    `Rc` is not defined in the current crate
   |         impl doesn't use only types from inside the current crate
   |
   = note: define and implement a trait or new type instead

Is it possible to make this work again or should I wrap my Rc in a tuple struct?

@dtolnay
Copy link
Owner

dtolnay commented Nov 23, 2020

This is restricted to a local type for now because if multiple bridges reused the same type as an extern Rust type in the same namespace, they ended up colliding on symbols. I expect to relax the restriction again but it needs some design work and I wanted to be slightly more conservative for 1.0.

@2xsaiko
Copy link
Author

2xsaiko commented Nov 23, 2020

Ahh, fair enough, I figured it was something like that. Then I'll just wrap it in a tuple struct.

@alexeyr
Copy link

alexeyr commented Nov 23, 2020

Is there an approximate timeframe for relaxing it? I am only asking because if it's likely to work in 2-3 weeks I will keep adding bindings for new types/modules and wait to switch to 1.0+, otherwise it makes sense to switch immediately.

@dtolnay
Copy link
Owner

dtolnay commented Dec 1, 2020

I had a chance to think about this today and I think here's what we'll do:

  • If an extern "Rust" type does not appear in Box and does not appear in Vec and is not the receiver of any extern "C++" methods, then there are absolutely no restrictions. In practice this means Rust types to which C++ only obtains references &T / &mut T.

    #[cxx::bridge]
    mod ffi {
        extern "Rust" {
            type MyType;  // ok to be from different crate
            type Receiver;  // must be defined by current crate due to method
        }
        unsafe extern "C++" {
            fn f(x: &MyType, y: &mut MyType);
            fn g(self: &Receiver);
        }
    }
  • If an extern "Rust" type does have any of the above, by default we'll require that it is locally defined by the current crate. Additionally, if multiple FFI bridges inside the same crate refer to the same extern "Rust" type within the same C++ namespace, then by default only one of the bridges will be able to use Box of the type and only one of the bridges (not necessarily the same) will be able to use Vec of the type.

    #[cxx::bridge]
    mod ffi1 {
        extern "Rust" {
            type MyType;
            fn f() -> Box<MyType>;
        }
    }
    
    #[cxx::bridge]
    mod ffi2 {
        extern "Rust" {
            type MyType;
            fn g() -> Vec<MyType>;
        }
    }
  • If someone needs the same Rust type used as an extern "Rust" type in the same C++ namespace, in different bridges, and they need both bridges to use the type in Box or Vec, then instead of saying extern "Rust" { type MyType; } they will need to use an ordinary use statement in the bridge (as made possible by Improvements to namespace support #353): use crate::path::to::MyType;.

    This makes Box and Vec related codegen on the C++ side opt in rather than opt out as is the default for extern "Rust" types. Instead of getting Vec-related codegen just because your bridge used a Vec, you'd need to requested it as described in https://cxx.rs/extern-c++.html#explicit-shim-trait-impls, if some other bridge doesn't already provide that codegen elsewhere.

    #[cxx::bridge]
    mod ffi1 {
        extern "Rust" {
            type First;
            type Second;
    
            fn f() -> Box<First>;
            fn g() -> Vec<Second>;  // results in C++ receiving an instantiation of Vec<Second>
        }
    }
    
    #[cxx::bridge]
    mod ffi2 {
        use super::{First, Second};
    
        extern "Rust" {
            fn h() -> Vec<First>;
            fn i() -> Vec<Second>;
        }
    
        impl Vec<First> {}  // needed because C++ doesn't already know about Vec<First>, and `use` doesn't automatically create it
    }
  • Lastly, if we're dealing with types that are from a different crate, your explicit impl would be required to be an unsafe impl. It's on the programmer to guarantee that multiple crates aren't instantiating the same C++ Box or Vec for the same type. (It's technically undefined behavior. In practice nothing bad would actually happen since all duplicated symbols would be functionally identical, I think the worst is you'd get linker errors.)

    A completely reasonable situation for which the programmer can make this guarantee is something like:

    struct PrivateType {...}  // private to this crate
    
    type MyType = futures::channel::oneshot::Sender<PrivateType>;
    
    #[cxx::bridge]
    mod ffi {
        use super::MyType;
    
        extern "Rust" {
            fn f() -> Box<MyType>;
        }
    
        unsafe impl Box<MyType> {}  // no other crate could possibly contain this instantiation because they don't have PrivateType
    }

@futscdav
Copy link

futscdav commented Jul 27, 2023

What is the solution to this today? Seems like using use statement is no longer supported and when I simply use type X; in two different bridges, I get a error[E0119]: conflicting implementations of trait 'RustType' for type 'X'.

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

No branches or pull requests

4 participants