Skip to main content

SeaORM FAQ Q01

· 2 min read
Chris Tsang

Q01 Why SeaORM does not nest objects for parent-child relation?

let cake_with_fruits: Vec<(cake::Model, Vec<fruit::Model>)> =
Cake::find().find_with_related(Fruit).all(db).await?;

Consider the above API, Cake and Fruit are two separate models.

If you come from a dynamic language, you'd probably used to:

struct Cake {
id: u64,
fruit: Fruit,
..
}

It's so convenient that you can simply:

let cake = Cake::find().one(db).await?;
println!("Fruit = {}", cake.fruit.name);

Sweet right? Okay so, the problem with this pattern is that it does not fit well with Rust.

Let's look at this playground: https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=6fb0a981189ace081fbb2aa04f50146b

struct Parent {
a: u64,
child: Option<Child>,
}

struct ParentWithBox {
a: u64,
child: Option<Box<Child>>,
}

struct Child {
a: u64,
b: u64,
c: u64,
d: u64,
}

fn main() {
dbg!(std::mem::size_of::<Parent>());
dbg!(std::mem::size_of::<ParentWithBox>());
dbg!(std::mem::size_of::<Child>());
}

What's the output you guess?

[src/main.rs:21] std::mem::size_of::<Parent>() = 48
[src/main.rs:22] std::mem::size_of::<ParentWithBox>() = 16
[src/main.rs:23] std::mem::size_of::<Child>() = 32

In dynamic languages, objects are always held by pointers, and that maps to a Box in Rust. In Rust, we don't put objects in Boxes by default, because it forces the object to be allocated on the heap. And that is an extra cost! Because objects are always first constructed on the stack and then being copied over to the heap.

Ref:

  1. https://users.rust-lang.org/t/how-to-create-large-objects-directly-in-heap/26405
  2. https://github.com/PoignardAzur/placement-by-return/blob/placement-by-return/text/0000-placement-by-return.md

We face the dilemma where we either put the object on the stack and waste some space (it takes up 48 bytes no matter child is None or not) or put the object in a box and waste some cycles.

If you are new to Rust, all these might be unfamiliar, but a Rust programmer has to consciously make decisions over memory management, and we don't want to make decisions on behalf of our users.

That said, there were proposals to add API with this style to SeaORM, and we might implement that in the future. Hopefully this would shed some light on the matter meanwhile.