| Crates.io | chaud |
| lib.rs | chaud |
| version | 0.1.0 |
| created_at | 2025-05-24 05:39:16.221632+00 |
| updated_at | 2025-05-24 05:39:16.221632+00 |
| description | A hot-reloading library for Cargo workspaces designed for ease of use. Unix only. |
| homepage | |
| repository | https://github.com/TimNN/chaud |
| max_upload_size | |
| id | 1687042 |
| size | 54,144 |
Chaud (French for "hot") is a hot-reloading library for Cargo workspaces designed for ease of use. Unix only.
use std::sync::atomic::{AtomicU32, Ordering};
// Statics annotated with `persist` will keep their value, even if the crate
// is hot-reloaded.
#[chaud::persist]
static STATE: AtomicU32 = AtomicU32::new(0);
// Functions annotated with `hot` will be hot-reloaded, and use the latest
// available version every time they are called.
#[chaud::hot]
fn do_something() -> u32 {
STATE.fetch_add(1, Ordering::Relaxed)
}
fn main() {
chaud::init!();
loop {
println!("Something: {}", do_something());
std::thread::sleep(std::time::Duration::from_secs(2));
}
}
Unless the relevant Cargo feature is enabled, the #[chaud::*] macros are
essentially no-ops (and can thus be present even in production code). Check the
documentation for details on the syntax supported by
the macros.
Enabling the unsafe-hot-reload feature will rewrite the items annotated with
#[chaud::*] so that they can be hot-reloaded. Then, once you call
chaud::init!(), Chaud does everything necessary to hot-reload your code:
#[chaud::hot] functions to
their latest version.This requires some specific linker features to work, which need to be configured and are not supported on Windows.
See How It Works if you are curios about the details.
[!CAUTION] Hot-reloading is fundamentally unsafe. Use at your own risk.
Care was taken when writing the unsafe code in Chaud itself, but at this point it has not been audited by any (community) experts.
Chaud is still experimental and needs more extensive testing, especially in non-standard linking scenarios, larger projects, and on platforms other than macOS.
Chaud's hot-reloading implementation is tested on aarch64-apple-darwin and
x86_64-unknown-linux-gnu via CI. Other Unix platforms are expected to work as
well, unless their linkers differ significantly from the Linux linker, in which
case Chaud may require platform-specific support.
Hot-reloading is not supported on Windows, because as far as I could tell it is not (easily) possible to create DLLs with undefined symbols.
If hot-reloading is not enabled (i.e., the unsafe-hot-reload feature is not
enabled), then Chaud should compile on all platforms.
Chaud is tested on stable, beta and nightly. However, it requires some
unstable rustc flags to operate properly, and generally depends on rustc
implementation details that could change at any time.
For now Chaud targets the latest stable Rust version. In the future older Rust versions will likely be supported as well, probably with a policy along the lines of "if hot-reloading is enabled, the current or previous stable release is required; otherwise a stable release from the past 18 months is required".
Add chaud as a dependency to your application and call chaud::init!() during
your startup process (after you have configured logging).
The init!() macro can only be used in the package that contains your
fn main. The init() function can be used from any package, but requires more
manual setup.
chaud must be a dependency of the package that contains your fn main, so
that its features can be enabled with the --features chaud/<feature name>
flag.
The easiest way to actually enabled hot-reloading is via
cargo install chaud-cli. This enabled the cargo chaud and chaud-rustc
integrations.
cargo chaudcargo chaud takes the same arguments as cargo run, but automatically does
everything necessary to enable hot-reloading.
chaud-rustcIf you cannot use cargo chaud (e.g. because cargo is invoked by some other
build tool), you can instead set RUSTC_WRAPPER=chaud-rustc to get most of the
same benefits.
chaud-rustc will automatically add the necessary rustc flags when it detects
compilation of a binary that has hot-reloading enabled.
If used with the init!() macro it can also detect enabled features
automatically. In case that does not work, you must manually specify
CHAUD_FEATURE_FLAGS as described in the next section.
To actually enable hot-reloading you must enable Chaud's unsafe-hot-reload
feature.
As when using chaud-rustc, you must enable the unsafe-hot-reload feature
to actually enabled hot-reloading.
To ensure that everything is linked correcty, you must pass additional flags
to rustc when it links your application. This is often accomplished via the
RUSTFLAGS environment variable. The <platform specific> part is:
-Clink-dead-code: Disable dead-code stripping.-Zpre-link-args=-Wl,-all_load: Include all symbols from static
libraries.
-Zpre-link-args=-Wl,--whole-archive: Include all symbols from static
libraries.-Clink-args=-Wl,--allow-multiple-definition: Ignore duplicate symbols
from Rust's compiler_builtins and libgcc.-Clink-args=-Wl,--export-dynamic: Make all symbols in the executable
available to hot-reloaded libraries.If you are not using nightly, you must set RUSTC_BOOTSTRAP=1 to use the
-Z flag.
If you are not using your crate's default features, you set
CHAUD_FEATURE_FLAGS to inform Chaud about the enabled features. For example,
CHAUD_FEATURE_FLAGS="--no-default-features --features=alpha,beta".
Hot-reloading is fundamentally unsafe. By enabling the unsafe-hot-reload
feature, you acknowledge and accept the associated risks.
The following is an incomplete list of things to keep in mind:
statics defined in hot-reloaded crates will be duplicated, unless they are
annotated with #[chaud::persist].#[chaud::hot] is called. If such a function is never called, old code will
keep running indefinitely.The vast majority of Chaud's work happens in the background, which leaves
logging as the only real option for reporting any errors. The
log crate is used for that purpose.
Many things can go wrong while hot-reloading, so to avoid any confusion it is
important that you enable logging for Chaud at least at the warn level to
be informed about any issues.
Enabling the info level is recommended to track what Chaud is doing, so
you know when e.g. a Cargo build is running, or a hot-reload completes.
If you do not configure any logger, Chaud will install a simple one (which prints to stderr) for you.
If you do configure your own logger, but do not enable at least the warn
level for Chaud, Chaud will print a single message to stderr complaining about
that fact. (You can disable this behavior with the silence-log-level-warning
feature).
error: Unrecoverable errors, hot-reloading will not workwarn: Potentially recoverable errors, hot-reloading likely won't work
correctlyinfo: High-level messages about what Chaud is doing
debug: Detailed messages about what Chaud is doing
trace: Verbose messages to aid in debugging
Carefully read all warn messages logged by Chaud, they may be able to point
out what the problem is.
If that doesn't help, then feel free to open an issue and I'll do my best to
help. Please include trace logs for chaud.
To debug issues with undefined symbols, compiling with
-Csymbol-mangling-version=v0 can be useful because it includes more
information in the symbol name.
During the initial compilation with the unsafe-hot-reload feature enabled,
Chaud generates code similar to the following:
// For `#[chaud::hot]`:
fn do_something(args) {
#[chaud::persist]
static __chaud_FUNC: AtomicFnPtr = AtomicFnPtr::new(actual_fn);
__chaud_FUNC.get()(args)
}
// For `#[chaud::persist]`:
#[export_name = "_CHAUD::module::path::STATE"]
static STATE: Whatever = Whatever::new();
The hot macro stores a function pointer to the actual implementation in an
atomic static, and just calls the latest value of the atomic every time.
The persist macro gives that static a non-mangled name that never changes.
To see the full expansion, check out the expanded_*.rs files in the /demo/
directory.
chaud::init() isn't particulary intersting. It spawns a background thread
that:
cargo metadata to understand the structure of the workspace.A reload build sets the __CHAUD_RELOAD environment variable in addition to
enabling the unsafe-hot-reload feature. This changes the code generated by
the macros:
// For `#[chaud::hot]`:
fn do_something(args) {
#[chaud::persist]
static __chaud_FUNC: AtomicFnPtr = AtomicFnPtr::new(actual_fn);
// NEW:
ctor! { __chaud_FUNC.update(actual_fn) }
__chaud_FUNC.get()(args)
}
// For `#[chaud::persist]`:
unsafe extern "Rust" {
#[link_name = "_CHAUD::module::path::STATE"]
static STATE: Whatever;
}
The persist macro changes the static to reference the one defined by the
initial compilation.
The hot macro now defines a ctor! that
automatically updates the function pointer stored in the atomic to the latest
version once the dynamic library containing it is loaded.
Since the extern statics would produce linker errors, Chaud performs
reload builds with -Clinker=true and records the linker invocation via
--print=link-args.
From the linker invocation, it extracts the rlib and object files that have
changed since the initial build, manually links them together into a dynamic
library, and then loads that library.