Earlier quoted context omitted.
Of course, but then you've hamstrung the typesystem that makes Rust safer than C, and generated a lot of work for developers who have to convert all internal types to #[repr(C)] ones at library boundaries. Rust without cross-crate generics is way less fun--you lose "theorems for free", unless you can make guarantees about the pre-ABI translation to/from C types (which would ultimately amount to defining an ABI).
You've hamstring the type system at the interface , internally you still get all the benefits. In any case, a typical use-case for a shared library like this is embedded into other applications, which may not be written in Rust at all, and hence things like generics don't work anyway. In fact, this is reflected in a common way to expose an interface in this manner: have a main Rust interface (i.e. with normal generic…
Generics are another place where this becomes the case: if a library exposes a "fold" function generic over both the accumulator and element type, accepting void* instead of T and U means you lose the ability to make sure user don't mix up places where you want an element and places where you want an accumulator.
Dynamically upgradable cross-crate generics, ultimately, require recompilation if you want the performance benefits of monomorphization. But it would be possible to have patchable sites around inlined generics that the dynamic linker could change to instead jump to a version which did not inline generics and which simply called into a non-type-specialized routine in the other library. You'd end up with two versions of compiled code in the binary around each call site for a generic: an inlined-optimized-and-code-motioned one, as usual, and an assumption-free one compiled considering generics to be opaque blobs and using dynamic dispatch for trait methods so that the same code can be used for all instantiations of the generic function.
For example, a function parameterized over an unconstrained type T can't do anything useful with values of that type, except dropping them. Thus code would be generated in which the call to the drop impl would be dynamic.
I think this can be made to work w/r/t non-object-safe trait bounds as well, but I'm not sure how to justify that.
I think library changes which alter which impls get called on which types (i.e. which affect specialization) would still be ABI-breaking, unless you had some more cleverness (type-id-based dispatch for the deoptimized case? ugh) to deal with that complexity.
I also don't think this solution is ideal, but I think it's very important for Rust to be considering these issues, because given the design of the current modern operating system, having separately upgradable libraries is really important. Maybe Rust should be pushing for changes in the OS design, in a direction like NixOS is taking, but it seems like it isn't ready use for building the future's userspace when it can't provide both safety benefit across library boundaries and separate upgradeability.