This won't prevent recursive functions from borrowing it twice. And just disallowing recursive functions or somehow detecting them won't be enough because you can recurse indirectly by using dynamic dispatch.
For a certain class of programs â likely with significant if not total overlap with those that benefit most from the use of such globalization â it can be a reasonable restriction to constrain control flow entirely to a fully statically known call graph.
This is a huge restriction that essentially means you're unable to use general-purpose libraries. I don't think such really has any place in off the shelf Rust. The use case does exist, though.
#[non_reentrant]
functions (NRFs) cannot be recursive. that's kinda the whole point.
assuming by "dynamic dispatch" you mean function pointers (since this attribute wouldn't make sense to put on trait methods), there's a few possible ways to deal with that:
- NRFs could always have a static mutex to enforce the "only called once" invariant, panicing if the mutex cannot be aquired. this has a runtime cost, but it makes a lot of things easier[1], and the overhead would be low enough to not matter for functions like
main
, since they're only called once - forbid taking a function pointer to a NRF.
- use the panic check described above, but only when called dynamically[2]
- allow taking a function pointer, but only if that function pointer is marked
unsafe
.
if you're talking about recursing via something like a dynamic symbol table, that's already unsafe
.
You've now weakened the concept of the OP so far that it's not really useful anymore, though. Was not the point to transform code working with &[mut] GlobalData
into using a GLOBAL_DATA
static place directly? Just turning a function's stack locals into statics without any sort of sharing isn't particularly beneficial, and in fact is quite likely to be detrimental due to cache locality effects.
Also, you need to either forbid NRF calling non-NRF or non-NRF calling NRF, as otherwise the non-NRF can be used to re-entrantly call the NRF. "Surprise" reentrancy is possible for basically any code because of global hooks like the panic hook.
yes, that is still the end goal, but recent posts have been mostly focused on what might be possible with a minimal non_reentrant
attribute.
this is true for most processors, however, many small microcontrollers lack a cache, so even a partial globalization may be beneficial.
Given this is usually almost trivial to do safely on a per-pertinent-local basis[1] you must be thinking of something more thorough. Doing so as a compiler feature could put every stack local in static memory with one atomic per function, but if you're in a situation where stack memory is precious enough to want this I fail to see how static overhead per function is preferable, especially since it's going to get in the way of optimizing out function frames[2].
Won't work, since it permits a statically known call to be reentrant with a dynamic call.
This is functionally no different from just writing unsafe fn
yourself. For the scope where NRF are possible without dynamic checks â main
and directly invoked NRF without any intervening non-NRF frames â this seems entirely reasonable. More functional, even, as it won't prevent you from using closure-based APIs.
âŠī¸static DATA: Mutex<Data> = Mutex::new(Data::new()); let data = &mut *DATA .try_lock() .expect("function should be non-reentrant") .expect("function should not panic");
To avoid this, you'd need to implement the hoisting late in the backend (i.e. LLVM) after optimizations rather than as part of Rust. Otherwise you're going to hit the same issues as async does except worse, depending on how eagerly you move data to static locations. âŠī¸
i mean, the main difference of having it as an attribute is that it would be known to the compiler, and can be used for optimization.
sorry, i really don't understand the correlation here. surely not having local variables means you can skip the prelude in more situations, not less?
there's one difference, which is that you can use unsafe operations inside unsafe functions, at least in rust 2021.
also, an unsafe fn
doesn't tell the compiler "this will never be called reentrantly".
This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.