# Pure annotation for stateless functions

**URL:** <https://internals.rust-lang.org/t/pure-annotation-for-stateless-functions/23178>\
**Category:** language design\
**Created:** [July 4, 2025, 12:06pm UTC](https://internals.rust-lang.org/t/pure-annotation-for-stateless-functions/23178 "2025-07-04T12:06:46Z")\
**Posts on this page:** 20\
**Page:** 1

<div class="post-metadata">

**Author:** ![Evian-Zhang](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/evian-zhang/32/12170_2.png) [@Evian-Zhang](https://internals.rust-lang.org/u/Evian-Zhang)\
**Post date:** [July 4, 2025, 12:06pm UTC](https://internals.rust-lang.org/t/pure-annotation-for-stateless-functions/23178/1 "2025-07-04T12:06:46Z")

</div>

Rust now has sub-expression elimination with automatic pure function identification:

```rust
#[inline(never)]
fn foo(a: usize) -> usize {
    a + 1
}

#[unsafe(no_mangle)]
fn bar(a: usize) -> usize {
    let a1 = foo(a);
    let a2 = foo(a);
    a1 + a2
}

```

`foo` does not have any side-effects (which is called "pure" function in some places). As a result, the generated `bar` is highly optimized (see [this playground link](https://play.rust-lang.org/?version=stable&mode=release&edition=2024&gist=c291ac501590513d0a29054c2a63ae46)), the two `foo` invocations are eliminated to one:

```plaintext
bar:
	pushq	%rax
	callq	playground::foo
	addq	%rax, %rax
	popq	%rcx
	retq

```

However, this optimization can be disabled by some unnoticed practice:

```rust
fn foo(a: usize) -> usize {
    if a > 2 {
        a
    } else {
        panic!("")
    }
}

```

If we add `panic` in the `foo` function, the generated `bar` will become (see [this playground link](https://play.rust-lang.org/?version=stable&mode=release&edition=2024&gist=115253f2ac269af644497226f4fa3ef2)):

```plaintext
bar:
	pushq	%rbx
	movq	%rdi, %rbx
	callq	playground::foo
	movq	%rbx, %rdi
	callq	playground::foo
	addq	%rbx, %rbx
	movq	%rbx, %rax
	popq	%rbx
	retq

```

The reason is very straightforward and easy to understand: the panicking itself has side effect, so `foo` is not pure anymore.

Although this is reasonable enough, when we actually coding, I don't think most people will be aware that **a simple `unwrap` or `log::info!` would disable this optimization**. And moreover, in most stituations, this optimization should still be enabled in these situations.

As a result, I think a `pure` annotation would be helpful if people want to manually mark a function as pure, even if it has side-effects like `unwrap` or `log`.

There is a [similar post](https://internals.rust-lang.org/t/const-pure/13824), however, it was questioned about the necessity and motivation. (In my understanding, if we design this annotation as an unsafe attribute, we can even get rid of the complicated rules introduced in that post.)

---

<div class="post-metadata">

**Author:** ![Vorpal](https://avatars.discourse-cdn.com/v4/letter/v/aca169/32.png) [@Vorpal](https://internals.rust-lang.org/u/Vorpal)\
**Post date:** [July 4, 2025, 4:07pm UTC](https://internals.rust-lang.org/t/pure-annotation-for-stateless-functions/23178/2 "2025-07-04T16:07:18Z")

</div>

> [@Evian-Zhang](#):
>
> As a result, I think a `pure` annotation would be helpful if people want to manually mark a function as pure, even if it has side-effects like `unwrap` or `log`.

Wouldn't it make more sense to have a pure attribute that causes a compiler error if the function isn't actually pure? That is certainly how I would expect it to behave.

Pure function that actually have side effects seems it could trivially lead to UB and even if unsafe, it would be an unnecessary footgun.

I would love to see pure, similar to const though. But this feels like keyword generics / effects system territory.

---

<div class="post-metadata">

**Author:** ![Vorpal](https://avatars.discourse-cdn.com/v4/letter/v/aca169/32.png) [@Vorpal](https://internals.rust-lang.org/u/Vorpal)\
**Post date:** [July 4, 2025, 5:18pm UTC](https://internals.rust-lang.org/t/pure-annotation-for-stateless-functions/23178/3 "2025-07-04T17:18:19Z")

</div>

Thinking a bit more about this, it would perhaps be useful to have a notion of a function that is either pure or divergent, with the constraint that divergence is determinsitic from the input parameters (no looking at globals, no randomness, IO etc).

I would not expect an attribute for this concept but it could be something the optimiser tracks internally. I would assume this would need LLVM work.

EDIT: This wouldn't solve logging obviously, but that _is_ a side effect. But it would solve the panic case.

---

<div class="post-metadata">

**Author:** ![RustyYato](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/rustyyato/32/13627_2.png) [@RustyYato](https://internals.rust-lang.org/u/RustyYato)\
**Post date:** [July 4, 2025, 5:28pm UTC](https://internals.rust-lang.org/t/pure-annotation-for-stateless-functions/23178/4 "2025-07-04T17:28:54Z")

</div>

This would not solve the panic case because the panic hook can run some arbitrary code and is called whenever a panic happens

---

<div class="post-metadata">

**Author:** ![Vorpal](https://avatars.discourse-cdn.com/v4/letter/v/aca169/32.png) [@Vorpal](https://internals.rust-lang.org/u/Vorpal)\
**Post date:** [July 4, 2025, 5:43pm UTC](https://internals.rust-lang.org/t/pure-annotation-for-stateless-functions/23178/5 "2025-07-04T17:43:32Z")

</div>

While that is true, a panic is divergent. Guaranteed to not return. So whatever that side effect is will be observed exactly once, so what is the issue? Or am I missing something?

---

<div class="post-metadata">

**Author:** ![jrose](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/jrose/32/9591_2.png) [@jrose](https://internals.rust-lang.org/u/jrose)\
**Post date:** [July 4, 2025, 6:00pm UTC](https://internals.rust-lang.org/t/pure-annotation-for-stateless-functions/23178/6 "2025-07-04T18:00:16Z")

</div>

As far as the attribute side goes, I wonder how much happier people would be if it wasn’t spelled `const` or `pure` but instead something like `okay_to_ignore_side_effects`. It’s not even `unsafe` without other `unsafe` stuff going on, since the rewrites the optimizer would do based on such an attribute are always something a human _could_ have written manually [EDIT: as long as ownership is still respected].

---

<div class="post-metadata">

**Author:** ![RustyYato](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/rustyyato/32/13627_2.png) [@RustyYato](https://internals.rust-lang.org/u/RustyYato)\
**Post date:** [July 4, 2025, 7:52pm UTC](https://internals.rust-lang.org/t/pure-annotation-for-stateless-functions/23178/7 "2025-07-04T19:52:09Z")

</div>

Because it is sound to replace any pure expression with another pure expression as long as they compute the same value or both duverge. So it would be legal to just convert panics to `loop {}`, which is obviously nonsense. So panics cannot be considered pure.

---

<div class="post-metadata">

**Author:** ![the8472](https://avatars.discourse-cdn.com/v4/letter/t/0ea827/32.png) [@the8472](https://internals.rust-lang.org/u/the8472)\
**Post date:** [July 4, 2025, 9:04pm UTC](https://internals.rust-lang.org/t/pure-annotation-for-stateless-functions/23178/8 "2025-07-04T21:04:46Z")

</div>

> [@Vorpal](#):
>
> Thinking a bit more about this, it would perhaps be useful to have a notion of a function that is either pure or divergent, with the constraint that divergence is determinsitic from the input parameters (no looking at globals, no randomness, IO etc).

There's lots of nuance to effects. And I'd love to have them in the type system, there are so many optimizations we could do if we knew that certain closures don't have a particular effect.

> **[Extending Rust's Effect System](https://blog.yoshuawuyts.com/extending-rusts-effect-system/#what-are-effects)**

> **[Totality](https://blog.yoshuawuyts.com/totality/#const-functions)**

---

<div class="post-metadata">

**Author:** ![Vorpal](https://avatars.discourse-cdn.com/v4/letter/v/aca169/32.png) [@Vorpal](https://internals.rust-lang.org/u/Vorpal)\
**Post date:** [July 4, 2025, 11:44pm UTC](https://internals.rust-lang.org/t/pure-annotation-for-stateless-functions/23178/9 "2025-07-04T23:44:36Z")

</div>

But I never said pure, I said: "a notion of a function that is either pure or divergent, with the constraint that divergence is determinsitic from the input parameters". That is not the same thing as purity. It would only allow a subset of the optimisations that full purity allows.

In particular it would allow collapsing multiple calls with the same values for the parameters, since either the function is pure _for that particular input_ or it diverges for that same input. And thus it will diverge the first time it is called with that input (and additional extra calls can't be observed anyway).

Once it diverges you cannot treat whatever happens after that as pure obviously. It could unwind, loop forever or abort. And if there are any catch\_unwind you have to treat that as a barrier for optimisations based on purity of such a function.

You could think of this notion as partial purity, mirroring the concept of total/partial functions.

---

<div class="post-metadata">

**Author:** ![Evian-Zhang](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/evian-zhang/32/12170_2.png) [@Evian-Zhang](https://internals.rust-lang.org/u/Evian-Zhang)\
**Post date:** [July 5, 2025, 12:00am UTC](https://internals.rust-lang.org/t/pure-annotation-for-stateless-functions/23178/10 "2025-07-05T00:00:53Z")

</div>

> I would not expect an attribute for this concept but it could be something the optimiser tracks internally.

To be honest, I don't expect compiler could handle all things about purity. On one hand, whether functions containing logging should be pure is determined by business logic, it is better if we can manually mark a function pure. On the other hand, determining purity (or divergence, just bikeshed it) is not an easy work and as the previous post I linked, it involves many many complicated rules. I think manually marking pure could be more flexible.

---

<div class="post-metadata">

**Author:** ![Evian-Zhang](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/evian-zhang/32/12170_2.png) [@Evian-Zhang](https://internals.rust-lang.org/u/Evian-Zhang)\
**Post date:** [July 5, 2025, 12:06am UTC](https://internals.rust-lang.org/t/pure-annotation-for-stateless-functions/23178/11 "2025-07-05T00:06:46Z")

</div>

> [@jrose](#):
>
> It’s not even `unsafe` without other `unsafe` stuff going on, since the rewrites the optimizer would do based on such an attribute are always something a human _could_ have written manually [EDIT: as long as ownership is still respected].

Sorry I don't get it. If we make it unsafe, the UB rules of rust do need to extend about purity, but just like `unsafe(no_mangle)` attribute, we could not manually write a unmangled function without such attribute.

---

<div class="post-metadata">

**Author:** ![jrose](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/jrose/32/9591_2.png) [@jrose](https://internals.rust-lang.org/u/jrose)\
**Post date:** [July 5, 2025, 12:20am UTC](https://internals.rust-lang.org/t/pure-annotation-for-stateless-functions/23178/12 "2025-07-05T00:20:03Z")

</div>

I’m saying “not performing side effects” cannot itself be `unsafe` (in the memory-safety sense) unless there is a use of `unsafe` already in the side effects or a use of `unsafe` relying on the side effects. Which means if someone applies the attribute incorrectly but there’s no `unsafe` in sight, they don’t lose memory safety; they might just get behavior they didn’t expect. Which is a nice guarantee that matches other things in Rust that don’t require `unsafe`, and therefore this wouldn’t either.

---

<div class="post-metadata">

**Author:** ![Evian-Zhang](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/evian-zhang/32/12170_2.png) [@Evian-Zhang](https://internals.rust-lang.org/u/Evian-Zhang)\
**Post date:** [July 5, 2025, 12:34am UTC](https://internals.rust-lang.org/t/pure-annotation-for-stateless-functions/23178/13 "2025-07-05T00:34:32Z")

</div>

Ok I understand, thank you for explanation. `unsafe(no_mangle)` does not involve memory safety neither. In my understanding, `unsafe` just indicates that the developer makes sure that there is no UB here. As a result, if we add a new rule to rust' UB list about which function could be marked as pure, the `unsafe(pure)` would then make sense.

After some more thoughts, I think I understand what you mean. Yes, I need to think more about whether it should be marked as unsafe... My first impression is that this is a dangerous operation that may completely change program behavior if users wrongly mark purity. But yes, it is not necessarily marked as unsafe. I'm willing to hear about other opinions.

---

<div class="post-metadata">

**Author:** ![jrose](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/jrose/32/9591_2.png) [@jrose](https://internals.rust-lang.org/u/jrose)\
**Post date:** [July 5, 2025, 12:39am UTC](https://internals.rust-lang.org/t/pure-annotation-for-stateless-functions/23178/14 "2025-07-05T00:39:30Z")

</div>

`no_mangle` involves memory safety in that memory safety is only guaranteed by the definition of the function matching the declaration as seen by callers; `no_mangle` removes the primary enforcement of that.

I can’t think of _any_ undefined behavior that `okay_to_skip_side_effects` would cause (on its own, specifically), but similarly I can’t think of any undefined behavior that can’t lead to memory unsafety; they’re equivalent in my mind. Note that this is distinct from “just slap the LLVM `pure` attribute on it”, because that wouldn’t respect ownership.

---

<div class="post-metadata">

**Author:** ![Evian-Zhang](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/evian-zhang/32/12170_2.png) [@Evian-Zhang](https://internals.rust-lang.org/u/Evian-Zhang)\
**Post date:** [July 5, 2025, 12:44am UTC](https://internals.rust-lang.org/t/pure-annotation-for-stateless-functions/23178/15 "2025-07-05T00:44:19Z")

</div>

Yeah, it's then fell into the discussion about by which rules should we determine UB when we are designing language features. This question has been in my mind long ago, but I didn't find discussions about it.

---

<div class="post-metadata">

**Author:** ![SkiFire13](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/skifire13/32/7579_2.png) [@SkiFire13](https://internals.rust-lang.org/u/SkiFire13)\
**Post date:** [July 5, 2025, 6:42am UTC](https://internals.rust-lang.org/t/pure-annotation-for-stateless-functions/23178/16 "2025-07-05T06:42:53Z")

</div>

> [@RustyYato](#):
>
> Because it is sound to replace any pure expression with another pure expression as long as they compute the same value or both duverge.

I believe you need to be careful with the order of operations here though.

Once an expression diverges you may have side effects (e.g. panic hooks, printing stack traces, etc etc) so you need to ensure that the first possibly-divergent expression is still computed first.

---

<div class="post-metadata">

**Author:** ![Noratrieb](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/noratrieb/32/12440_2.png) [@Noratrieb](https://internals.rust-lang.org/u/Noratrieb)\
**Post date:** [July 5, 2025, 8:30am UTC](https://internals.rust-lang.org/t/pure-annotation-for-stateless-functions/23178/17 "2025-07-05T08:30:45Z")

</div>

I view a non-unsafe version of behavior changing optimization hints as very suspect and likely unsound. All assumptions made by the optimizer today can only rely on UB and nothing more, and I think extending that to rely on arbitrary user provider attributes would have wide consequences I think.

I can't come up with an example right now, but I would expect this to lead to unsoundnesses.

---

<div class="post-metadata">

**Author:** ![ais523](https://avatars.discourse-cdn.com/v4/letter/a/a183cd/32.png) [@ais523](https://internals.rust-lang.org/u/ais523)\
**Post date:** [July 5, 2025, 10:00am UTC](https://internals.rust-lang.org/t/pure-annotation-for-stateless-functions/23178/18 "2025-07-05T10:00:35Z")

</div>

I think maybe the solution here is for the compiler to infer whether functions are "safely cacheable", i.e. "if the function is given input X and returns normally with some return value, there were no side effects, and all future calls to the function with input X will also return normally with the same return value".

This property is sufficient to merge multiple calls to the function with the same arguments into one (as long as the caller doesn't `catch_unwind` – it's OK for the program as a whole to catch panics as long as the calling function doesn't), and doesn't require reasoning about situations where "side effects are OK to remove".

---

<div class="post-metadata">

**Author:** ![Evian-Zhang](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/evian-zhang/32/12170_2.png) [@Evian-Zhang](https://internals.rust-lang.org/u/Evian-Zhang)\
**Post date:** [July 5, 2025, 10:16am UTC](https://internals.rust-lang.org/t/pure-annotation-for-stateless-functions/23178/19 "2025-07-05T10:16:20Z")

</div>

Current Rust compiler already has such ability to some extend, as shown in the first example in the post. However, in most situations, the logging should not disable this optimization (although in some situations not). Based on this motivation, I proposed the manual pure attribute to customize such behavior. Moreover, if we let compiler to automatically infer the purity with awareness of panicking, I don't know how much complexity would it add to Rust compiler considering the complexity of unwinding.

---

<div class="post-metadata">

**Author:** ![toc](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/toc/32/6692_2.png) [@toc](https://internals.rust-lang.org/u/toc)\
**Post date:** [July 5, 2025, 1:55pm UTC](https://internals.rust-lang.org/t/pure-annotation-for-stateless-functions/23178/20 "2025-07-05T13:55:32Z")

</div>

> [@SkiFire13](#):
>
> Once an expression diverges you may have side effects (e.g. panic hooks, printing stack traces, etc etc) so you need to ensure that the first possibly-divergent expression is still computed first.

Perhaps this deserves a side discussion, but I don't particularly see why this couldn't be relaxed for panics. I can certainly imagine being confused by a panic happening out of order with another panic, but it's not like optimizations don't lead to confusing output at other times.

[Next page](https://internals.rust-lang.org/t/pure-annotation-for-stateless-functions/23178.md?page=2)
