-
-
Notifications
You must be signed in to change notification settings - Fork 44
Description
Declaring a static
to be defined downstream seems right in the "linker shenanigans" alley of the linkme crate, and can be made (mostly) typesafe using essentially the same linker techniques as with #[distributed_slice]
.
I'm most interested in the static RESOURCE: dyn Trait
case (c.f. #[distributed_slice]
using static SLICE: [T]
), but any concrete type that can be put in a static
could of course be used.
If you agree that this would be a reasonable fit for the linkme crate to provide, I'm willing to work on getting a working implementation.
Example
// upstream
pub trait Log {
fn record(&self, entry: &Record<'_>);
fn flush(&self);
}
#[global_resource]
pub static GLOBAL_LOGGER: dyn Log;
// downstream
#[global_resource(GLOBAL_LOGGER)]
static LOGGER: MyLogger = MyLogger::new();
Implementation strategy
There are two main potential implementation strategies. The first is to use a DistributedSlice<&dyn Log>
with a runtime assert_eq!(len(), 1)
(like the distributed slice dupcheck). Reusing the #[distibuted_slice]
machinery is a reason for putting this in linkme.
The second one is to use a single symbol instead, e.g.
// upstream
static GLOBAL_LOGGER: GlobalResource<dyn Log> = {
extern {
#[link_name = "__linkme.resource.GLOBAL_LOGGER"]
static LINKME: &dyn Log;
}
GlobalResource::private_new(LINKME, todo!())
};
// downstream
static LOGGER: MyLogger = MyLogger::new();
const _: () = {
#[link_name = "__linkme.resource.GLOBAL_LOGGER"]
static LINKME: &dyn Log = &LOGGER;
};
(attributes aren't exactly correct). An advantage of this approach is that it (potentially) works under Miri, but a big disadvantage is that it relies on linker errors for missing or duplicated definition of the linked symbol. Using weak symbols might be able to avoid this.
As a variation, instead of linking static: &dyn Log
, link fn() -> &dyn Log
instead. That could be more well supported and allows the downstream to more directly do lazy initialization, which could be an upside or drawback, depending on what you want.
In either case, the ability to provide a default implementation is useful (e.g. how #[global_allocator]
defaults to as-if Global
is used) and would theoretically be possible: a strongly ordered slice member for the former implementation strategy, and weak symbol definition for the latter.