Why does the borrowing checker not work for static item?

static mut GLOBAL:i32 = 0;
fn main(){
   let m = unsafe{ & mut GLOBAL };
   let m2 = unsafe{ & mut GLOBAL };
   *m = 1;
}

The borrowing checker let the code pass. If replace the GLOBAL with a local variable with keeping other things identical, the classic borrowing checker error will emit, for example

fn main(){
   let mut i:i32 = 0;
   let m = unsafe{ & mut i };
   let m2 = unsafe{ & mut i };
   *m = 1;
}

cannot borrow i as mutable more than once at a time

Does the borrowing checker have a special treatment for the static variables?

The borrow checker can't know whether a static mut is currently borrowed, because any function in any thread could do it.

That is why borrowing a static mut can only be done in an unsafe block: because it has to be your responsibility to "borrow check" the code manually.

Whenever possible, you should use a non-mut static that contains an interior mutable type (e.g. Mutex) instead of static mut, because that avoids the risk of getting the unsafe wrong. It is very easy to make a mistake with static mut.

3 Likes

The borrow checker only allows mutable access to variables owned by your function. Global variables are not owned by the main function, or any other function; they are inherently shared, as any and all functions can access them, and mutability is thus disallowed, unless you use unsafe code and pay attention to ensure that mutual exclusion of mutable access is enforced manually. (As always with unsafe code, never do this without a good reason, such as e. g. measurable performance benefits compared to all safe alternatives.)

And with global variables in particular it's also often a good thing to question why they are necessary for your use case at all; there can exist good reasons for using globar variables, but in case you could just as well work with local variables, possibly a local variable in the main function, possible by passing around references to this variable to various places (assuming the amount it passing references does not get too tedious), such an approach would be generally preferred since it's easier to reason about in detail, both for humans and also for the borrow checker that will allow mutation without problems.

You ask whether there's any "special" treatment of special variables, and the answer is a clear yes, but also, the question is phrased weirdly, as static variables are not some minor variation of local variables, but something conceptually entirely different. Hence, I wouldn't say it's any "special" treatment, and I'd rather say they are treated differently since they inherently are a different thing.

1 Like

Anyway, I know the usage would easily cause UB, however, the question is not about whether it is a sound program or not, or whether it is a good practice. I just asked why the borrowing checker lets the code pass when multiple mutable references to a single variable(the static) exist simultaneously.

Ah, got you.

I believe the answer is simply: &FOO for a static variable static FOO: Foo = …; gives back a &'static Foo reference, and I thus assume for consistency that the mutable statics will du the analogous thing with mutable references, allowing you to allow a &'static mut Foo reference (using unsafe code, of course). This means that the reference lifetime is completely unrestricted and the borrow checker will not be enforcing any restriction.

In this context, adding restrictions to borrows of static mut variables, even though their type is allowed to be &'static mut reference, would presumably be requiring a special case for borrow checking (and perhaps also a false sense of security), and the way it works right now is just normal operation.

1 Like

The short answer to this question is: the borrow checker does not apply the same rules on static items as it would apply to the traditional local variable, is it?

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.