Skip to content

Proposal: add a #[disjointed_static] attribute #81

@CAD97

Description

@CAD97

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions