# Another case for smaller structs

**URL:** <https://internals.rust-lang.org/t/another-case-for-smaller-structs/15214>\
**Category:** Uncategorized\
**Created:** [August 23, 2021, 3:34pm UTC](https://internals.rust-lang.org/t/another-case-for-smaller-structs/15214 "2021-08-23T15:34:59Z")\
**Posts on this page:** 10\
**Page:** 1

<div class="post-metadata">

**Author:** ![matklad](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/matklad/32/12266_2.png) [@matklad](https://internals.rust-lang.org/u/matklad)\
**Post date:** [August 23, 2021, 3:34pm UTC](https://internals.rust-lang.org/t/another-case-for-smaller-structs/15214/1 "2021-08-23T15:34:59Z")

</div>

Continuation of [Towards even smaller structs](https://internals.rust-lang.org/t/towards-even-smaller-structs/14686)

I realized there's one more use-case for `#[pack]` / `#[flatten]` / `#[cant_take_address_of_this]` attribute for fields. In today rust, you sometimes need to denormalized struct with an enum to make it smaller. To give an example from rowan, where saving extra bytes matters:

```rust
pub(crate) enum GreenChild1 {
    Node { rel_offset: TextSize, node: GreenNode },
    Token { rel_offset: TextSize, token: GreenToken },
}
static_assert!(mem::size_of::<GreenChild>() == mem::size_of::<usize>() * 2);

pub(crate) struct GreenChild2 {
    rel_offset: TextSize,
    node_or_token: Either<GreenNode, GreenToken>,
}
static_assert!(mem::size_of::<GreenChild>() == mem::size_of::<usize>() * 3);

```

Both versions define the same set of values. The second version is nicer code, but it takes more memory. I't be cool if I can say

```rust
pub(crate) struct GreenChild2 {
    rel_offset: TextSize,
    #[packed]
    node_or_token: Either<GreenNode, GreenToken>,
}

```

to shrink it by one `usize`.

[Source code](https://github.com/rust-analyzer/rowan/blob/ea763c97c64830c4f16016d5e9576ee2efd9d5f5/src/green/node.rs#L25-L31).

Trivia: [realized that I need to post it while explaining how rust-analyzer syntax trees work](https://youtu.be/95REqdGceOg?t=3381).

---

<div class="post-metadata">

**Author:** ![felix.s](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/felix.s/32/8073_2.png) [@felix.s](https://internals.rust-lang.org/u/felix.s)\
**Post date:** [August 23, 2021, 4:17pm UTC](https://internals.rust-lang.org/t/another-case-for-smaller-structs/15214/2 "2021-08-23T16:17:40Z")

</div>

> [@matklad](#):
>
> The second version is nicer code, but it takes more memory.

Why is that? I am not familiar with that crate. The types look like they should occupy the same amount of space.

Also would be nice to say explicitly this calls for non-transitive compacting (what I [termed `#[repr(inline)]`](https://github.com/fstirlitz/rust-rfcs/blob/2f5bbb626fc2edb7585fdc826b62fdd023be148c/text/0000-compact-fields.md#reference-level-explanation)), and think for a while if transitive compacting may yield further advantages.

Though having drafted that proto-RFC for a while, I don’t think it’s more use cases that this feature needs to land. What it needs is [careful consideration how to reconcile it with other Rust features](https://github.com/fstirlitz/rust-rfcs/blob/2f5bbb626fc2edb7585fdc826b62fdd023be148c/text/0000-compact-fields.md#unresolved-questions), like borrow returning, `UnsafeCell`, smart pointers, etc.

(Although a couple of better examples in the RFC wouldn’t hurt. This is probably the weakest point of my draft.)

---

<div class="post-metadata">

**Author:** ![matklad](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/matklad/32/12266_2.png) [@matklad](https://internals.rust-lang.org/u/matklad)\
**Post date:** [August 23, 2021, 4:33pm UTC](https://internals.rust-lang.org/t/another-case-for-smaller-structs/15214/3 "2021-08-23T16:33:15Z")

</div>

> [@felix.s](#):
>
> Why is that?

Both `GreenNode` and `GreenToken` are pointers, so `size_of::<Either<Node, Token>>` is 2 usizes: pointer + tag + alignment. For `c: GreenChild2`, the `&c.node_or_token` thus should return a reference to proper either, so _just_ `node_or_token` field occupies two usizes. Then, you need to add `rel_offest` on top of that, which is again aligned to 3\*usize.

For the first case, while the data is logically `Either<Node, Token>`, we can't take a reference to that. So the compiler can stuff the discriminant into the padding between `rel_offest` and node or token.

Here's a minimized example:

> **[Rust Playground](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=5a467aade56217a6dcf0fb858770153a)**
>
> A browser interface to the Rust compiler to experiment with the language

```rust
enum E1 {
    A { x: i32, y: *const () },
    B { x: i32, y: *const () },
}

struct E2 {
    x: i32,
    ab: Either<*const (), *const ()>,
}

enum Either<L, R> { Left(L), Right(R) }

fn main() {
    println!("{}", std::mem::size_of::<E1>()); // 16
    println!("{}", std::mem::size_of::<E2>()); // 24
}

```

---

<div class="post-metadata">

**Author:** ![jhpratt](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/jhpratt/32/11640_2.png) [@jhpratt](https://internals.rust-lang.org/u/jhpratt)\
**Post date:** [August 24, 2021, 12:10am UTC](https://internals.rust-lang.org/t/another-case-for-smaller-structs/15214/4 "2021-08-24T00:10:32Z")

</div>

Another situation where some way to control this would be extremely useful is a struct where all/many fields are `Option<T>`. If there's a way to guarantee that the field never has a reference to it, the `Some`/`None` bits could be bitpacked into a single field. I've benchmarked this before: it _does not_ have a performance impact in my experience.

---

<div class="post-metadata">

**Author:** ![zackw](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/zackw/32/2071_2.png) [@zackw](https://internals.rust-lang.org/u/zackw)\
**Post date:** [August 24, 2021, 2:18pm UTC](https://internals.rust-lang.org/t/another-case-for-smaller-structs/15214/5 "2021-08-24T14:18:05Z")

</div>

Bitpacking "is None" bits can also be useful when you just have a lot of `Option<T>` variables. For instance, [from this users- thread](https://users.rust-lang.org/t/expressing-if-all-four-of-these-options-are-some-then/62891),

```rust
struct LandmarkFileColumns {
    addr: usize,
    longitude: usize,
    latitude: usize,
    distance: usize,
}

fn select_columns(header: &csv::StringRecord, location: &str)
    -> Result<LandmarkFileColumns, LandmarkFileMissingColumnsError> {

    let mut addr: Option<usize> = None;
    let mut lon: Option<usize> = None;
    let mut lat: Option<usize> = None;
    let mut dist: Option<usize> = None;

    for (index, field) in header.iter().enumerate() {
        match field {
            "addr" => { addr = Some(index); },
            "longitude" => { lon = Some(index); },
            "latitude" => { lat = Some(index); },
            f if f == location => { dist = Some(index); },
            _ => {},
        }
        if let (Some(addr), Some(lon), Some(lat), Some(dist)) =
            (addr, lon, lat, dist) {
                return Ok(LandmarkFileColumns { addr, lon, lat, dist });
            }
    }
    return Err(LandmarkFileMissingColumnsError(/* ... */));
}

```

Because `usize` has no niches, the compiler needs 8 words for the 4 `Option<usize>` variables, and (on x86-64) we run out of registers. Hand-coded assembly would pack all four of the "is None" bits into one register. It's not that big of a deal (this particular function is executed only once per run of the program it was extracted from) but I do kinda wish the compiler knew how to do that.

---

<div class="post-metadata">

**Author:** ![jhpratt](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/jhpratt/32/11640_2.png) [@jhpratt](https://internals.rust-lang.org/u/jhpratt)\
**Post date:** [August 24, 2021, 6:11pm UTC](https://internals.rust-lang.org/t/another-case-for-smaller-structs/15214/6 "2021-08-24T18:11:15Z")

</div>

Same idea, but limiting it to structs/enums at first seems more plausible. I've previously suggested doing this automatically if no references are taken, but some expressed concern about `size_of::<T>()` not being deterministic in that case.

---

<div class="post-metadata">

**Author:** ![mjbshaw](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/mjbshaw/32/5103_2.png) [@mjbshaw](https://internals.rust-lang.org/u/mjbshaw)\
**Post date:** [August 24, 2021, 7:07pm UTC](https://internals.rust-lang.org/t/another-case-for-smaller-structs/15214/7 "2021-08-24T19:07:48Z")

</div>

One advantage of starting with local stack variables is that this has no observable side effects (aside from the generated assembly). Applying this to a struct means that attempting to take the address must result in a compilation error (or prevent the optimization, but that only makes sense if it's automatically applied in which case I agree with the concern around `size_of`). But this can be applied to local stack variables automatically be checking if any references are taken (no references → optimization can be applied; yes references → optimization cannot be applied) with zero impact to whether or not the code compiles or what `size_of` returns.

---

<div class="post-metadata">

**Author:** ![cuviper](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/cuviper/32/1897_2.png) [@cuviper](https://internals.rust-lang.org/u/cuviper)\
**Post date:** [August 24, 2021, 7:14pm UTC](https://internals.rust-lang.org/t/another-case-for-smaller-structs/15214/8 "2021-08-24T19:14:09Z")

</div>

Won't LLVM SROA already do this for locals? Probably not bitpacking, exactly, but if it splits the aggregate tag+value, it can also optimize that tag in whatever way it sees fit.

---

<div class="post-metadata">

**Author:** ![zackw](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/zackw/32/2071_2.png) [@zackw](https://internals.rust-lang.org/u/zackw)\
**Post date:** [August 24, 2021, 7:25pm UTC](https://internals.rust-lang.org/t/another-case-for-smaller-structs/15214/9 "2021-08-24T19:25:40Z")

</div>

I'm not good at reading LLVM IR dumps, but I'm pretty sure, based on the generated assembly, that SROA _does_ split up the aggregates in the example. It's the further optimization of "these four abstract variables could share a register" that's missing.

---

<div class="post-metadata">

**Author:** ![system](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/system/32/14092_2.png) [@system](https://internals.rust-lang.org/u/system)\
**Post date:** [November 22, 2021, 7:26pm UTC](https://internals.rust-lang.org/t/another-case-for-smaller-structs/15214/10 "2021-11-22T19:26:28Z")

</div>

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.
