declare_interior_mutable_const triggers on array initializer
Lint name: declare_interior_mutable_const
I tried this code:
let _: [Cell<bool>; 7] = [Cell::new(true); 7];
Since the array initialization syntax requires T: Copy, rustc errors and suggests this fix that works correctly:
const TRUE_CELL: Cell<bool> = Cell::new(true);
let _: [Cell<bool>; 7] = [TRUE_CELL; 7];
To my knowledge this is the best way to initialize an array with a const but !Copy value.
(stackoverflow answer)
However, declare_interior_mutable_const triggers and incorrectly suggests "make this a static item (maybe with lazy_static)", which doesn't actually work at all. The lint documentation claims the code is bad because "Consts are copied everywhere they are referenced, ... , which defeats the whole purpose of using these types in the first place.", but in this case that is the entire reason for using a const item.
The known issues mention "a legacy way to supply an initialized value to downstream static items" as a legit use, but it is not immediately clear if this case is covered by that.
Meta
Rust version (rustc -Vv):
rustc 1.54.0 (a178d0322 2021-07-26)
binary: rustc
commit-hash: a178d0322ce20e33eac124758e837cbd80a6f633
commit-date: 2021-07-26
host: x86_64-unknown-linux-gnu
release: 1.54.0
LLVM version: 12.0.1
Hey, thank you for the report :upside_down_face:. This is actually a difficult case, where this way of initializing the array uses the fact that each const value is copied. This is copying is the exact thing that the lint, tries to warn for as is can be surprising to newcomers if this is not intended.
For cases like this, I would suggest adding a #[allow(clippy::declare_interior_mutable_const)] attribute to the const value as you intentionally use this behavior. Currently, I can sadly not think of a simple way to fix this, as we would need to check that the const value is only used for array initialization. It is possible to check for the usage of a value, but we would need to check the entire crate for global constant values. A good compromise could be to only check if a const value is used to initialize an array if the code is inside a function body. That would limit the scope of the search.
As this is theoretically working as intended, I'll mark this as a possible enhancement of the lint :upside_down_face:
@rustbot label -C-Bug -I-false-positive +C-enhancement
This is copying is the exact thing that the lint, tries to warn for as is can be surprising to newcomers if this is not intended.
Wouldn't that be just as surprising if the const is not interior-mutable?
The example in the documentation also triggers borrow_interior_mutable_const, so it's not clear why this lint is needed in addition. In fact, it seems to me that any attempt to mutate the const requires a shared reference and triggers borrow_interior_mutable_const. If there is no such attempt, the interior mutability is inconsequential.
This is copying is the exact thing that the lint, tries to warn for as is can be surprising to newcomers if this is not intended.
Wouldn't that be just as surprising if the
constis not interior-mutable?
It shouldn't, because you can't modify constant values. Here is an example, what the lint is actually about Playground:
const GLOBAL_CELL: Cell<bool> = Cell::new(false);
fn main() {
GLOBAL_CELL.set(true);
println!("{:?}", GLOBAL_CELL)
}
This will print out "Cell { value: false }" which might be counterintuitive. The lint basically tries to warn if a user tries to use const to create global values, as they are not truly global and instead placed in the code.
The example in the documentation also triggers borrow_interior_mutable_const, so it's not clear why this lint is needed in addition. In fact, it seems to me that any attempt to mutate the
constrequires a shared reference and triggers borrow_interior_mutable_const. If there is no such attempt, the interior mutability is inconsequential.
You're right, those lints have some overlaps. I'm not sure why, my guess is that declare_interior_mutable_const is for constant values in general and borrow_interior_mutable_const is an additional lint in case you use such a const value from an external crate. But I'm not sure on this one. It might also be so that you can allow declare_interior_mutable_const for these cases.
I stumbled upon this issue. It was in code like this:
static REGISTERED_NAMES: [OnceLock<String>; 256] = {
const INIT_HELPER: OnceLock<String> = OnceLock::new();
[INIT_HELPER; 256]
};
This const is the only way to initialize the static that I'm aware of, advertised e.g. in answers to this StackOverflow question.
As of 1.79 you can use inline const blocks (e.g. [const { Cell::new(0) }; 5]).