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

Stable Layout and Implementation #11

Open
carbotaniuman opened this issue Oct 31, 2020 · 5 comments
Open

Stable Layout and Implementation #11

carbotaniuman opened this issue Oct 31, 2020 · 5 comments

Comments

@carbotaniuman
Copy link

a stable ABI involves a lot more than just fixing #[repr(Rust)] in place and deciding on a calling convention, but also making sure every struct is ABI compatible and that functions and traits don't change.

This work is is the kind of work that I think could be done mostly as a library, with a few compiler changes of course. There's a fundamental trade-off between flexibility and stability here, and Rust chose to lean towards flexibility with regards to ABI.

Functions - inlining affords you no flexibility in changing any externally observable behavior of a function (including bug fixes). Swift has precedent here with @inlineable, and we may have to treat everything as #[inline(never)] unless we have the appropriate #[inline].

Traits can be thought of as basically a bunch of functions, but with the caveat that associated consts can never change (this is also a problem in Java, C, and C#).

In the same vein, constants and statics can never change their externally visible value, but I assume that this won't be a big deal in practice.

Structs are likely going to be the biggest problem in practice, because the prevalence of privacy and static linking has lead to a behavior of libraries changing their type representations to evolve. Fixing the representation of types has been partially addressed by the Safe Transmute RFC, but it doesn't really address our needs. Some sort of pimpl idiom/extern type is going to be neccessary I might imagine.

A final note about std and libraries is that we'll either have to pin their versions or switch to libraries that promise a stable ABI themselves (if any of their types are used in our ABI). Pinning std is easiest but runs the risk of incompatible desugarings (not to mention lack of cargo-std support). Pinning libraries might lead to annoying issues in practice with DLL hell. I would hate to have shim libraries that pimpl everything, but they may be the price for dynamic linking...

@BenLewis-Seequent
Copy link
Contributor

a stable ABI involves a lot more than just fixing #[repr(Rust)] in place and deciding on a calling convention, but also making sure every struct is ABI compatible and that functions and traits don't change.

Agree, even if Rust allows providing a stable ABI it doesn't automatically mean that libraries have a stable ABI, a stable ABI at that point is more of a guarantee from the library author that the library will maintain ABI compatibility.

In the same vein, constants and statics can never change their externally visible value, but I assume that this won't be a big deal in practice.

Statics will be able to change, as they aren't inlined like constants are and their initialiser will be part of the dynamic library.

Structs are likely going to be the biggest problem in practice, because the prevalence of privacy and static linking has lead to a behavior of libraries changing their type representations to evolve. Fixing the representation of types has been partially addressed by the Safe Transmute RFC, but it doesn't really address our needs. Some sort of pimpl idiom/extern type is going to be neccessary I might imagine.

A pattern we might see is to have an additional constructor function that returns the value boxed, which will allow using the struct's API across a stable ABI boundary, though at the cost of an allocation.

Example:

pub struct Foo {
    // implementation details which may changes over time
}

impl Foo {
    // normal constructor function, still will be able to be used when statically linking with this crate 
    pub fn new() -> Foo {
        ...
    }

    // constructor function which will have a stable ABI
    pub fn new_boxed() -> Box<Foo> {
        Box::new(Self::new())
    }
}

@bjorn3
Copy link

bjorn3 commented Oct 31, 2020

Such a type can't be dropped on the other side without accidentially using the wrong size to deallocate the memory.

@carbotaniuman
Copy link
Author

C++ smart pointers have a custom deleter to facilitate this. Rust may also get away with passing opaque arrays representing the size of the reserved struct, which gives authors some freedom to evolve without having to box.

@BenLewis-Seequent
Copy link
Contributor

Didn't realize that deallocate needed the size of alignment of the pointer 😢.

A solution would be to change the return value of new_boxed to something would also track the layout of Foo, either dynamically with BoxWithLayout which would store the layout from when it was constructed or statically with struct BoxedFoo { boxed: Box<Foo> } defined in the same crate as Foo.

Though a proper solution might to have a dynamically sized type extern Foo which can be coerced to from Foo, and which extra value is a pointer to struct that contains the size and alignment of Foo. i.e. new_boxed would become:

pub fn new_boxed() -> Box<extern Foo> {
    Box::new(Self::new()) as Box<extern Foo>
}

@carbotaniuman
Copy link
Author

Here's some discussion about this topic. I don't get boats' dismissal of the concept - a custom implementation of Box could look like this. This could give as-good-as C++ ergonomics to Rust extern types, while also fitting into Rust's ecosystem of custom smart pointers.

pub struct ExternBox<T: ?Sized + ?DynSized, A, Deleter>(_)
 where ....

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

3 participants