Global Registration (a kind of pre-rfc)

The relevant one being primarily that if you don't use some feature you don't have to pay runtime cost for it. C++ RTTI isn't great on the test because the object model and separate compilation and mean that every object must carry around the RTTI metadata, whereas Rust's trait objects only create that metadata as needed. Unwinding is a contentious pass only because platform ABIs already bake in the support for runtime assisted side channel unwinding.

But critically, static registration has no runtime support cost if you don't use it. Even if the implementation that uses life-before-main to link together different compilation units' entries is adopted, if a compilation unit doesn't register anything, no support code is run.

And it's strictly speaking incorrect to say Rust has no life before main. There's no extensible life before main, but there is some setup code done by the standard library runtime before calling into main. Fixing up some what are effectively relocations during runtime initialization when getting the linker to do so is impractical fits right into the type of things already done during startup.

7 Likes

Strictly speaking, a tomato is botanically classified as a fruit. However, it is not typically considered to be a suitable ingredient for a fruit salad. Additionally, it may not be a relevant point to bring up in a discussion about food preparation.

Humans, unlike computers, rely on context when communicating. Being overly pedantic in this manner may make you appear difficult to converse with.

No one is objecting to the very spealised code in the standard library for calling main. This is emphatically not the topic of discussion here. No "life before main" means precisely that there is only one explicit entry point. That is the definition of the term. Trying to argue about internal implementation details of the compiler are not relevant to this invariant of the Rust language.

The reason I mention it is that the maximum extent of life before main that would potentially be used to make distributed slices work across dynamic libraries is tiny. There are no additional entry points into user defined code; the Rust runtime only needs to adjust up to one pointer per dynamic library loaded. Furthermore, this kind of pointer fixups are the exact thing which the dynamic loader is already doing; while utilizing the existing "on load" callback is the most straightforward way to accomplish the fixup we would need, it's been mentioned that some dynamic loaders could potentially be convinced to do it for us, eliminating the need to do the fixup manually during the "life before main".

And even then, the general consensus of the thread has been that an initial implementation would only handle linking within a static bundle, i.e. not use any life before main. We want to preserve the ability for the API to work across dynamic libraries, but there's decent possibility that it won't need to.

This isn't even a poor man's type introspection — it's an entirely orthogonal feature. Introspection allows you to ask questions about a type once you know it exists, and to avoid the very size cost you're alluding to, it wouldn't be possible to iterate over type information without explicitly collecting a list of candidates, such as by utilizing this feature to do so in a distributed manner.

Doing the collection work at build time when you ask for it to be done is exactly the zero overhead solution that aligns with Rust's principles. With dylibs, you've chosen to defer determination of whether you'll look at the built list until runtime, so you couldn't do better than what it's proposed the compiler could do, thus it's still "zero cost."

13 Likes

i would very much like if this feature didn't make reproducible builds impossible. although i suppose if you seed the RNG with something like the metadata hash of the crate it would still be reproducible and deterministic..

2 Likes

Question: Does this hold for wasm? (Note: I have no idea, but often Wasm is a good edge case )

What would suite my use cases would be to introduce a new kind of attribute like macro which could be placed on any type or trait and which gives the fully qualified name of a static container to place it in as well as an optional key, if the container were a hashmap. Everything can then be type-checked so the item and the container types match. And usage of the container itself is currently just usage of a normal container. It’s more the remote assembly part that is inconvenient right now, but also to be able the explicitly control what gets assembled to what container. I’d expect this to be run at compile time. In my case the container is in a library crate and the handlers (registry items) are scattered across the application binary.

Wasm doesn't have a standard dynamic linker. It barely has a convention for static linking. So whether it holds depends on what linker conventions you're using.

The difficulty is asking when exactly is this compile time. If rustc assembles the final statically linked binary, then it can assemble the collection at that point — this is essentially how #[global_allocator] and #[test] work today. The RFC also only proposes to handle the static linking case initially. Dynamic linking is only discussed for the purpose of forward compatibility.

It's when dynamic linking gets involved that things get interested. Fundamentally, global registration that crosses dynamic linking requires that some linking work happens for the registration at dynamic link time. Whether it occurs via direct instruction to the dynamic linker or runtime support invoked by a linker hook isn't particularly relevant, since the same work is done either way, and it's just a matter of who is responsible for doing the work.

Whatever the standard library provides is extremely unlikely to be more powerful than what inventory or linkme provide. Some lazy initialization may be needed to turn a list of registrations into the usable registry form you want, but that's fine.

2 Likes

Is it possible / feasible to make an std abstraction for dynamic linking?

I don't understand what you're asking. Rust already supports dynamic linking in about as cross-platform a way as it can be.

perhaps they meant an abstraction over dlopen or linker scripts?

1 Like

another usecase is for projects like clippy. currently there needs to be a single function registering all the passes in one file, which is a frequent source of merge conflicts. being able to have a distributed iterator, with each pass adding itself to the iterator, would be a distinct improvement, that way the pass registering function would just have to iterate over this single iterator instead of having to be modified every time a new pass is added.

maybe at first we could consider constraining distributed iterators to a single crate, postponing the issue of dynamic linking.

1 Like

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.