cxx icon indicating copy to clipboard operation
cxx copied to clipboard

Extern "Rust" types that are nonlocal

Open dblsaiko opened this issue 5 years ago • 5 comments

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?

dblsaiko avatar Nov 22 '20 22:11 dblsaiko

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.

dtolnay avatar Nov 23 '20 01:11 dtolnay

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

dblsaiko avatar Nov 23 '20 11:11 dblsaiko

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.

alexeyr avatar Nov 23 '20 15:11 alexeyr

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 #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
    }
    

dtolnay avatar Dec 01 '20 06:12 dtolnay

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'.

futscdav avatar Jul 27 '23 19:07 futscdav