How to deal with global world state?


I'm working on operating system implementation in Rust and I'm figuring out how to approach different problems.

Right now I'm looking for a way to deal with global "World State". The whole point of OS kernel is to manage global state, while it must be fast and safe.

I don't want to have global variables, as they make mocking and testing harder. I'd like to be able to test everything like it was pure and functional as much as I can.
One naive approach that I'm thinking about is to pass &mut WorldState all over the code, but it seems tedious.

I'd be thankful for ideas, and pointers how to approach this. I know that game developers have similiar problems: games are full of global state.

In ecs-rs I do what you describe and just pass DataHelper all over the place.

Surprisingly it's not that bad. Although I can imagine it being a bit of a nightmare for a non-component-system setup.

As for an OS kernel, it really depends on your design. I'm not too experienced in this area myself, but I think what you've described would work okay. But again, depends on your design.

In my game, I keep global state in the main loop function, and pass down references. But admittedly the game is not very complex yet.

In my web application engine, for configuration data (read from a toml file and command line args) I went from passing a variable everywhere, to using lazy_static to build a global CONFIG. This is not mutable though. Recently I went back to passing the variables around again.

I still sometimes run into ownership issues passing references to data, or mulitple references to parts of data, etc. But I've been able to work through all of those issues so far.

If you hit an issue too hard to work around, there's always unsafe (though what exactly you might do in the unsafe block escapes me at present)

As regard to global world state, I wonder if there is some locking facility provided by Rust.(Or automatically lock on borrowing?)

A kernel definititely needs some sort of global state. At least you need to access page table registers, io-ports, interrupt settings and you have just one scheduler although it can have shared components.
For everything else a kernel normally needs to be small and so it is quite easy (but not neccessarily fast) to just pass references around.

I played a littlebit with writing a (gui data-)binding framework for rust with automatic loop detection and automatic dependency detection. For some of this I needed a nonlocal state without passing it through the function chain. It turned out thread local storage is quite useful for it. But then it will not work out of the box for a barbone rust kernel! Also it might be a little bit to heavy for a os primitiv.
You might want to put up a global data handle with synchronisation primitives and then implement some static functions witch internall use the globals.
Or have some local objects witch internally use the global objects so that you don't have to route all data from the top through the function chain but just from some well defined points.

You gave me an idea. On aarch64 there's a cpu regiser that is supposed to be used in application specific way. I guess I could use it to store pointer to global state, and fake it mockups. On other platforms I can use some other similar mechinism maybe...

Register in application specific way may means compiler is free to use it. I think it dangerous to mix this low level thing with high level language like Rust.

You're right. Oh well.