Est-il possible d'allouer des structures auto-référentielles sur la pile ? La documentation pin montre un exemple d'épingler une structure sur le tas. Je l'ai suivi pour écrire le code correspondant :

pub struct Cont {
    pub f: i32,
    // shall point to 'f' field
    ptr_f: NonNull<i32>,
}

impl Cont {
    pub fn read(&self) -> i32 {
        unsafe { *self.ptr_f.as_ref() }
    }

    pub fn pinned(value: i32) -> Pin<Self> {
        // ensures ptr_f = &f
    }
}

fn main() {
    let a = Cont::pinned(5);
    let b = Cont::pinned(12);

    assert_eq!(a.f, a.read());
    assert_eq!(b.f, b.read());
}

Mais je ne sais pas comment écrire la fonction Cont::pinned, ni même si c'est la bonne signature (si possible).

3
Guillaume Lemaître 31 oct. 2020 à 14:10

1 réponse

Meilleure réponse

mais je ne sais pas comment écrire la fonction Cont::pinned, ni même si c'est la bonne signature (si possible).

Le paramètre de type de Pin<P> est toujours un pointeur ou une référence ; un Pin ne possède jamais ses données, sauf via un type de pointeur propriétaire tel que Box. Étant donné que vous souhaitez conserver la valeur d'origine sur la pile, l'analogue de la fonction dans l'exemple est :

fn pinned(value: i32) -> Pin<&mut Self>;

Mais ce n'est pas possible car une fonction ne peut pas renvoyer une référence à quelque chose qu'elle a créé - à moins que quelque chose ne soit stocké sur le tas ou dans la mémoire statique. Donc, si vous deviez construire un Pin d'une valeur allouée à la pile, vous devriez d'abord créer la valeur non épinglée, afin que l'épingle puisse la référencer.

Vous pourriez peut-être essayer de concevoir une API qui crée un Pin en mutant certaines données déjà présentes sur la pile :

let a: Option<Cont> = None;
let b: Option<Cont> = None;

let a = Cont::pinned(&mut a, 5);
let b = Cont::pinned(&mut b, 12);

Mais alors la valeur vivrait plus longtemps que l'épingle et vous ne pouvez appliquer les garanties de l'épingle que tant que l'épingle est active, ce qui rend cela malsain.

Pour que cela sonne, vous devez d'une manière ou d'une autre faire en sorte que les valeurs d'origine ne soient pas accessibles après la suppression de la broche. Cela se traduirait par une API très contrainte. Par exemple:

fn main() {
    // setup_conts creates the pinned Cont values and calls the closure 
    setup_conts(5, 12, |a: Pin<&mut Cont>, b: Pin<&mut Cont>| {
        // a and b can only be used inside this closure
    })
}
2
Peter Hall 31 oct. 2020 à 13:31