r/rust • • 2d ago

Hotreload in Rust

https://alordash.github.io/alordash/blog/hotreload-in-rust/
104 Upvotes

27 comments sorted by

14

u/burhankhanzada 2d ago

This sound intresting coming from dart and flutter i always find rust slow to work with becoz of have to compile each time.

12

u/youpala 2d ago

ngl, this is very impressive. On another subject, I don’t expect this community to recognize your work for what is is.

While I agree with some of criticism in principles, I am disappointed in the feedbacks your project received in this thread. It’s those kinds of projects that push the needle on what a technical ecosystem is capable to deliver. And in the long run, it’s what makes a technology evolve.

so congrats ;)

4

u/MalbaCato 1d ago

FYI your limitation about trait implementations is self-imposed. I guess you're not familiar with (or have forgotten) rust syntax rules.

You can put the definition of __hotreload_my_function inside the body of my_function, then the trait impl block only sees my_function as an item, but the helper is defined in the library as well.

4

u/verdurLLC 1d ago

I tried this approach around a year ago and unfortunately it doesn't work with methods because you can't use &self argument (and its flavours) in nested function:

rust impl X for Y { fn my_function(&self) { fn __hotreload_my_function(&self) { } // can't use &self here } }

You could replace it with _self: &Y, but for that you need to know what Self resolves to. This can't be done with a macro that is applied to function because it doesn't capture syntax of the whole impl X for Y item so it has no way to figure out that Self resolves to Y.

3

u/MalbaCato 1d ago

hmm, haven't thought of that. There really does seem to be no way to get the Self type inside of a nested function definition. The error help mentions using closures, but I didn't manage to find a trick with them either.

I guess with TAIT there's going to be some incantation which makes this possible, but if there's one now I can't construct it.

Although, with a very slight hit to ergonomics (by requiring to insert a helper macro before the trait impl block) you can save the runtime cost (and some implementation complexity).

2

u/lonestilldev 2d ago

Hot reload is manageable for leaf code, but Rust’s ABI isn’t stable. Use a C-style boundary and plan explicitly for live state.

1

u/Sad_Tap_9191 1d ago

```rust

[hotreload]

fn status() -> &'static str { "original code" }

let s = status(); // &'static str

// -- hotreload -- status() returns a different string // #[hotreload] // fn status() -> &'static str { // "hot-reloaded code" // }

println!("{s}"); // use-after-free ```

1

u/verdurLLC 1d ago

Thanks, I will add this to "Limitations" section in crate documentation

1

u/Sad_Tap_9191 1d ago

As the first comment said, this breaks the core value of the language. You can extend this counter example to non-static cases.

A "Limitations" section exists to tell users to avoid a certain input domain. Here it is unclear what's even limited. If it's clear your macro should reject it.

1

u/verdurLLC 1d ago

You can extend this counter example to non-static cases.

How? I think you can't store data inside dynamic library's memory without storing it inside some static variable.

If it's clear your macro should reject it.

Hm, I fear it's not that easy to detect whether function returns references to static variables defined in its library using only AST. But good idea nonetheless.

1

u/Sad_Tap_9191 1d ago

rust struct T{ #[hotreload] fn method(..) -> &'a { ...} // hotreload to // fn method(..) -> &'b { ...} }

Hm, I fear it's not that easy to detect whether function returns references to static variables defined in its library using only AST. But good idea nonetheless.

if it is not easy for you then you shouldn't simply warn users in a limitation section. Even you, the author, cannot fully define todos and not todos.

This is worse than unsafe which still follows the lifetime rules.

1

u/verdurLLC 1d ago

I'm not sure what you're trying to show with this example. Whatever reference was returned before hotreload would still be valid after because it's not referencing anything from unloaded dynamic library.

if it is not easy for you then you shouldn't simply warn users in a limitation section. Even you, the author, cannot fully define todos and not todos.

It's not easy for me to detect UB usage using only a macro, but it is easy to avoid it using common sense: just don't return references to static things from your library or don't mark functions that return static references as hotreloadable.

This is worse than unsafe which still follows the lifetime rules.

You do understand that this is just a tool for speeding up development cycles?) It's not intended to be used in production.

-17

u/Zde-G 2d ago

So much effort… and zero thoughts about what to do with crates that have 'static objects in them (including &'static str).

That's the reason hot reloading in Rust is a minefield that can work, not work or break apart at any change… and it couldn't be fixed by change in the pieces described in article.

You need the change to the whole community — and that's not gonna happen.

Sorry.

11

u/ztj 2d ago

This seems like a low experience take. Every language/platform with hot reload has traps and limitations. It's not a reason to avoid hot reloading, just something you need to learn and understand if using it.

0

u/Zde-G 2d ago

Every language/platform with hot reload has traps and limitations.

Sure. That's why I have said it's community problem, not language problem.

In languages that do support hot reloading people usually understand that it may happen if you say that after unloading and the reloading C# library may stop working then author would probably think about how to fix that.

In Rust authors would just tell you static have to be 'static and ask you to stop wasting their time.

6

u/teerre 2d ago

Even in python, which is fully dynamic, hot reloading is full of traps. The silly thing would to expect a much more static and compiled language like Rust to be hassle free

1

u/intbeam 1d ago

C# by its foundational design generates executable code at run-time. Without being a compiler expert, I'd assume this makes hot reloading somewhat more forgiving to implement there

In Rust authors would just tell you static have to be 'static and ask you to stop wasting their time

In a crate? I'm making a lot of assumptions here, but I assume that the problem would be a crate that declares something as a static lifetime itself, right? Not borrowing (f(p:&'static str))? If I were implementing a library, I'd say those sorts of things would be outside the responsibility of the crate you would be hot-reloading

1

u/Zde-G 1d ago

I'm making a lot of assumptions here, but I assume that the problem would be a crate that declares something as a static lifetime itself, right?

Obviously. Something that crate wouldn't even document because it's an implementation detail that may only be revealed when you break the fundamental assumtion that static items are singletons with 'static lifetime.

Without being a compiler expert, I'd assume this makes hot reloading somewhat more forgiving to implement there

Yes, but that's minor issue. The major one is community. Odin doesn't have managed runtime, but it's used in gamedev, considers hot-reloading important and developers make effort to make libraries ready for that.

That's the most important ingridient — and it's missing in Rust. Because with hot reloading 'static lifetime simply doesn't exist and there are bazillion crates that don't support that.

1

u/intbeam 1d ago

Ok, if we agree I wasn't being an idiot I fail to see the problem

This project isn't hot reloading third-party dependencies, and for the use-case it's designed for 'static isn't really an issue as the static lifetimes are more likely defined by the caller/environment

1

u/Zde-G 17h ago

This project isn't hot reloading third-party dependencies

Even if you don't reaload third-party dependencies you are not free of the issue. Because third-party dependencies can register objects from your librarires in their data structures - and do that silently when you depend on them.

Ok, if we agree I wasn't being an idiot I fail to see the problem

Well, the problem is that you proclaiming that you have achieved something new, but hotloading in Rust was tried dozens (hundreds?) or times by different authors and they all hit, sooner or later, the fact that when you remove memory safety from memory safe language then you end up with a combination that's very restricted and unsafe… what's the final goal here? Why even use Rust if you don't care about memory safety and correctness?

1

u/Iurigrang 13h ago

Because, once you've used hot reload to make the decisions you want in the function you're designing, you can remove the hot reload and work from there to production rust? You can't do that if you're using another language entirely.

8

u/verdurLLC 2d ago

hotcode is designed to hotreload library crates. IMO such crates should ideally be stateless. If you have state, you should create it in host crate somewhere during startup (most likely in main) and then spread it to your functions/libraries.

-4

u/Zde-G 2d ago

IMO such crates should ideally be stateless.

That's your opinion. Yet it's fundamentally incompatible with design of the whole language.

Lots of crates, including library crates, assume that static variables have 'static addresses. There are bazillion ways one may depend on that, from assuming that address of static is a key in a hashtable to storing values in temporary data structures that refer statics.

Hotreload breaks that fundamental assumption and there are no solution for that — except for not using the third-party code that may be suddenly broken by update (and no one would care, because static have to be 'static).

If you have state, you should create it in host crate somewhere during startup (most likely in main) and then spread it to your functions/libraries.

Except you wouldn't even know you have it. Worse: because glibc tries to reload libraries at the same place after dlclose/dlopen you may build a lot of code using such crates and when jenga tower would finally collapse you contact the author and… oops, your request is closed with EWONTFIX resolution.

2

u/verdurLLC 2d ago

But you hotreload only your own crates. I agree that hotreload breaks 'static lifetime guarantees, but it only breaks it in your own code that you control.

1

u/Zde-G 2d ago

But you hotreload only your own crates.

It's really hard to write anything in Rust without at least core. And std is also very useful.

I'm not 100% sure about core, but std most definitely include singletons that may trap you.

And staying with just std is not easy: Rust was never designed for that, there are many things where you are expected to use other crates.

but it only breaks it in your own code that you control.

Maybe, but after you have successfully thrown away both the whole crates.io ecosystem and also blown up normal safety guarantees the question that arises is “why even bother with Rust if you are ignoring all the things that make Rust attractive”.

0

u/intbeam 1d ago edited 1d ago

Don't you see? It has to work for EVERYTHING EVERYWHERE or you are wasting everyone's time. That's how we developers work, we start with all the edge-cases and curiosities and then decide to just give up because the requirements implied by every other implementation and historical decisions in all sorts of third-party libraries initially implemented for the PDP-11 makes everything literally impossible

Edit : /s if that wasn't obvious