web-dev-qa-db-fra.com

Pourquoi les types de structures récursives sont-ils illégaux dans Rust?

J'essaie des choses au hasard pour approfondir ma compréhension de Rust. I vient de rencontrer l'erreur suivante avec ce code :

struct Person {
    mother: Option<Person>,
    father: Option<Person>,
    partner: Option<Person>,
}

pub fn main() {
    let susan = Person {
        mother: None,
        father: None,
        partner: None,
    };

    let john = Person {
        mother: None,
        father: None,
        partner: Some(susan),
    };
}

L'erreur est :

error[E0072]: recursive type `Person` has infinite size
 --> src/main.rs:1:1
  |
1 | struct Person {
  | ^^^^^^^^^^^^^ recursive type has infinite size
2 |     mother: Option<Person>,
  |     ---------------------- recursive without indirection
3 |     father: Option<Person>,
  |     ---------------------- recursive without indirection
4 |     partner: Option<Person>,
  |     ----------------------- recursive without indirection
  |
  = help: insert indirection (e.g., a `Box`, `Rc`, or `&`) at some point to make `Person` representable

Je comprends que je peux le réparer si je mets le Person dans un Box , donc ça marche :

struct Person {
    mother: Option<Box<Person>>,
    father: Option<Box<Person>>,
    partner: Option<Box<Person>>,
}

pub fn main() {
    let susan = Person {
        mother: None,
        father: None,
        partner: None,
    };

    let john = Person {
        mother: None,
        father: None,
        partner: Some(Box::new(susan)),
    };
}

J'aimerais comprendre toute l'histoire derrière cela. Je sais que la boxe signifie qu'elle sera stockée sur le tas plutôt que sur la pile mais je ne comprends pas pourquoi cette indirection est nécessaire.

55
Christoph

Les données à l'intérieur de structs et enums (et des tuples) sont stockées directement en ligne dans la mémoire de la valeur de structure. Étant donné une structure comme

struct Recursive {
    x: u8,
    y: Option<Recursive>
}

calculons la taille: size_of::<Recursive>(). Il a clairement 1 octet dans le champ x, puis le Option a la taille 1 (pour le discriminant) + size_of::<Recursive>() (pour les données contenues), donc, dans résumé, la taille est la somme:

size_of::<Recursive>() == 2 + size_of::<Recursive>()

Autrement dit, la taille devrait être infinie.

Une autre façon de voir les choses consiste simplement à développer Recursive à plusieurs reprises (sous forme de tuples, pour plus de clarté):

Recursive ==
(u8, Option<Recursive>) ==
(u8, Option<(u8, Option<Recursive>)>) ==
(u8, Option<(u8, Option<(u8, Option<Recursive>)>)>) ==
...

et tout cela est stocké en ligne dans un seul morceau de mémoire.

Un Box<T> Est un pointeur, c'est-à-dire qu'il a une taille fixe, donc (u8, Option<Box<Recursive>>) Est de 1 + 8 octets. (Une façon de considérer Box<T> Est qu'il s'agit d'un T normal avec la garantie qu'il a une taille fixe.)

85
huon

Le Rust langage de programmation , deuxième édition a ceci à dire sur les types récursifs:

Rust doit savoir au moment de la compilation combien d'espace un type prend. Un type de type dont la taille ne peut pas être connue au moment de la compilation est un type récursif où une valeur peut avoir en tant que partie d'une autre valeur du même type. Cette imbrication de valeurs pourrait théoriquement continuer indéfiniment, donc Rust ne sait pas combien d'espace une valeur d'un type récursif a besoin. Les boîtes ont une taille connue, cependant, donc en insérant une boîte dans un définition de type récursif, nous sommes autorisés à avoir des types récursifs.

Fondamentalement, la structure serait de taille infinie si vous n'utilisez pas de boxe. Par exemple, Susan a une mère, un père et un partenaire, chacun ayant une mère, un père et un partenaire ... etc. La boxe utilise un pointeur, qui est une taille fixe, et une allocation de mémoire dynamique.

18
Steve Cobb