Why are infinite stack-based data structures allowed when references and lifetimes are used?

I have this enum here:

enum FieldValue<'a> {
    Int(i32),
    Short(i32),
    Char(i32),
    Byte(i32),
    Boolean(i32),
    Float(f32),
    Long(f64),
    Double(f64),
    String(String),
    Vec(&'a FieldValue<'a>),
    // Value found was outside of specification.
    #[default] Invalid,
}

I know recursive enums have a potentially infinite size, so I was expecting it to ask me to Box<T> the recursing variant, which, as I understand, means the enum contains a pointer to the value on the heap which is stored on the stack, instead of the value itself being stored on the stack. I thought this was because placing potentially infinitely large things on the stack is a bad idea, and because Rust likes things on the stack to be of a size known at compile time. So why is it okay if instead of a Box<T> I make it store a reference with a lifetime, as in the code above? Isn't this still stack-only data of an unknown size?

The reason adding a Box<FieldValue> field works has nothing to do with storing data on the stack versus the heap. It uses a pointer to introduce some level of indirection, which means now the Box<FieldValue> field has a known size that doesn't depend on the size of FieldValue.

Whether that indirection is done using an owning Box<FieldValue> or a borrowing &'a FieldValue doesn't matter. What matters is that the size of a type's field no longer depends on the size of the type itself.

4 Likes

It's not. References have a perfectly compile-time-known size.

(Where the reference points is irrelevant.)

1 Like