[WIP, Pre-RFC] statics inside <...> and fixing unsoundness bugs

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?
1 Like

You already have two proposals (1, 2) which involve function calls expanding to an expression and the implicit creation of a static (or more).

I wouldn’t mind removing Any completely, though.

Neither of which a) are a pre-RFC b) talk about fixing Any/TypeId.

You aren't proposing this as a serious alternative, are you?

3 Likes

it’s a serious alternative. the question is whether it’s an acceptable alternative.

we do have WSL now, which gives you an native linux-compatible runtime on windows 10. this is a huge improvement over the emulated linux from cygwin and friends.

That’s not a serious alternative.

5 Likes

can we please keep criticism about the rfc and not about whether that’s a serious alternative or not? because I’d be willing to drop winapi on windows, personally, so it’s serious to me.

Allow me to apologize for the harsh tone, but I think it’s merited in this case. Allow me to further point out that I really don’t like Windows’ idiosyncrasies either.

Dropping support for a major operating system, especially one with the majority market share on desktop clients, is comparable to suggesting removing the borrowck because it still has open unsoundness bugs. This is how you drive your language into irrelevance.

It is definitionally unreasonable, and very much a criticism of the RFC, because alternatives are a crucial part of an RFC. It is hard to take an RFC with unironicly ludicrous components seriously.

15 Likes

Rust works just fine on WSL and nothing stops us from bootstrapping WSL from a Windows C program.

Rust supports Windows versions from Vista upwards (I think there was an idea to drop XP support, but atm it’s a Tier 3 platform afaik). WSL does not support the majority of those supported platforms and as such is not a viable solution. To keep your pre-RFC on track, it would probably make sense to remove that alternative and move forward.

3 Likes

WSL is not Windows support. As Microsoft puts it:

Note that, just as on Windows 10, WSL is for running your favorite Linux distros & tools for local interactive use, not for hosting production Linux workloads .

- WSL arrives on Windows Server! - Windows Command Line

Rust already has many organizations shipping applications written or partially written in Rust on Windows. It would be unacceptable to our users to remove Windows support.

7 Likes

How does this interact with function pointers,eg if you tried to do this:

fn foo<static BAR: usize = 0>() -> usize {
    BAR
}
fn bar<static BAZ: AtomicUsize = ATOMIC_USIZE_INIT>() -> usize {
    BAZ.fetch_add(1, SeqCst)
}

fn baz(func:fn()->usize){
    func()
}

baz(foo);
baz(bar);


Do this, but hide the implementation details so I’m still forced to use macros in eventbus crate.

You are saying this like using macros is a bad thing.

This is obviously no better than macros.

2 Likes

This is a lot better than macros:

  • Generic type inference actually works.
  • Cleanly propagates to generic callers.
  • Backwards compatible.
  • Can you even make function pointers to generic functions? I never tried tbh.

Yes, you can have function pointers to monomorphizations of a generic function.

how does that work?

yes, you can have pointers to monomorphizations of these.

  • Generic type inference actually works.
  • Backwards compatible.

How do these not apply to macros?(It's not obvious what you are talking about here.)

  • Cleanly propagates to generic callers.

I would not call propagating statics to the caller clean. At least with macros you can tell something weird might be happening.

Also,as one of the alternatives you should mention passing down a &'static Foo instead of a generic static,and say why that does not work.

1 Like
let x: &Foo = any.downcast_ref();
let x: &Foo = downcast_ref!(any); // doesn't work, no way to get/use &Foo from inside the macro

Your specific macro is wrong.

how? it clearly shows the lack of type inference…

In you macro specifically,not Rust in general.See vec in the standard library.