Rust: & mut auto emprunter en conflit avec lui-même.

Créé le 3 févr. 2015  ·  20Commentaires  ·  Source: rust-lang/rust

Je ne sais pas pourquoi cela se produit mais rovar et XMPPwocky sur IRC pensaient que c'était un bogue du compilateur.

struct A {
    a: i32
}

impl A {
    fn one(&mut self) -> &i32{
        self.a = 10;
        &self.a
    }
    fn two(&mut self) -> &i32 {
        loop {
            let k = self.one();
            if *k > 10i32 {
                return k;
            }
        }
    }
}

... donne l'erreur suivante ...

<anon>:12:21: 12:25 error: cannot borrow `*self` as mutable more than once at a time
<anon>:12             let k = self.one();
                              ^~~~
<anon>:12:21: 12:25 note: previous borrow of `*self` occurs here; the mutable borrow prevents subsequent moves, borrows, or modification of `*self` until the borrow ends
<anon>:12             let k = self.one();
                              ^~~~
<anon>:17:6: 17:6 note: previous borrow ends here
<anon>:10     fn two(&mut self) -> &i32 {
...
<anon>:17     }
              ^
error: aborting due to previous error
playpen: application terminated with error code 101

Fait intéressant, si la deuxième méthode est remplacée par ...

    fn two(&mut self) -> &i32 {
        loop {
            let k = self.one();
            return k;
        }
    }

Cela compilera bien.

parc pour bébé: http://is.gd/mTkfw5

A-NLL A-borrow-checker A-typesystem C-bug NLL-polonius T-compiler

Commentaire le plus utile

Je pense que j'ai rencontré un problème similaire en production où un emprunt immuable semble vivre trop longtemps ( solution de contournement ).

Je devrais également mentionner que le code de starwed ne se compile plus le dernier soir. @oberien a suggéré une regression-from-nightly-to-nightly ", et il m'a également été suggéré de taguer @nikomatsakis au cas où ce serait un problème urgent :)

Tous les 20 commentaires

Je pense que c'est un comportement correct. Pour voir pourquoi, considérez le code avec des annotations explicites à vie:

struct A {
    a: i32
}

impl A {
    fn one<'a>(&'a mut self) -> &'a i32{
        self.a = 10;
        &self.a
    }
    fn two<'a>(&'a mut self) -> &'a i32 {
        loop {
            let k = self.one();
            if *k > 10i32 {
                return k;
            }
        }
    }
}

two doit retourner une référence avec la même durée de vie que l'emprunt mutable qui lui est donné. Cependant, depuis qu'il obtient son. Cela signifie que k doit avoir une durée 'a vie one ait une durée 'a vie one doit également avoir une durée 'a vie self et de le déplacer vers one . Puisque les références mutables sont linéaires, elles ne peuvent être déplacées qu'une seule fois, mais le loop signifie que one peut devoir être appelé à plusieurs reprises avec la même référence mutable.

Le deuxième exemple ne fonctionne que parce que Rust peut voir que la boucle ne s'exécutera qu'une seule fois, donc self ne sera déplacé qu'une seule fois.

Cela ressemble à un problème d'emprunt non lexical. Vous pouvez obtenir un comportement similaire sans aucune boucle:

struct Foo { data: Option<i32> }

fn main() {
    let mut x = Foo{data: Some(1)};

    foo(&mut x);
}

fn foo(x: &mut Foo) -> Option<&mut i32> {
    if let Some(y) = x.data.as_mut() {
        return Some(y);
    }

    println!("{:?}", x.data); 
    None
}
<anon>:14:22: 14:28 error: cannot borrow `x.data` as immutable because it is also borrowed as mutable
<anon>:14     println!("{:?}", x.data); 
                               ^~~~~~
note: in expansion of format_args!
<std macros>:2:43: 2:76 note: expansion site
<std macros>:1:1: 2:78 note: in expansion of println!
<anon>:14:5: 14:30 note: expansion site
<anon>:10:22: 10:28 note: previous borrow of `x.data` occurs here; the mutable borrow prevents subsequent moves, borrows, or modification of `x.data` until the borrow ends
<anon>:10     if let Some(y) = x.data.as_mut() {
                               ^~~~~~
<anon>:16:2: 16:2 note: previous borrow ends here
<anon>:9 fn foo(x: &mut Foo) -> Option<&mut i32> {
...
<anon>:16 }
          ^
error: aborting due to previous error

Rustc ne peut pas «gérer» les rendements d'emprunt conditionnels.

CC @pcwalton

Je pense que le problème est en fait que ce n'est pas la mutabilité de la valeur de retour qui est utilisée mais la mutabilité de l'argument de la fonction.

Le code suivant appelle une fonction de mutation qui renvoie une référence immuable. Après cela, je ne peux pas prendre de références immuables.

struct Foo (u8);

impl Foo {
    fn bar(&mut self) -> &u8 {
        self.0 += 1;
        &self.0
    }
}

fn main() {
    let mut x = Foo(42);
    let a = x.bar(); // note: borrow of `x` occurs here
    let b = x.0; // error: cannot use `x.0` because it was mutably borrowed
}

Parc à bébé

Je rencontre toujours ce problème le dernier soir. Il semble qu'il traite les retours comme ayant une durée de vie jusqu'à la fin de la fonction, même si la fonction sans retour passe le vérificateur d'emprunt. Cela semble un peu étrange, compte tenu du fait que le but d'un retour est de terminer la fonction plus tôt.

Si vous regardez l'exemple de @Gankro , il passe le vérificateur d'emprunt si vous supprimez le mot-clé return, même si la fonction résultante ne fonctionnera pas correctement.

Ceci est bloqué sur les durées de vie non lexicales, ce qui est à son tour bloqué sur MIR. Idéalement, cela sera corrigé d'ici la fin de 2016, mais un correctif ne sera pas bientôt disponible.

Quelques discussions sur les composants internes:

Discussion sur les emprunts non lexicaux: https://github.com/rust-lang/rfcs/issues/811

Donc lire le [thread interne] me donne l'impression que ce n'est pas réparable (même avec NLL). Quelqu'un peut-il confirmer? (@eddyb?)

cc @ repos-long / long

C'est en effet NLL et, si je ne me trompe pas, ce serait réglé par les différentes propositions que j'ai faites. Cela correspond à peu près au "cas problème n ° 3" de mon premier article de blog . L'idée de base (en termes de formulation du dernier article de blog ) de la raison pour laquelle cela fonctionnerait est que le sous-typage de l'emprunt ne serait nécessaire que jusqu'à la fin de 'a dans la branche de if qui provoque un return .

@nikomatsakis Pourriez-vous nous expliquer comment NLL interagit avec ce problème? Dans mon modèle mental de références &mut , elles peuvent être passées soit par déplacement, soit par réemprunt , et le problème dans ce problème est que le retour nécessite le mode déplacement et la réutilisation de self nécessite ensuite un réemprunt . À mon avis, la durée de vie de la nouvelle référence &mut réempruntée est limitée par la durée de vie de la variable contenant la référence &mut - dans ce cas, la variable self , elle est donc limitée par le corps de la fonction, il ne peut donc pas s'étendre en dehors de l'appel de fonction. La NLL va-t-elle modifier cette limitation des réemprunts (ou peut-être n'y a-t-il pas une telle limitation)?

Je me demande également si la résolution de ce problème est quelque chose de intrinsèquement lié à NLL ou est-ce peut-être orthogonal? Si c'est le dernier, peut-être vaut-il la peine de poser un correctif avant NLL?

De plus, si NLL résout ce problème, cela signifie-t-il que sous NLL, vous n'aurez jamais à choisir manuellement entre un déplacement et un réemprunt?

Cela va-t-il être réglé de sitôt?

@krdln

Pourriez-vous s'il vous plaît expliquer comment NLL interagit avec ce problème?

Le résumé est que, aujourd'hui, si une valeur est renvoyée par la fonction sur un chemin, le prêt doit être valide pour le reste de la fonction sur tous les chemins. Selon la proposition NLL que j'ai rédigée, cette restriction est levée.

Plus en détail, si vous prenez la signature élaborée ici, vous pouvez voir que nous devons renvoyer une référence à vie 'a :

fn two<'a>(&'a mut self) -> &'a i32 { .. }

observez maintenant que nous appelons let k = self.one() puis retournons ce k . Cela signifie que le type de k doit être &'a i32 . Cela signifie à son tour que lorsque nous appelons self.one() , nous devons lui fournir un emprunt de self de type &'a mut Self . Par conséquent, au let k = self.one() nous émettons un prêt de self à vie 'a . Cette durée de vie est plus grande que la boucle (et, en fait, la fonction entière), elle persiste donc lorsque nous faisons le tour de la boucle, conduisant au rapport d'erreur. Selon les règles NLL que j'ai proposées, la durée de vie de k n'est requise pour s'étendre à 'a si le if est pris. Par conséquent, si le if n'est pas pris, le prêt peut se terminer plus tôt.

Est ce que ça aide?


@ osa1

Cela va-t-il être réglé de sitôt?

Il reste encore quelques étapes à franchir avant de pouvoir le faire, mais il y a un travail actif pour faire les refactorisations nécessaires.

@nikomatsakis
Merci, cela a quelque peu aidé. Mais il y a une petite chose que je ne comprends pas - ce qui se passe exactement lorsque vous écrivez self dans votre code - comment fonctionne le reborrow de self . J'ai lu votre explication du «cas problème 3» ( get_default ), où vous insérez le code à l'intérieur de l'appelant, mais là, vous avez changé chaque utilisation de self en empruntant le map directement, de sorte que la désuétude ne m'a pas éclairci.

Voici où je suis bloqué: lorsque nous appelons let k = self.one() , le self ne peut pas être déplacé (car il est nécessaire plus tard), donc il est considéré comme emprunté. Plus tard, nous retournons conditionnellement ce k , de sorte que l'emprunt doit avoir une durée 'a vie self , qui ne vit que jusqu'à la fin de la fonction. Cette limitation semble raccourcir 'a , donc dans mon modèle mental, même sous NLL, nous devrions obtenir l'erreur "ne vit pas assez longtemps".

@krdln nous avons en fait emprunté à *self - c'est-à-dire que nous avons réemprunté ce à quoi self se réfère. Nous vous permettons d'emprunter *self pour toute une vie survivant self lui-même car le type de self implique un «verrou» pour toute la vie 'a . Par conséquent, tant que nous pouvons garantir que pendant 'a , self n'est plus utilisé, le résultat devrait être une référence exclusive - dans ce cas, après le retour de fn, self a été sauté, et par conséquent, il pourrait ne pas être utilisé, il suffit donc de garantir que self ne sera pas utilisé avant la fin de la fn. (Au moins j'espère que c'est vrai. =)

Ceci est bloqué sur les durées de vie non lexicales, ce qui est à son tour bloqué sur MIR. Idéalement, cela sera corrigé d'ici la fin de 2016, mais un correctif ne sera pas bientôt disponible.

Pas très surprenant, mais j'ai confirmé que l'exemple initial se compile effectivement

Je pense que j'ai rencontré un problème similaire en production où un emprunt immuable semble vivre trop longtemps ( solution de contournement ).

Je devrais également mentionner que le code de starwed ne se compile plus le dernier soir. @oberien a suggéré une regression-from-nightly-to-nightly ", et il m'a également été suggéré de taguer @nikomatsakis au cas où ce serait un problème urgent :)

Je suis tombé sur ce problème en essayant de faire quelque chose de très similaire (ce qui n'est pas autorisé par nll actuel):

fn f(vec: &mut Vec<u8>) -> &u8 {
    if let Some(n) = vec.iter_mut().find(|n| **n == 1) {
        *n = 10;
        n
    } else {
        vec.push(10);
        vec.last().unwrap()
    }
}

fn main() {
    let mut vec = vec![1, 2, 3];
    f(&mut vec);
}
error[E0499]: cannot borrow `*vec` as mutable more than once at a time
 --> src/main.rs:6:9
  |
1 | fn f(vec: &mut Vec<u8>) -> &u8 {
  |           - let's call the lifetime of this reference `'1`
2 |     if let Some(n) = vec.iter_mut().find(|n| **n == 1) {
  |                      --- first mutable borrow occurs here
3 |         *n = 10;
4 |         n
  |         - returning this value requires that `*vec` is borrowed for `'1`
5 |     } else {
6 |         vec.push(10);
  |         ^^^ second mutable borrow occurs here

error[E0502]: cannot borrow `*vec` as immutable because it is also borrowed as mutable
 --> src/main.rs:7:9
  |
1 | fn f(vec: &mut Vec<u8>) -> &u8 {
  |           - let's call the lifetime of this reference `'1`
2 |     if let Some(n) = vec.iter_mut().find(|n| **n == 1) {
  |                      --- mutable borrow occurs here
3 |         *n = 10;
4 |         n
  |         - returning this value requires that `*vec` is borrowed for `'1`
...
7 |         vec.last().unwrap()
  |         ^^^ immutable borrow occurs here

(nous aurions dû supprimer E-needstest lorsque nous avons supprimé NLL-fixed-by-NLL)

Cette page vous a été utile?
0 / 5 - 0 notes