Add an allow-by default lint for statics that could be constants

this would be similar to the dead_code lint and the unused mut lint, but for private static items that could be const items.

as i understand it, there are a few downsides to statics, such as not being able to perform constant folding or constant deduplication.

the advantages of statics is you can take their address, they can reference other constants, and they can be mutable.

however, if you never use these features of a static, you are only getting the downside, with no upside. larger codebases may wish to avoid this.

this would only apply to items not exported from the crate, as public items may need to be statics so that other crates can use them as such, similarly to how public items will never be considered dead code.

something like warn(static_could_be_const) would be useful.

1 Like

Another advantage of static is that it is only computed once, not on every use.

3 Likes

Big statics are often intentional to avoid duplication, even if they'd work entirely fine as constants, for example.

I don't think this is a good rustc lint. As allow-by-default, the people who would most benefit from it wouldn't know about it to turn it on in the first place. And checking exactly whether it's a good idea to have it a static feels like the kind of unreliable or incredibly slow (because it'd look at all the code in the whole crate) check that we wouldn't really want, especially if the only recourse is to allow it.

Do you have some examples from the wild of things that you think are worth linting? Maybe there's something more specific where it would ok to enable it at warn-by-default, which would make it way more acceptable.

(Alternatively, maybe it'd be better as a R-A hint or clippy "did you know you could do this a different way?" idea...)

2 Likes

i don't think it would have to be any slower than dead code checking, although i did not know rustc duplicates large constants like that, that's a usecase i wasn't aware of, and one that's less quantifyable.

There's no point deduplicating small types like integers, so the lint could complain about values that are same size or smaller than a pointer to the static.

4 Likes

note that pointer size is platform dependent. i don't think there's any lints that give a different output depending on target platform?

1 Like

That's an implementation detail. It could be $bikeshed-bits for all platforms.

  1. this is the rust internals forum. where is one to discuss implementation details if not here?
  2. it changes things significantly. taking the address of an i32 would waste space on a 64 bit target, but save space on 16 bit targets.

There are no significant 16-bit Rust targets, and writing for such platforms is a completely different style of programming. Many Rust APIs and lints don't make sense there. The lint could be disabled there entirely. There's no rule saying lints can't be platform specific.

There's a cost of extra CPU instructions that load the value. There's cost of missed optimizations from it being a run-time indirect value, and not a compile time constant.

1 Like

the main usecase i can think of for &u32 on an embedded target is some sort of tree/graph with each node labeled with a set of bitfields that are likely to be duplicated. using an arena allocator for the graph would handle any possible difficulties with freeing memory, and for a graph with a lot of nodes, those downsides may be worth it in order to save memory.

there's exactly one tier 3 16 bit target: msp430-none-elf

Note, though, that there's also a speed tradeoff - last time I measured on x86-64, by-value was better for anything up to one cache line (64 bytes) in size, and better for two cache lines if cache line aligned.

As a result, I'd suggest that the lint remains useful even if it suggests changes that would make your binary larger on some platforms; it could lint on statics less than or equal to 64 bytes, and be a useful hint.