Suggestion for Globals

I honestly haven’t spent a ton of time thinking about this, so it might be a bad idea; but I was wondering what other people’s opinion would be on adding captures to Rust for the sake of subverting the borrow checker without using “unsafe”.

I feel like it’d make working with globals much less of a pain, and probably be easier to audit than unsafe blocks if you can enforce narrowing captures.

Feeling some pain to use globals is OK.


I admit that the notion of “subverting” the borrow checker without unsafe makes me wary. Could you possibly explain more what you mean by (narrowing) captures? I’m not familiar with the term.

1 Like

By narrowing captures, I mean that any function called from the function that uses the capture can only capture a subset of the previous capture. So something like:

fn sub_a[g2]();
fn sub_b[g3]();

fn do_thing[g1, g2]() {
    sub_a();    // this is okay
    sub_b();    // this is not

The captures document what globals/upvalues the function is messing with; making “unsafe” blocks unnecessary. And the narrowing ensures that the capture is true.

If you’re going to explicitly pass “globals” around… how is that different to just passing parameters?

Also, this would in no way remove the need for unsafe. The problem with globals is that they can be accessed from anywhere, including other threads. Unless you’re suggesting that the presence of a capture of a global prevents other functions from capturing it… but again, that’s not a global, that’s a local variable.

(That said, I entirely support the idea of adding explicit captures to the language, though for entirely unrelated reasons.)

1 Like

You’re not passing globals around. You’re capturing them. They’re basically implicit arguments.

True, they’re not thread-safe. But that’s not what this idea is for. Sometimes, you need to be able to do unsafe things, and this is a way of doing those unsafe things that’s easier to audit.

This completely breaks the point of using Rust to my mind. What use is this? Break the fundamental guarantees of the language to make using Globals more explicit, but, still "unsafe" without requiring the use of "unsafe". I don't get the point.

1 Like

Like I said, the point is auditing. Rust’s “unsafe” block just says, “this code is unsafe!”. It doesn’t tell you “this code is unsafe because it’s mutating a global variable”.

And if you’re dealing with code that has a lot of globals, you now just have “unsafe” sprinkled all over the place, but no understanding of what’s mutating what. The problem isn’t “oh no, I have globals!”; it’s the inability to tell what code is messing with them.

I’ll also add that yes, it is nice to be thread-safe by default. But there’s nothing wrong with being non-thread-safe if you’re explicit about it.

Why not use thread_local to be explicit about singlethreaded-ness?

1 Like

idk. I’ve never used thread_local before. I’m guessing that this means there would be a separate copy of the data for each thread, which just isn’t an option if your globals are things like texture maps, polygon meshes, etc.

Right, but if you’re singlethreaded then this shouldn’t matter? You’d have just the single copy on whatever thread owns and accesses the data, and the API prevents you from leaking it out elsewhere.

1 Like

hmm. not a bad idea…

Obviously this is somewhat a matter of taste, but you could make this even easier to audit by minimizing the surface area of actually borrowing globals, and passing those references to functions that don't touch the globals at all:

fn do_thing() {
    let (a, b) = unsafe { (&mut G1, &mut G2) };

I don't want to unnecessarily derail the thread, but I am curious as to what your unrelated reasons are. :slight_smile:

See Issue #1702. Actually, it’s fairly relevant to the wider thread anyway.