Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Tracking Issue: What do you use static mut for? #1

Open
jamesmunns opened this issue Dec 6, 2023 · 8 comments
Open

Tracking Issue: What do you use static mut for? #1

jamesmunns opened this issue Dec 6, 2023 · 8 comments

Comments

@jamesmunns
Copy link
Owner

If you have an example of why you use/need static mut, particularly in an embedded context, I'm interested in hearing why! This will help me provide safer alternatives in grounded in the future.

If you can share a link to code, or a snippet inline or on the Rust Playground, that would help out a ton.

@spcan
Copy link

spcan commented Dec 6, 2023

Coming here from the Matrix chat.

For me there are three main uses of static muts, although mostly they have to do with uninit memory and hardware restricted choices, which do not map well to Rust's model.

  1. Mapping hardware regions. Some chips have small buffers that can be difficult to handle with OnceCell or similar abstractions. As I'm mainly developing on the driver side, I mainly use statics to map to these regions and offer to the user only a safe view into the hardware buffer. An example of this would be DMA descriptor regions, SDMMC hardware buffers (usually 512 bytes) and similar. This also maps well to external memories (e.g. STM32 external SDRAM memory access region).
  2. On the driver side when I need statically allocated non-init memory for descriptors or configuration. Mainly because the API to access uninitialized memory is cumbersome (and I'm too lazy to be unwraping and matching everywhere) I simply create a const fn method I call empty which fills the structs with 0s and then link those statics in the .bss or .uninit section (depends on if the initial state needs to be 0s). This skips initialization and zeroing of memory that does not need to be initialized, improving performance somewhat.
  3. When I need uninitialized memory because I know I'm going to fill it later. For example when I need matrices to do quick math on a coprocessor, I just pass the pointers to the matrices to the coprocessor and it will fill them on its own. This model does not map well to Rust because I feel there are no good ways of telling it, 'Yes, this is undefined behaviour, but I know the state this section of memory will be when I use it'. Also sometimes I have a limited number of matrices I need to process and statically allocating them and then only using pointers/references without checking again is faster than allocating, initializing, checking then using.

Hope it helps

@diondokter
Copy link

I've use them once in a while if I want to prove something to me. For example, I'm inspecting some generated assembly and I need a write to not be optimized away. A static mut is a quick way to get that in place of a let mut.

Another thing I can imagine, but haven't done myself so far, is having to have a global variable for compat with C. Say a C lib has a global extern variable, how would you create that un Rust without static mut?

@jamesmunns
Copy link
Owner Author

jamesmunns commented Dec 6, 2023

@spcan that definitely helps! Grounded already has some tooling for getting a pointer to a static uninit item or array of items, that I believe to be more sound of an abstraction to build on top of (check out GroundedCell and GroundedArrayCell). The API is just a single .get() that returns an *mut T, which is often what you need for DMA, and allows you to "bring your own safety" on top of it if you want to create a reference from it.

I definitely want to add items (or examples) that show how to use it with specifically defined linker sections, in case it needs to be mapped at a specific location.

Additionally:

This model does not map well to Rust because I feel there are no good ways of telling it, 'Yes, this is undefined behaviour, but I know the state this section of memory will be when I use it'

Just nitpicking, the compiler is allowed to assume that undefined behavior never exists, and this can cause it to optimize your program in unexpected ways, including removing whole chunks of it, even if you use unsafe!

Similarly to @diondokter, in theory a static mut should be no more resistant than any other mut variable to being optimized out. Though if it's a single threaded example and you are very careful to not create aliasing references, it shouldn't be immediately UB (just very eas to accidentally cross the line).

Another thing I can imagine, but haven't done myself so far, is having to have a global variable for compat with C. Say a C lib has a global extern variable, how would you create that un Rust without static mut?

From my answer in chat:

The answer is probably It Depends (based on what kinds of guarantees the C program makes). For example, if the C program ever leaves the global in an inconsistent state (say: a bool with "2" in it), then you definitely shouldn't expose it as just a static mut.

however, an UnsafeCell<MaybeUninit> or just UnsafeCell can also be used as a non-mut static.

IMO: basically anywhere you use static mut you should basically be using UnsafeCell or UnsafeCell<MaybeUninit>.

@jamesmunns
Copy link
Owner Author

Also, thank you both for sharing! I do want to share why I want to provide other options, and both of your examples help a ton on what kinds of structures, docs, and examples I should provide!

@thejpster
Copy link

I'd love to get rid of this one: https://github.com/ferrous-systems/rust-exercises/blob/7e6d9e6029ad9a31b0c75d233dda6921cd8421f0/nrf52-code/boards/dongle/src/lib.rs#L294

It's a static mut in a function that can only be called once (it uses Peripherals::take() to ensure this). It has to be static because there are stack-allocated structures elsewhere that want to hold a &Clocks and the only reasonable way to do this was to make it a &'static Clocks.

@jamesmunns
Copy link
Owner Author

@thejpster your example is nearly exactly the use case of StaticCell, widely used in embassy.

I've avoided duplicating it because static cell is already well implemented and widely used.

@thejpster
Copy link

Neat, I'll just use that then.

@NickeZ
Copy link

NickeZ commented Oct 21, 2024

I use static muts for basically all the context in my current project. Quickly summarizing the SDK, you implement callbacks / message handlers that are called from the kernel. Chip is called DA14531. Code is over here.

Looks something like this:

static const struct app_callbacks user_app_callbacks = {
    .app_on_connection                  = user_app_connection,
    .app_on_disconnect                  = user_app_disconnect,
    .app_on_update_params_rejected      = NULL,
    .app_on_update_params_complete      = NULL,
    .app_on_set_dev_config_complete     = default_app_on_set_dev_config_complete,
    .app_on_adv_nonconn_complete        = NULL,
    .app_on_adv_undirect_complete       = user_app_adv_undirect_complete,
    .app_on_adv_direct_complete         = NULL,
    .app_on_db_init_complete            = default_app_on_db_init_complete,
    .app_on_scanning_completed          = NULL,
    .app_on_adv_report_ind              = NULL,
    .app_on_get_dev_name                = default_app_on_get_dev_name,
    .app_on_get_dev_appearance          = default_app_on_get_dev_appearance,
    .app_on_get_dev_slv_pref_params     = default_app_on_get_dev_slv_pref_params,
    .app_on_set_dev_info                = default_app_on_set_dev_info,
    .app_on_data_length_change          = NULL,
    .app_on_update_params_request       = default_app_update_params_request,
    .app_on_generate_static_random_addr = default_app_generate_static_random_addr,
    .app_on_svc_changed_cfg_ind         = NULL,
    .app_on_get_peer_features           = NULL,
#if (BLE_APP_SEC)
    .app_on_pairing_request             = NULL,
    .app_on_tk_exch                     = NULL,
    .app_on_irk_exch                    = NULL,
    .app_on_csrk_exch                   = NULL,
    .app_on_ltk_exch                    = NULL,
    .app_on_pairing_succeeded           = NULL,
    .app_on_encrypt_ind                 = NULL,
    .app_on_encrypt_req_ind             = NULL,
    .app_on_security_req_ind            = NULL,
    .app_on_addr_solved_ind             = NULL,
    .app_on_addr_resolve_failed         = NULL,
#if !defined (__DA14531_01__) && !defined (__DA14535__)
    .app_on_ral_cmp_evt                 = NULL,
    .app_on_ral_size_ind                = NULL,
    .app_on_ral_addr_ind                = NULL,
#endif // not for DA14531-01, DA14535
#endif // (BLE_APP_SEC)
};

https://lpccs-docs.renesas.com/UM-B-119_DA14585-DA14531_SW_Platform_Reference/User_guides/User_guides.html#ble-event-callbacks

ps. Perhaps there is a better way (with less statics) to integrate with the SDK I'm using.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants