| Crates.io | phylactery |
| lib.rs | phylactery |
| version | 2.0.2 |
| created_at | 2025-08-10 21:44:02.083139+00 |
| updated_at | 2025-09-08 03:44:13.926788+00 |
| description | Safe and thin wrappers around lifetime extension to allow non-static values to cross static boundaries. |
| homepage | |
| repository | https://github.com/Magicolo/phylactery |
| max_upload_size | |
| id | 1789327 |
| size | 53,689 |
Crafted through a vile ritual, a phylactery is a magical receptacle that holds a necromancer's soul, permanently binding it to the mortal world as an immortal lich.
Safe and thin wrappers around lifetime extension to allow non-static values to cross static boundaries.
T with Soul<T>::new(value).Soul with core::pin::pin! or Box/Arc/Rc::pin.Lich<dyn Trait> to the Soul with soul.bind::<dyn Trait>() (where Trait is a trait implemented by T).Lich in a lifetime-extended context (such as crossing a std::thread::spawn 'static boundary or storing it in a static variable).Liches before dropping the Soul.Soul will block the thread until all Liches are dropped, potentially creating a deadlock condition (in the name of memory safety).Since this library makes use of some unsafe code, all tests are run with miri to try to catch any unsoundness.
This library supports #[no_std] (use default-features = false in your 'Cargo.toml').
examples/thread_spawn_bridge.rs
/// Trivially reimplement [`thread::scope`] in a more powerful way.
///
/// Contrary to other `scope` solutions, here, the captured reference can be
/// returned (as a [`Soul<T>`]) while the threads continue to execute.
#[cfg(feature = "shroud")]
pub mod thread_spawn_bridge {
use core::{num::NonZeroUsize, pin::Pin};
use phylactery::Soul;
use std::thread;
pub fn broadcast<F: Fn(usize) + Send + Sync>(
parallelism: NonZeroUsize,
function: F,
) -> Pin<Box<Soul<F>>> {
// Pin the `Soul` to the heap to be able to return it.
let soul = Box::pin(Soul::new(function));
// Spawn a bunch of threads that will all call `F`.
for index in 0..parallelism.get() {
// `Soul::bind` requires a pinning.
let lich = soul.as_ref().bind::<dyn Fn(usize) + Send + Sync>();
// The non-static function `F` crosses a `'static` boundary protected by the
// `Lich` and is called on another thread. `Send/Sync` requirements still apply.
thread::spawn(move || lich(index));
}
// The `Soul` continues to track the captured `F` and will guarantee that it
// becomes inaccessible when it itself drops.
//
// If a `Lich` bound to this `Soul` still lives at the time of drop,
// `<Soul as Drop>::drop` will block the current thread until all `Lich`es are
// dropped.
soul
}
}
fn main() {
#[cfg(feature = "shroud")]
thread_spawn_bridge::broadcast(
std::thread::available_parallelism().unwrap_or(core::num::NonZeroUsize::MIN),
&|index| println!("{index}"),
);
}
examples/scoped_static_logger.rs
/// Implements a thread local scoped logger available from anywhere that can
/// borrow values that live on the stack.
#[cfg(feature = "shroud")]
pub mod scoped_static_logger {
use core::{cell::RefCell, fmt::Display, pin::pin};
use phylactery::{Lich, Soul, shroud};
// Use the convenience macro to automatically implement the required `Shroud`
// trait for all `T: Log`.
#[shroud]
pub trait Log {
fn parent(&self) -> Option<&dyn Log>;
fn prefix(&self) -> &str;
fn format(&self) -> &str;
fn arguments(&self) -> &[&dyn Display];
}
pub struct Logger<'a> {
parent: Option<&'a dyn Log>,
prefix: &'a str,
format: &'a str,
arguments: &'a [&'a dyn Display],
}
impl Log for Logger<'_> {
fn parent(&self) -> Option<&dyn Log> {
self.parent
}
fn prefix(&self) -> &str {
self.prefix
}
fn format(&self) -> &str {
self.format
}
fn arguments(&self) -> &[&dyn Display] {
self.arguments
}
}
// This thread local storage allows preserving this thread's call stack while
// being able to log from anywhere without the need to pass a logger around.
//
// Note that the `Lich<dyn Log>` can have the `'static` lifetime.
thread_local! {
static LOGGER: RefCell<Option<Lich<dyn Log>>> = RefCell::default();
}
pub fn scope<T: Display, F: FnOnce(&T)>(prefix: &str, argument: &T, function: F) {
let parent = LOGGER.take();
{
// This `Logger` captures some references that live on the stack.
let logger = Logger {
parent: parent.as_deref(),
prefix,
format: "({})",
arguments: &[argument],
};
// The `Soul` must be pinned since `Lich`es will refer to its memory.
let soul = pin!(Soul::new(logger));
// The `Lich` is bound to the `Soul` as a `dyn Trait` wrapper.
let lich = soul.as_ref().bind::<dyn Log>();
// Push this logger as the current scope.
// The non-static `Logger` crosses a `'static` boundary.
LOGGER.set(Some(lich));
// Call the function.
function(argument);
// Pop the logger.
LOGGER.take().expect("`Lich` has been pushed");
// If a `Lich` bound to this `Soul` still lives at the time of drop,
// `<Soul as Drop>::drop` will block the current thread until all
// `Lich`es are dropped.
}
// Put back the old logger.
LOGGER.set(parent);
}
}
fn main() {
#[cfg(feature = "shroud")]
scoped_static_logger::scope("some-prefix", &37, |value| {
assert_eq!(*value, 37);
});
}
See the examples and tests folder for more detailed examples.
phylactery is actively maintained and pull requests are welcome.phylactery was useful to you, please consider leaving a star!