Non-generic statics in generic contexts

Change localized statics in generics to be unique to the complete codegen for a generic.

At the moment the following code will only have one static for all the instantions of the function.

fn foo<T>() {
    static local_foo : u32 = 0;
}

This is not what I would expect. I would expect foo::<u8> and foo::<u16> would generate different local_foo's. This would allow for the case of Self to be used in a static call as a generic argument like so.

struct Foo;
impl Foo {
    fn foo() -> &'static str {
        std::any::type_name::<Self>()
    }
}

Well, this would be a breaking change for existing code, so it would need a transition plan or an opt-in.

Note that right now it's the rule that -- other than visibility -- there's no difference for where you put an item (fn/struct/impl/const/static/...). If it's inside or outside a fn, it works exactly the same. That's why you can't do something like fn Foo<T>() { static FOO: Option<T> = None; } either.

3 Likes

what would it break?

Your example calling type_name already compiles today; I don't see how that's related to static codegen.

It would change the behavior of mutable global variables that happen to be scoped within a generic function.

For example: https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=0a52b0db5a674761927d07348e1fd9fa

use std::sync::atomic::{AtomicUsize, Ordering};

fn instantiate<T: Default>() -> (T, usize) {
    static COUNTER: AtomicUsize = AtomicUsize::new(0); 
    
    (Default::default(), COUNTER.fetch_add(1, Ordering::Relaxed))
}

fn main(){
    dbg!(instantiate::<u8>());
    dbg!(instantiate::<u16>());
    dbg!(instantiate::<String>());
}

this currently prints

[src/main.rs:10] instantiate::<u8>() = (
    0,
    0,
)
[src/main.rs:11] instantiate::<u16>() = (
    0,
    1,
)
[src/main.rs:12] instantiate::<String>() = (
    "",
    2,
)

if that was a different static for each different type argument, this would print zero for all the function calls.

6 Likes

Another thing is that generic functions can ne duplicated (and meed to if they are instantiated within multiple crates), but statics are defined as having a single address, which would be impossible for generic statics, even for a single instantiation. If I were to use the same A<u8> static within two crates, both would need to define a symbol for it. If this symbol were to be public, it would result in a conflicting definition and thus linker error, if it were local, you would get two copies.

I agree that we shouldn't change the existing behavior, as it would break existing semantics. I do think, though, we should have a simple syntax for "give me a different one of these for each generic instantiation", or "... for each generic instantiation of this subset of parameters". (Even if the type of the static isn't actually generic.)

I think that should only be allowed if there is no interior mutability as rustc is free to instantiate generics multiple times. It has to cross-crate and often does inside the same crate for each cgu that uses it. Currently cgu splitting splits at the module boundary, but this is not guaranteed. Allowing interior mutability with generic statics would make it way too easy to accidentally depend on a specific cgu splitting method IMHO.

In my honest opinion this breaks encapsulation. I would expect it to print 0 for each. I would be willing to entertain like a keyword or a context dependent modifier.

i had the wrong example. I meant to cache it in a static variable which works if I do Foo but not if I use Self as shown here Rust Playground

Note that for types without internal mutability this can already be achieved by using associated constants and static rvalue promotion Rust Playground

2 Likes

that seems like a lot boiler plate for something i would expect to work.

I tried if a const {} block would work, but it is both not allowed and ices:

#![feature(const_type_name, inline_const)]
struct Foo;

impl Foo {
    fn get_name()->&'static str {
        const { std::any::type_name::<T>() }
    }
}
error[E0412]: cannot find type `T` in this scope
 --> src/lib.rs:7:39
  |
5 | impl Foo {
  |     - help: you might be missing a type parameter: `<T>`
6 |     fn get_name()->&'static str {
7 |         const { std::any::type_name::<T>() }
  |                                       ^ not found in this scope

warning: the feature `inline_const` is incomplete and may not be safe to use and/or cause compiler crashes
 --> src/lib.rs:1:12
  |
1 | #![feature(inline_const)]
  |            ^^^^^^^^^^^^
  |
  = note: `#[warn(incomplete_features)]` on by default
  = note: see issue #76001 <https://github.com/rust-lang/rust/issues/76001> for more information

error: internal compiler error: compiler/rustc_infer/src/infer/lexical_region_resolve/mod.rs:374:17: cannot relate region: LUB(ReStatic, ReErased)

thread 'rustc' panicked at 'Box<dyn Any>', compiler/rustc_errors/src/lib.rs:1147:9
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

note: the compiler unexpectedly panicked. this is a bug.

note: we would appreciate a bug report: https://github.com/rust-lang/rust/issues/new?labels=C-bug%2C+I-ICE%2C+T-compiler&template=ice.md

note: rustc 1.58.0-nightly (46b8e7488 2021-11-07) running on x86_64-unknown-linux-gnu

note: compiler flags: -C embed-bitcode=no -C codegen-units=1 -C debuginfo=2 --crate-type lib

note: some of the compiler flags provided by cargo are hidden

query stack during panic:
#0 [typeck] type-checking `<impl at src/lib.rs:5:1: 9:2>::get_name`
#1 [typeck_item_bodies] type-checking all item bodies
end of query stack
For more information about this error, try `rustc --explain E0412`.
warning: `playground` (lib) generated 1 warning
error: could not compile `playground` due to previous error; 1 warning emitted

Edit:

Needed to do the following I think, but I am getting a timeout. Not sure if it is a playground issue or an infinite loop.

#![feature(inline_const)]
#![feature(const_type_name)]
struct Foo;

impl Foo {
    fn get_name()->&'static str {
        const { std::any::type_name::<Self>() }
    }
}

This code currently does not compile:

use std::marker::PhantomData;
use std::sync::atomic::{AtomicUsize, Ordering};

struct Foo<T> {
    i: AtomicUsize,
    _erik: PhantomData<T>
}

fn fun<T>() -> usize {
    static LOCAL_FOO: Foo<T> = Foo { i: AtomicUsize::new(0), _erik: PhantomData };
    LOCAL_FOO.i.fetch_add(1, Ordering::Relaxed)
}

fn main() {
    println!("{}", fun::<i32>());
    println!("{}", fun::<u32>());
    println!("{}", fun::<String>());
}

error[E0401]: can't use generic parameters from outer function

If it did compile I would expect it to print 0, 0, 0. I don't necessarily think it should compile (because the difference between Foo<i32> ⇒ all instantiations share a static and Foo<T> ⇒ each generic instantiation gets its own static is a fairly small edit distance for a fairly large effect.). But a noisier syntax would make sense if it happens at all.

AFAIK this is not possible with dylib crates on windows, since when the complete set of instantiations of the static can be known it's no longer possible to merge them.