Statics inside <…> and fixing unsoundness bugs
Motivation
Sometimes you want to fix an unsoundness bug that affects you directly because you do some things that are more likely to hit those unsoundness bugs.
Sometimes that fix doesn’t quite work on windows, so you need a workaround.
Syntax
All of the following involve statics inside <…>:
fn foo<static BAR: usize = 0>() -> usize {
BAR
}
fn bar<static BAZ: AtomicUsize = ATOMIC_USIZE_INIT>() -> usize {
BAZ.fetch_add(1, SeqCst)
}
static GLOBAL_TID_REGISTRY: Mutex<HashMap<SlowTypeId, TypeId>> = ...;
static NEXT_TID: AtomicUsize = ...;
fn get_type_id<T: 'static + SlowTypeIdentifiable, static TID: TypeIdStore<T> = TYPE_ID_STORE_INIT>() -> TypeId {
TID.call_once(|| {
let slow_typeid = T::get_slow_typeid();
let mut map = GLOBAL_TID_REGISTRY.lock().expect();
TypeId { id: map.get_entry(slow_typeid).or_insert_with(|| NEXT_TID.fetch_add(1, SeqCst) }
})
}
The first is the most basic one, and can be invoked with any static usize:
static MY_STATIC: usize = 3;
assert_eq!(foo::<MY_STATIC>(), 3);
assert_eq!(foo(), 0);
The second is slightly more involved, and shows that each call location creates a new such static:
fn fuz(cmp: usize, special: (bool, usize)) {
assert_eq!(bar(), cmp);
if special.0 {
assert_eq!(bar(), cmp);
assert_eq!(bar(), cmp);
}
}
fuz(0, (true, 0));
fuz(1, (false, 0));
fuz(2, (true, 1));
The third finally shows that the static’s type may depend on a generic type, and is, in fact, quite close to what I propose for fixing the unsoundness bug in a backwards compatible way.
However, when I say “backwards compatible way”, you’re probably thinking, “how?”. After all, wouldn’t a generic caller also have to specify such statics? For example, if Any’s downcast* methods had the generic signature <T, static TID: ...>
, wouldn’t all callers that take a generic T also have to take a static generic TID?
Well, this is where this proposal gets a little shitty… If the static depends on a generic type, and the caller has a generic type, we automatically propagate the static to the caller’s generic signature, without the caller having to add it manually. Additionally, because of this, and for ergonomic reasons, calling such a function with an explicit type doesn’t require you to also specify the statics - so you can still call it as downcast_ref::<T>
rather than downcast_ref::<T, MY_STATIC>
.
Note: Everything in these statics needs to be public. That is, the type of the static, and the initializer. (<static NAME: Type = INITIALIZER>
)
Alternatives
- Do nothing.
- Deprecate Any/TypeId entirely, and remove it entirely in the future.
- Drop Windows support (Windows 10 has WSL, so we get to use a POSIXy dynamic linker) and add generic statics (
static FOO<T>: Whatever<T> = whatever
). - Do this, but hide the implementation details so I’m still forced to use macros in
eventbus
crate. - Pass down a
&'static foo
- this doesn’t work because a) Box::leak and b) breaks backwards compatibility. - Make Any slow af and let the users of Any deal with it.
Unresolved Questions
- Some ppl don’t like this syntax because it’s not clear that the caller automatically generates statics, rather than those statics being part of the callee. So, the syntax itself is an unresolved question. What should the syntax look like?