Art’s Substack

Art’s Substack

Rust: enum, boxed error and stack size mystery

Art's avatar
Art
May 15, 2024

Usually you model errors in Rust using enum and if you want to reduce boilerplate you can use crates like thiserror. Typically you define Error and ErrorKind:

use thiserror::Error;

#[derive(Error, Debug)]
#[error(transparent)]
pub struct Error(Box<ErrorKind>);

#[derive(Error, Debug)]
pub enum ErrorKind {
    #[error("IllegalFibonacciInputError: {0}")]
    IllegalFibonacciInputError(String),
    #[error("VeryLargeError:")]
    VeryLargeError([i32; 1024])
}

You may notice Error contains boxed ErrorKind, the reason is to limit the maximum size of Result<T> . The size of enum ErrorKind is equal to the largest variant of the enum + padding, in some cases it can become quite large and method returning a type that contains ErrorKind will need to use larger stack space even if it is happy path. Here is how memory for enum can be visualized (kudos to Rust container cheat sheet, by Raph Levien)

Let's test that assumption by creating two methods to calculate Fibonacci number recursively with different return types:

  • pub fn fib1(n: u32) -> Result<u64, Error>

  • pub fn fib2(n: u32) -> Result<u64, ErrorKind>

First we check what is the size of our types, we can use std::mem::size_of

fn main() {
    use std::mem::size_of;

	println!("Size of Result<i32, Error>: {}", size_of::<Result<i32, Error>>());
    println!("Size of Result<i32, ErrorKind>: {}", size_of::<Result<i32, ErrorKind>>());
}

Produced output make sense (consider padding as well)

Size of Result<i32, Error>: 16
Size of Result<i32, ErrorKind>: 4104

Is it enough? Not really, modern compilers are very complicated systems so I suggest we take a look deeper, to the generated assembly code!

Now an interesting part, Rust compiler version <= 1.74.0 reserves 4096 + 16 = 4112 bytes of stack (yes, with optimization level 3, opt-level=3), check this out https://godbolt.org/z/Wc9G13sW9:

example::fib1:
 push   r15
 push   r14
 push   rbx
 sub    rsp,0x1000           ; reserve 4096 bytes on stack
 mov    QWORD PTR [rsp],0x0
 sub    rsp,0x10             ; reserve another 16 bytes
 mov    r14d,esi
 mov    rbx,rdi
 lea    esi,[r14-0x1]
 cmp    esi,0x2
 ...
 add    rsp,0x1010
 pop    rbx
 pop    r14
 pop    r15
 ret

If I switch to Rust 1.75.0, it gets fixed, https://godbolt.org/z/Tss3ozPGc, it reserves 32 bytes

example::fib1:
 push   r15
 push   r14
 push   rbx
 sub    rsp,0x20            ; reserve 32 bytes on stack
 mov    r14d,esi
 mov    rbx,rdi
 lea    esi,[r14-0x1]
 cmp    esi,0x2
 ...
 add    rsp,0x20
 pop    rbx
 pop    r14
 pop    r15
 ret

I quickly checked the changes that landed in 1.75.0 and could not find anything. Any thoughts what was causing it? Is it a known bug?

Comments and suggestions are welcome! Thank you for your time.

Thanks for reading Art’s Substack! Subscribe for free to receive new posts and support my work.

Discussion about this post

User's avatar
John's avatar
John
Apr 22, 2025

https://internals.rust-lang.org/t/why-rust-compiler-1-77-0-to-1-85-0-reserves-2x-extra-stack-for-large-enum/22775/2

Reply
Share
1 reply by Art
1 more comment...

No posts

Ready for more?

© 2026 Art · Privacy ∙ Terms ∙ Collection notice
Start your SubstackGet the app
Substack is the home for great culture