Dynamic languages of course support dynamic linking.
ABI breaks are everywhere in C++... consider a class
class foo {
public:
foo();
void do_something();
private:
int x;
}
and an impl in the dynamic library: #include "foo.h"
foo::foo() {
this->x = 0;
}
void foo::do_something() { std::cout << this->x << std::endl; }
You build a libfoo.so and clients use it, calling `foo f; f.do_something();`, it calls your library and it's great.But as soon as you ship a new version that adds a new field to foo (still source-code compatible):
class foo {
public:
foo();
void do_something();
private:
int x;
int y;
};
With a new function body in your shared object: void foo::do_something() { std::cout << this->x << this->y << std::endl; }
You're hosed. Clients have to recompile, or they get: $ ./main
0, 0
*** stack smashing detected ***: terminated
[1] 2625391 IOT instruction (core dumped) ./main
Because the size information for an instance of foo is only known at compile-time, so the clients aren't allocating enough space on the stack for it (ditto the heap if you're using `new foo();`)The way around this is awkward and involves the pimpl pattern and moving all your constructors out-of-line... but you also need to freeze all virtual methods (even just adding a new one breaks ABI), and avoid using any template-heavy std:: types (not even std::string), since those are often fragile.
Most people just give up and offer an extern "C" API, because that has the added benefit that it's compatible across compilers.
"true" C++ shared libraries are crazy difficult to maintain. It's the reason microsoft invented COM.
Swift goes through crazy lengths to make this work, and it's impressive: https://faultlore.com/blah/swift-abi/#resilient-type-layout
Rust would have to do something like Swift is doing, and that's probably never going to happen.