> you can call a specific version of a function in a library

A library has some functions. Later, the library gets refactored, and those functions are thereby rewritten; but since no changes have actually been made, the version number stays the same. However, there exists some legacy code that horrendously breaks when given a non-ABI-compatible function from this library, _even though_ the functionality and version of those functions the same. Oh dear! What can we do? Well, clearly exact binary compatibility is important, so someone has an idea: we'll specify functions with exact hashes of their code instead of versioning, so this problem doesn't happen.

This is all well and good, but then the library decides to update some internal utility subroutine, changing its hash, but _not_ changing its call sites. Oh dear, now some other functions have hashes that are still the same, but they have different functionality due to that utility update, and legacy code breaks horribly again! In order to make sure this doesn't happen, someone comes up with the idea that a function's hash should be dependent on the hashes of all of the functions, variables, etc. it depends on _and_ its code, so all of the "inputs" that go towards defining the function end up updating its hash, thereby insuring legacy code stability.

Now we run into another problem: where do we put all of these functions? They need to be stored somewhere on the system so applications can have access to them, so we'll put them in a repository that we'll call a "store". Now we can do cool stuff with shared stores such as "only load code when needed"!

Oh dear, now the user sitting at the shell of the system wants to run programs too. Our function infrastructure works so well, why not extend it to programs too?

Some programs really like filesystems and environments and depend on specific structures. We can't really encode these structures as an "input" to our programs' hashes, because they could be highly dynamic (or even undescribable), so instead we can put software into a little box where it sees the filesystem structure it wants, so it is happy, and the box itself could be specified with input programs/functions in the same way that programs or functions can!

Hmm.. we seem to be recreating this "hash" structure that depends on inputs a lot. We need a common name for this... hmm, how about "derivations"?

Congratulations, you have just reinvented Nix! The solutions in your comment that you talk about are things that Nix already was designed to provide. The only discrepancy is that Nix doesn't actually provide hashes for individual functions, rather they work for libraries, but this is because of a limitation on how libraries work: that they can have shared, global, mutable state, making the idea of individual function versions useless.

The main problem why Nix doesn't obviously seem to solve these problems is that Nix's tools are not very well designed. For example, it's very non-obvious how to specify older versions of packages. However, flakes makes this easier, and although I don't believe _Nix_ specifically will be the solution, the holy grail at the end of the road will be very similar to Nix in core ideas.