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.
Hrm. I don’t think Python’s importlib.reload is used very broadly.
Are those being hot-reloaded, or merely re-executed from scratch with no compile time?
When I think hotreload I think “persist state but update code”. Persisting state safely when you change data layout is almost impossible. Even if the language is dynamic you’ll have stale objects that are missing the new members or didn’t go through the new initialization routine.
I use hot reloading in C++ every day in Unreal Engine. However that only supports changing functions. If you change data layout you have to close the editor, rebuild, and relaunch.
I consider it a form of hot reloading. The Live+ website states "Live++ enables hot-reload for C/C++ applications, combining the power of rapid iteration with the speed of a compiled language."
Critically useful. But I write a lot of code in C++ rather than blueprints.
Turn around time from changing a line of code is under 10 seconds. Closing and relaunching the editor is actually not too bad, but probably 30 seconds?
In JS for example, if you change the prototype of an object you have effectively changed it's data layout but of course everything is behind a pointer / an indirection.
When changing instance or class variables in Smalltalk, the system actually changing the object layout and goes and recompiles code using it. If you are interested in the details: Chapter 5 of [1] explains it a bit.
Yes, the indirection (and also late binding) is why hot reloading is (somewhat) ergonomic in those languages, but would be difficult to do in Rust (or C++, etc).
I think the shared library types would need to be indirected either via compiler support, or some kind of `Hot<T>` wrapper that acts like a `Box<T>` when hot reloading is enabled, and like a `T` in "production" mode to be ergonomic.
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.
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.
> have a "release" compilation mode that removes the hot reloading entirely
Oh that is easy to do thanks to Rust features, see the reload-feature example: https://github.com/rksm/hot-lib-reloader-rs/tree/master/exam...
Very cool!
Is there any language that supports hot reloading + data layout changes? I’m not sure that even makes sense.
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
https://rigsomelight.com/2014/05/01/interactive-programming-...
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.
Hrm. I don’t think Python’s importlib.reload is used very broadly.
Are those being hot-reloaded, or merely re-executed from scratch with no compile time?
When I think hotreload I think “persist state but update code”. Persisting state safely when you change data layout is almost impossible. Even if the language is dynamic you’ll have stale objects that are missing the new members or didn’t go through the new initialization routine.
I use hot reloading in C++ every day in Unreal Engine. However that only supports changing functions. If you change data layout you have to close the editor, rebuild, and relaunch.
Live coding or hot reloading? The latter is deprecated and famous for corrupting data.
Live coding.
I consider it a form of hot reloading. The Live+ website states "Live++ enables hot-reload for C/C++ applications, combining the power of rapid iteration with the speed of a compiled language."
Word I wasn't sure as UE has "both", do you find it useful in spite of the limitations?
Critically useful. But I write a lot of code in C++ rather than blueprints.
Turn around time from changing a line of code is under 10 seconds. Closing and relaunching the editor is actually not too bad, but probably 30 seconds?
Those all don’t let you change your data’s layout: they’re always behind a pointer, no? It’s a uniform representation.
In JS for example, if you change the prototype of an object you have effectively changed it's data layout but of course everything is behind a pointer / an indirection.
When changing instance or class variables in Smalltalk, the system actually changing the object layout and goes and recompiles code using it. If you are interested in the details: Chapter 5 of [1] explains it a bit.
[1] Hirschfeld et al. Assisting System Evolution: A Smalltalk Retrospective. http://soft.vub.ac.be/Publications/2002/vub-prog-tr-02-25.pd...
Thanks!
Yes, the indirection (and also late binding) is why hot reloading is (somewhat) ergonomic in those languages, but would be difficult to do in Rust (or C++, etc).
I think the shared library types would need to be indirected either via compiler support, or some kind of `Hot<T>` wrapper that acts like a `Box<T>` when hot reloading is enabled, and like a `T` in "production" mode to be ergonomic.
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.
Sounds like Erlang from what I’ve heard about it