2017-07-26 2 views
2
let array = [40]; 
let mut var = 60; 

for element in array.iter().filter(|&x| {*x < var}) { 
    var += 1; // Error 
} 

var += 1; // Fine again 

Pour moi, ce code semble tout à fait légitime, que la fermeture devrait être terminée au moment où je réellement accès var en dehors de celui-ci.Closures conservent la propriété des variables locales plus longtemps que prévu

error[E0506]: cannot assign to `var` because it is borrowed 
--> src/main.rs:6:9 
    | 
5 |  for element in array.iter().filter(|&x| {*x < var}) { 
    |          ---- borrow of `var` occurs here 
6 |   var += 1; // Error 
    |   ^^^^^^^^ assignment to borrowed `var` occurs here 

Pourquoi var encore empruntèrent lors de l'appel var += 1 même si la portée de la fermeture devrait être déjà terminée? Le résultat est nécessaire pour accéder à var += 1. Alors qu'il est possible de faire quelque chose comme ça sans filter, cela rend mon code beaucoup moins clair, donc je voudrais continuer à l'utiliser.

Répondre

4

la fermeture devrait être terminée au moment où je réellement accès

No. Iterators are lazy. Cela signifie que l'ordre des opérations est ici:

  1. Nous appelons next sur l'itérateur combiné.
  2. Ceci appelle next sur l'itérateur de la matrice. Répétez cette étape jusqu'à ce que la condition soit passée.
  3. Exécute le corps de la boucle avec la valeur.
  4. Répétez l'ensemble du processus.

Vous capturez val à l'intérieur de la fermeture du filtre. Vous essayez également de le modifier dans la boucle. Cela voudrait dire qu'il doit y avoir une référence mutable et une référence immuable en même temps, ce qui est interdit.

Vous pouvez utiliser Cell avoir intérieur mutabilité:

use std::cell::Cell; 

fn main() { 
    let array = [40]; 
    let var = Cell::new(60); 

    for element in array.iter().filter(|&&x| x < var.get()) { 
     var.set(var.get() + 1); 
    } 

    let mut var = var.get(); 
    var += 1; 
} 
+0

Donc, si je comprends bien tout, l'utilisation de filter() crée une nouvelle structure contenant la fermeture (et l'emprunt val) qui existe jusqu'à ce qu'elle soit hors de portée, ce qui arrive une fois que l'itérateur est appelé La boucle est terminée et la structure du filtre est hors de portée, ce qui signifie qu'elle arrête d'emprunter val et que je peux la muter de nouveau. Dans le cas où cela est vrai, je comprends enfin comment fonctionnent les itérateurs. Merci, et sry pour mon mauvais anglais, j'écris ceci sur mon téléphone. – SleepingPanda

+1

@SleepingPanda oui. Il crée un ['Filter'] (https: //doc.rust-lang.org/std/iter/struct.Filter.html) struct qui contient la fermeture et la fermeture tente d'emprunter immuablement 'var'. Le corps de la boucle tente de muter 'var'. La propriété de cette structure est transférée à la boucle 'for'. – Shepmaster

2

La réponse de Shepmaster est correcte (je ne savais pas itérateurs étaient que paresseux), mais si vous ne voulez pas que le chèque var (la condition filter) changer au cours de la boucle, vous pouvez utiliser un move closure:

filter(move |&x| *x < var) 

Depuis i32 met en œuvre Copy, la valeur de var sera simplement copié dans le cadre de la fermeture.

+0

Cela change la logique (probablement souhaité). Je suppose que le filtre devrait "dynamiquement" mettre à jour à chaque itération. – Shepmaster

+0

@Shepmaster J'avais l'impression que l'on s'attendait à ce que lorsque le corps de la boucle est exécuté, le filtre a déjà fait son travail (d'où la question de savoir pourquoi l'emprunt est toujours en vigueur à l'intérieur). – ljedrz

+0

Im mon code réellement 'var' ne change pas, mais c'est une structure assez grande, donc l'utilisation de la copie serait un mauvais choix. Une fois que j'ai un peu de temps je vais tester la performance avec copie, car j'ai seulement besoin d'une partie de ma structure pour 'filter()'. Merci :) – SleepingPanda