Rustc: compiler check for "no allocation"?

Is there any compiler directive for

#[this_function_will_not_allocate_memory]
fn foo( ... ) {
...
}

This means the function foo can not do anything that involves allocating memory. Not even a Vec<u32>.push because it might re-allocate space when we hit capacity.

1 Like

You can put the function in a !#[no_std] crate without any allocator. How would you propose to detect which functions allocate, except to declare a function to be allocating? There's no such declaration on Vec::push...

I do not know. I am willing to pay the price of manually annotating every function. (Hopefully won't have to annotate anonymous |args| { body } functions too).

It seems a compiler that error on the conservative side can say: a function tagged with "does not allocate" can only call functions that have also been tagged with "does not allocate".

You could just use a deny lint and then wrap every function that allocates in the thing you're denying. (#![deny(while_true)] might be a good candidate.)

But even then, I doubt you'd really be willing to go through that effort. Someone could get around any annotation you provide by just not annotating an allocation they want to do. And if you can trust people to not do that, you can trust them to not allocate in the first place.

In the context of programming, I don't even trust myself. I'm 100% responsible for all the bugs in my hobby projects. :slight_smile:

I see this 'duplication' as a way to detect contradictions / mismatched expectations in my own mental models.

The most practical solution is @skysch's suggestion of putting your code in a crate that doesn't link to an allocator or the alloc crate. After all, if you don't have access to an allocator you can't allocate memory.

Alternatively, I don't think it already exists, but I'm sure you could make a custom static analysis tool which checks the body of a function and marks anything which calls std::alloc::alloc() either directly or transitively. It could even be implemented as part of a clippy lint.

I'm kinda curious as to the use case here, though. Why do you want to forbid all allocations?

3 Likes

I'm not the original asker, but one thought that comes to mind is if you're doing unsafe stuff and you want to compile to webassembly. I came across a function needed in webgl that's unsafe, but it's okay as long as you don't allocate while you're holding the reference it gives you. If you accidentally cause the web assembly environment to reallocate the chunk of memory that it gave you then the unsafe pointer would point to the wrong place. Maybe they want to do this because they're doing some cool unsafe stuff like that.

3 Likes

Mainly goofing around with "Can we know memory usage upfront?" and "Can we get predictable latency even in the tail of the distribution?"

1 Like

The alloc_counter crate has an #[no_alloc] macro which you can add to a function to forbid allocation inside. I haven't tried it myself, but it sounds like that you're looking for.

3 Likes

It looks to me like that is a runtime check, which isn't as nice as a compile-time check would be.

I confirm alloc_counter is runtime. In #[no_alloc] blocks, it counts every allocation, and checks at the end of the block whether the count is zero.

Yeah, you're both right :slight_smile:

I was coming at this from a runtime perspective since I've been interested in figuring out if I used Vec::with_capacity in the right places. So I was definitely planning on allocating, but I was trying to minimize the allocations by doing them upfront.

Sounds like a reasonable strategy.

However on the few occasions I have done that in the search for a little extra performance I could not measure a significant gain.

I've definitely seen gains (in Rust) moving from nested vectors to one flat one and operating on slices, though there are other factors than the number of allocations in that case.

I've seen nice improvements in performance by preallocating strings and vectors to have roughly the right capacity from the start. An example is a simple indent function in my Textwrap library where preallocating the output saved about 20%:

This will of course depend a lot on the kind of function you're optimizing: in this example, the function is tiny and the work done is mostly about copying string slices around. So having a few unnecessary reallocations can take up a proportionally large amount of time — even though the whole thing runs in microseconds.

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.