How can I use nested structs?

I'm new to Rust and for the first time I'm writing this sort of code:

PLAYGROUND HERE

#[tokio::main]
async fn main() {
    #[derive(Default, Clone)]
    pub struct Coach {
        id: Option<i64>,
        name: String,
        team: Team
    }

    #[derive(Default, Clone)]
    pub struct Team {
        id: Option<i64>,
        name: String,
        coach: Coach
    }
}

The error is:

error[E0072]: recursive type `Coach` has infinite size
 --> src/main.rs:4:5
  |
4 |     pub struct Coach {
  |     ^^^^^^^^^^^^^^^^ recursive type has infinite size
...
7 |         team: Team
  |               ---- recursive without indirection
  |
help: insert some indirection (e.g., a `Box`, `Rc`, or `&`) to make `Coach` representable
  |
7 |         team: Box<Team>
  |               ++++    +

error[E0072]: recursive type `Team` has infinite size
  --> src/main.rs:11:5
   |
11 |     pub struct Team {
   |     ^^^^^^^^^^^^^^^ recursive type has infinite size
...
14 |         coach: Coach
   |                ----- recursive without indirection
   |
help: insert some indirection (e.g., a `Box`, `Rc`, or `&`) to make `Team` representable
   |
14 |         coach: Box<Coach>
   |                ++++     +

For more information about this error, try `rustc --explain E0072`.

Coming from Go where something like this is easily possible, what is Rust idiomatic code?

Have you tried doing what the error message suggests?

help: insert some indirection (e.g., a `Box`, `Rc`, or `&`) to make `Coach` representable
  |
7 |         team: Box<Team>
  |               ++++    +
2 Likes

Try changing team: Team into team: Rc<Team>. This will have other problems, but one fix at a time. :slight_smile:

What other problems?

Rc or Box?

I tried with lifetimes but I have other issues (in the future I'll use lifetimes only).

Very small runtime cost of an indirection (both Rc and Box and a counter (for Rc).

Don't. Absolutely do not only use lifetimes. If you observe the Rust ecosystem you'll learn when lifetime are appropriate when they are not (mostly only appropriate for "view structs").

You're just bumping against a fundamental issue with the borrowing model and linked lists. It's quite hard to do correctly in Rust, it's hard to do correctly in other languages too, but rust will check that you do it somewhat correctly.

Don't put references in structs. Reference in a struct means the struct is not storing its data, and you will be unable to build the graph. The correct type for storing things by reference in structs is Box (or Rc/Arc or something else owning, not &).

3 Likes

Given the way the two structs refer to each other, you may eventually run into circular reference problems. One way around this is to have a Vec of coaches, a Vec of teams, and store indexes to each other.

You can also use Weak in some cases to avoid dealing with indices like that.

Can you write an example, please?

Playground

#![allow(dead_code)]
use std::rc::{Rc, Weak};

#[derive(Debug, Default, Clone)]
pub struct Coach {
    id: Option<i64>,
    name: String,
    team: Weak<Team>,
}

#[derive(Debug, Default, Clone)]
pub struct Team {
    id: Option<i64>,
    name: String,
    coach: Coach,
}

fn main() {
    let teams = vec![
        Rc::new_cyclic(|weak| Team {
            id: Some(1),
            name: "First".into(),
            coach: Coach {
                id: Some(1),
                name: "Some Guy".into(),
                team: weak.clone(),
            },
        }),
        Rc::new_cyclic(|weak| Team {
            id: Some(2),
            name: "Second".into(),
            coach: Coach {
                id: Some(2),
                name: "Another Person".into(),
                team: weak.clone(),
            },
        }),
    ];

    println!("{teams:#?}");

    println!("{:#?}", teams[0].coach.team.upgrade());
}

You have to use new_cyclic since the team on Coach isn't optional, and neither is the coach field on Team.

This setup won't work correctly if you try and use a Coach without keeping the Rc<Team> around though, since Weak's purpose is to not increase the reference count.

Depending on what you want to do, you could also simply do:

#[derive(Default, Clone)]
pub struct Coach {
    id: Option<i64>,
    name: String,
}
#[derive(Default, Clone)]
pub struct Team {
    id: Option<i64>,
    name: String,
}
#[derive(Default, Clone)]
pub struct TeamWithCoach {
    team: Team,
    coach: Coach,
}

(Playground)

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.