Hacker News new | ask | show | jobs
by choeger 1597 days ago
> DLL’s add a level of complexity to writing and using software, and newer languages like Rust and Go have eschewed them,

Yeah, no. Every sane compiler developer would always prefer DLLs over static linking because of modularity. It makes the compiler much smaller if you can defer the combination of modules to a linker and thus have proper separate compilation.

Unfortunately, separate compilation with a C-like ABI only works well with a relatively limited monomorphic language. All other languages would have to leave quite a few optimizations on the table or make some suboptimal choices for uniform object representation (compare OCaml's ABI with Rust or Haskell).

Consequently, a modern language would face the tremendous (but I think interesting) task to extend the C-ABI in a way that allows these optimizations and separate compilation. Understandably, developers then play down the importance of separate compilation and choose a global approach.

3 comments

I do not understand what you mean.

There is no relationship between whether static or dynamic linking is used and modularity.

When a program is decomposed in modules, those are compiled separately and the complete executable program is made by linking, either statically or dynamically.

The static vs. dynamic option does not influence the semantics of the program regarding modularity.

Dynamic linking offers a few extra features, e.g. delaying the linking of a library to some time after a program starts and choosing one between more libraries at that time, but exactly the same functionality can be implemented in a statically linked program (using pointers to functions), with no difference in behavior (but with different costs in memory space and execution time; which costs are larger will be different for each particular case).

Actually a compiler that targets only static linkers will be slightly smaller, because many of the standards for dynamic linking, e.g. the UNIX SVR4/ELF ABI, require the compiler to emit additional instructions and data structures whenever external variables or functions are accessed, in comparison with the case when only static linking is used.

Dynamic linking is an additional complication for the compiler, not a simplification. Proper separate compilation of modules is the easiest with only static linking, when the compiler just has to emit appropriate relocation and linking data (which was actually the job of an assembler for the traditional UNIX compilers, which generated an assembly program, not an ELF object file), besides what it needs to do for compiling a monolithic program.

> When a program is decomposed in modules, those are compiled separately

No, they're not. At least not in languages with nontrivial features and optimizations. Consider the identity function id = \\x.x in some module A and it's usage A.id 42 in some module B. Unless you commit to uniform object representation, excluding several optimizations, there's no way to compile A separately from it's use in B (because specialization of A.id is required). That fact excludes the option of creating dynamic libraries because you would expect the dynamic library compiled from A to be used in stead of A (with the necessary type interface data). Similar problems occur with polymorphic data structures.

Separate compilation, modularity, and dynamic linking are all aspects of the same problem.

I think that's why Rust uses a global compilation approach and if I am not completely mistaken, only Swift tries to have dynamic linking with polymorphism.

I don't think the parent meant to use "modularity" to mean "pertaining to a programming language's concept of modules", but rather something along the lines of "pertaining to the act of splitting something into self-contained, clearly delineated, interoperating pieces".
Exactly. Modularity on the frontend is dead simple. Modularity all the way down to executing machine code is hard.
As a compiler developer, yeah, no, you're wrong. Dynamic linking creates several more challenges for compilation than static linking.

If you're trying to be cross-platform, you have the major issue that dynamic libraries have very different models on different platforms. Windows DLLs require all of the exported--and imported--names to be explicitly identified at compile time; MACH-O and ELF files don't require that. On the other hand, ELF files have symbol visibility which can be toggled to do something similar, but the standard expectations in C are completely different: every symbol is capable of being exported (or overridden--thanks symbol preemption!). (I don't have much familiarity with MACH-O's two-namespace system, so I won't talk about it further).

There are other issues. DSOs make it hard to use linker features such as arranging things in a section to make a distributed static list (e.g., for registering reflection stuff). Even doing static constructors in dynamic libraries is hard. Arranging for single static addresses (as C/C++ require) can be challenging. On Windows, malloc in one DLL can't be freed by free in another... sometimes. It also requires defining a stable ABI, since you have no reasonable expectation that the other side of the dynamic library is using the same compiler version you are. In general, crossing the dynamic library boundary is like doing FFI, given how little control you have over the process.

No, static linking is much easier. You don't have to hold in your head so much more crazy semantics with dynamic linking.

Static linking does not preclude separate compilation though. Go and Rust both use separate compilation with static linking by default (at least last I used them... it's been a while). I know Rust at least used to support dynamic linking too -- it just wasn't the default. And C also supports static linking of course.
I don't think Rust uses separate compilation. And go just seems to not care about a stable ABI.
It surely does, you can pack crates as rlibs.