Only being able to change function implementation (and non generics only at that) seems like a substantial limitation. Its not often I’m quickly prototyping something that would benefit from hot reloading, and not also changing data layout as I’m iterating.
I’m curious to see if the sharp edges will soften as time goes on. Mangling structs to be pinned to a particular loaded library version somehow, and injecting indirection where they’re used, would take this a long way. But would probably require compiler support.
Yeah the approach is restricted and more compiler support for this kind of workflow would be incredibly welcome. I wrote this in part to try to get more folks in the Rust community thinking in this direction. The hot-reload support that Zig is planning sounds really exciting and I would love for Rust to have something similar (one can dream, right ;).
That being said, you can still go quite some way with this approach. On the one hand, simply being able to twiddle an algorithm into shape is really handy when making a game, visualization or exploring data.
On the other hand, you can also serialize state or keep it in a generic form like a serde_json::Value. Serialization of course needs some kind of migration on the part of the code using it (if the data layout changes it loads an old version and needs to convert it to the new form) or defaults for newly added fields/types. However, this is true in general, even in other languages.
Like you say, modeling the interface between the shared library like a Thrift struct (which can be serialized) may be beneficial, and would facilitate adding/removing fields by having e.g. default fields. Or just crash if a non-backwards compatible change is made, e.g. a new field is expected but not present, but ignore fields that are present but no longer expected.
I think the other killer feature that this is missing is being able to have a "release" compilation mode that removes the hot reloading entirely, and seamlessly statically links in the library.
Clojure(script) has a pretty good story around this. But I hear tell that common lisp has even more powerful hot reloading magic.
But, in clojure if you idiomatically model your app state as plain-ol-data, and naked functions that modify it, then you are in dynamic editing heaven. You can make any change at runtime as long as your live data is still compatible with the functions that use it.. which ends up being most of the time unless you change the semantics of an attribute.
Might not be the best example, but this article goes over live-coding a flappy bird clone
Erlang, JavaScript, Ruby, Python come to mind. Dynamic languages which inherently don't expect data to have a statically enforced shape.
Doing it in a statically typed language would be substantially harder, and to make it feel "good", probably require lots of tracking of library versions associated with particular data objects or strict isolation of how data passes across the library boundary. I don't think there's a great solution to be had.
Common Lisp. You can redefine any class while your system is running, and you can customize how the data from old instances is updated to be used by new instances. This is part of the Metaobject Protocol, where the language calls functions and methods that are part of your program in order to implement many of its features.
Just a quick thought... wouldn't it be a bit more practical to implement this behind some form of "load balancer"-like system at the network/io layer? Are self updating processes really the right abstraction? Seems like it kinda flips reliable build/deploy processes on their head a bit.
It's definitely a possibility to design some form of RPC mechanism and have multiple parts of your application run as essentially a distributed system. There would still be the problem that the "main" application part is expecting the rest of the system to behave in a certain way and if it is maintaining state, the state to be in a certain form. So I'm not sure that you would really win much.
Also, part of the idea is to make hot-reload as little additional burden as possible. Proper compiler support would be the real solution to that but in the form presented in the article you can at least quickly switch between an efficient static build and a dev version, something that would be hard to achieve with an RPC mechanism.
> Are self updating processes really the right abstraction?
As a debug tool, absolutely. As as a normal part of program execution in production? A bit sketchy. But that said....
... what if there was a sure way to directly map your entire program state into the new version and you could be assured that you didn't miss any obscure corner of the state space? Like a borrow checker for transitioning between program versions. That'd be pretty neat.
Agreed, I made something similar to live patch services w/o downtime and despite being cool I'm not sure I'd recommend it vs. just putting replicated containers behind a load balancer.
I’m curious to see if the sharp edges will soften as time goes on. Mangling structs to be pinned to a particular loaded library version somehow, and injecting indirection where they’re used, would take this a long way. But would probably require compiler support.
That being said, you can still go quite some way with this approach. On the one hand, simply being able to twiddle an algorithm into shape is really handy when making a game, visualization or exploring data.
On the other hand, you can also serialize state or keep it in a generic form like a serde_json::Value. Serialization of course needs some kind of migration on the part of the code using it (if the data layout changes it loads an old version and needs to convert it to the new form) or defaults for newly added fields/types. However, this is true in general, even in other languages.
I think the other killer feature that this is missing is being able to have a "release" compilation mode that removes the hot reloading entirely, and seamlessly statically links in the library.
But, in clojure if you idiomatically model your app state as plain-ol-data, and naked functions that modify it, then you are in dynamic editing heaven. You can make any change at runtime as long as your live data is still compatible with the functions that use it.. which ends up being most of the time unless you change the semantics of an attribute.
Might not be the best example, but this article goes over live-coding a flappy bird clone
https://rigsomelight.com/2014/05/01/interactive-programming-...
Doing it in a statically typed language would be substantially harder, and to make it feel "good", probably require lots of tracking of library versions associated with particular data objects or strict isolation of how data passes across the library boundary. I don't think there's a great solution to be had.
Also, part of the idea is to make hot-reload as little additional burden as possible. Proper compiler support would be the real solution to that but in the form presented in the article you can at least quickly switch between an efficient static build and a dev version, something that would be hard to achieve with an RPC mechanism.
As a debug tool, absolutely. As as a normal part of program execution in production? A bit sketchy. But that said....
... what if there was a sure way to directly map your entire program state into the new version and you could be assured that you didn't miss any obscure corner of the state space? Like a borrow checker for transitioning between program versions. That'd be pretty neat.
Deleted Comment