| Crates.io | bitcoinleveldb-util |
| lib.rs | bitcoinleveldb-util |
| version | 0.1.19 |
| created_at | 2023-01-18 04:49:52.42962+00 |
| updated_at | 2025-12-01 16:57:05.880415+00 |
| description | Low-level utilities for bitcoin-rs/bitcoinleveldb providing C++-style non-dropping singletons and destructor control via DoNotDestruct and NoDestructor |
| homepage | |
| repository | https://github.com/klebs6/bitcoin-rs |
| max_upload_size | |
| id | 761511 |
| size | 188,765 |
Utility components used by the bitcoin-rs / bitcoinleveldb codebase, focused on precise control over object lifetime and destructor behavior.
This crate is intentionally small and low-level. It exports primitives that help emulate C++-style "never call the destructor" patterns in Rust, primarily for function-local statics and other long-lived objects that must not run cleanup code on shutdown.
The key types provided are:
DoNotDestruct – A test/utility struct whose destructor must never run. If its Drop implementation is invoked, the process will immediately abort.NoDestructor<T> – A wrapper that stores a fully-initialized instance of T inside MaybeUninit<T> and never drops it. This is particularly useful for singletons, function-local statics, or global state that must remain valid for the lifetime of the process and does not require orderly teardown.NoDestructorTest – A trivial grouping struct, mainly used for tests.The semantics are deliberately strict and closer to systems-programming idioms, trading graceful shutdown for predictable performance and the avoidance of complex destructor interactions at process exit.
DoNotDestructuse bitcoinleveldb_util::DoNotDestruct;
// Panics? No. If this is dropped, the process aborts.
let obj = DoNotDestruct::new(0xdead_beefu32, 0x0123_4567_89abu64);
// Read and write fields via generated getters/setters (from `getset`):
let a = *obj.a();
let b = *obj.b();
obj.set_a(1);
obj.set_b(2);
impl Drop for DoNotDestruct {
fn drop(&mut self) {
error!("DoNotDestruct destructor called! Aborting...");
std::process::abort();
}
}
DoNotDestruct instance (including unwinding across stack frames) will unconditionally call process::abort().impl DoNotDestruct {
pub fn new(a: u32, b: u64) -> Self {
info!("Constructing DoNotDestruct with a=0x{:x}, b=0x{:x}", a, b);
Self { a, b }
}
}
Logging macros (info!, error!) are used from the standard Rust logging ecosystem (e.g., log crate + chosen logger backend). To see log output, configure a logger in your binary (e.g. env_logger, tracing_subscriber, etc.).
NoDestructor<T>NoDestructor<T> stores an instance of T in MaybeUninit<T>, ensuring that Rust never runs its destructor. This is the Rust analogue of C++ code that uses aligned storage plus placement-new and deliberately omits destructor calls.
use core::mem::MaybeUninit;
#[derive(Debug)]
pub struct NoDestructor<InstanceType> {
instance_storage: MaybeUninit<InstanceType>,
}
impl<InstanceType> NoDestructor<InstanceType> {
/// Fully constructs the instance and stores it in `MaybeUninit`,
/// then **never** runs the destructor.
pub fn new(instance: InstanceType) -> Self {
info!("NoDestructor::new invoked");
let storage = MaybeUninit::new(instance);
Self { instance_storage: storage }
}
}
Usage:
use bitcoinleveldb_util::NoDestructor;
struct Cache { /* fields omitted */ }
fn global_cache() -> &'static mut Cache {
use core::mem::MaybeUninit;
use core::sync::atomic::{AtomicBool, Ordering};
static mut STORAGE: MaybeUninit<NoDestructor<Cache>> = MaybeUninit::uninit();
static INIT: AtomicBool = AtomicBool::new(false);
// Very low-level: caller must ensure this is called in a single-threaded
// initialization phase or otherwise synchronize access.
if !INIT.load(Ordering::Acquire) {
let cache = Cache { /* ... */ };
unsafe {
STORAGE.write(NoDestructor::new(cache));
}
INIT.store(true, Ordering::Release);
}
unsafe {
// `get()` yields *mut Cache; we convert to &'static mut Cache.
&mut *STORAGE.assume_init_ref().get()
}
}
impl<InstanceType> NoDestructor<InstanceType> {
/// Returns a mutable raw pointer to the stored instance.
/// Lifetime and aliasing are **not** tracked at the type level.
pub fn get(&self) -> *mut InstanceType {
trace!("NoDestructor::get returning pointer to the stored instance");
self.instance_storage.as_ptr() as *mut InstanceType
}
}
Important properties:
InstanceType is never called.get() returns a *mut InstanceType even though &self is shared; this mirrors some C++ patterns but deviates from idiomatic Rust. The burden of ensuring sound aliasing rules and exclusive mutation lies with the caller.InstanceType must be fully initialized by the time you call get() (which is guaranteed for instances constructed via NoDestructor::new).While NoDestructor<T> itself is safe to construct and use at the type level, it allows patterns where you can trivially violate Rust's higher-level aliasing disciplines if misused. You must:
T exists at any time.T's invariants would not permit it.T (file descriptors, sockets, memory buffers, lock guards, etc.) will never be released by RAII; they must either be leak-tolerant or managed manually.This design is appropriate for process-long singletons where resource reclamation is unnecessary or undesirable.
NoDestructorTest#[derive(Debug, Getters, Setters, Builder)]
pub struct NoDestructorTest {}
A minimal struct serving as a convenient grouping for tests. Its presence indicates that the crate is used to validate and regression-test the above lifetime primitives.
The patterns implemented in this crate arise from a specific class of problems in systems and database code:
C++ often uses patterns like:
static SomeType instance; with non-trivial destructors, whose order-of-destruction across translation units is notoriously complex.std::aligned_storage + placement-new, followed by intentionally omitting ~SomeType() calls.NoDestructor<T> mirrors the second approach using MaybeUninit<T> in Rust. DoNotDestruct serves as an explicit sentinel type that aborts if any destructor tries to execute, making tests detect any unintentional destruction.
The snippets use logging macros such as info!, error!, and trace!. At integration time, you will typically:
[dependencies]
log = "0.4"
# and a backend of your choice, e.g.
env_logger = "0.11"
Initialize logging in your binary:
fn main() {
env_logger::init();
// use bitcoinleveldb-util types here
}
This allows you to observe constructor calls and any unexpected destructor activity during testing.
Avoid these primitives if:
lazy_static, once_cell, or std::sync::OnceLock without destructors that cause problems.Use this crate when you:
bitcoinleveldb-utilThis crate is a component in the broader bitcoin-rs ecosystem. Consult that repository for examples of how these primitives are used in practice inside a leveldb-like storage engine and related Bitcoin infrastructure.