Enfreindre un fondement de Rust ?

a marqué ce sujet comme résolu.

Bonjour,

J’en avait un peu marre de suivre le book, j’ai donc décidé de prendre une petite pause en me lançant un défi avec Rust, le Javaquarium (dans mon cas : Rusted Tank).

Jusqu’à l’étape où il faut que les poissons mangent, je n’avais pas de problème. Je pense m’être bien débrouillé avec les références et tout ça. Le code ce trouve sur Github. Bon maintenant il faut que mes poissons puissent manger. Et là je bloque totalement puisque j’ai besoin d’avoir une référence mutable de mon poisson et de pouvoir le comparer avec les autres poissons d’un vecteur (et donc d’y accéder par référence) ce qui est impossible puisque Rust dit bien dès le départ qu’il ne peut y avoir qu’une seule référence à la fois si celle-ci est mutable. Je vous montre la fonction qui me permet de passer un tour dans mon aquarium :

pub fn next(&mut self) {
    self.round += 1;

    if !self.fishes.is_empty() {
        for fish in &mut self.fishes {  // Ici fish est une référence mutable, j'ai besoin de le modifier
            if fish.is_alive() {
                fish.take_damage(1);  // Je le modifie
                fish.age();  // Ici aussi

                if fish.is_hungry() {
                    match fish.get_diet() {
                        Diet::Carnivorous => {
                            let food = thread_rng().gen_range(0, self.fishes.len()); // len() essaye d'accéder à une référence non mutable de self.fishes, forcément erreur
                            
                            if &mut self.fishes[food] != fish { // J'ai l'ai mis en référence mutable pour pouvoir comparer avec l'autre fish mais bon, j'ai une erreur...

                            }

                        },
                        Diet::Herbivorous => {
                            // pick a random seaweed
                        }
                    }
                }
            }
        }
    }
}

J’ai mis des commentaires pour que vous suiviez et aussi pour que vous me disiez si mon raisonnement est correct. :lol:

Bref. J’imagine que c’est la logique de la fonction qui n’est pas bonne, je ne pense bien qu’il y a un moyen de le faire avec Rust, mais je ne vois pas comment…

Merci à vous !

+0 -0

Je ne vois pas trop la différence entre for fish in &mut self.fishes et for fish in self.fishes.iter_mut(). J’obtiens un &mut fish::Fish dans les deux cas. Et c’est ce dont j’a besoin. Mais j’ai aussi besoin de récupérer une référence non mutable de self.fishes en même temps ce qui n’est pas possible j’ai l’impression.

Je ne vois donc pas comment faire..

En fait, la bibliothèque standard de Rust te permet déjà de faire ce genre de choses avec les RefCell que tu vas découvrir dans le chapitre du book sur les pointeurs intelligents.

Cela consiste à déporter la vérification de la règle des emprunts à l’exécution plutôt qu’à la compilation : en gros ça permet de placer un objet dans une "cellule" qui, même si elle est immutable de l’extérieur, te permet de faire ponctuellement un emprunt mutable de son contenu.

En ce qui concerne ton problème particulier j’avoue que je n’ai pas trop regardé, par contre.

+0 -0

À noter que tu peux mettre self.fishes.len() en dehors de ta boucle pour éviter l’emprunt pendant l’écriture ici. Rc ne fonctionnera pas ici parce que tu veux faire plusieurs emprûnt, une solution serait donc de le faire en plusieurs fois.

pub fn next(&mut self) {
    self.round += 1;

    // on peut noter que c'est en doublon avec le reste
    if self.fishes.is_empty() {
        return;
    }

    self.fishes
        .iter_mut()
        .filter(|fish| fish.is_alive())
        .foreach(|fish| {
            fish.take_damage(1);
            fish.age();
        });

    // On a plus de référence mutable sur fishes, donc on peut refaire des références immutables

    let meals = self.fishes
        .iter()
        .filter(|fish| fish.is_alive() && fish.is_hungry())
        .map(|fish| match fish.get_diet() {
            Diet::Carnivorous => {
                let food = thread_rng().gen_range(0, self.fishes.len());
                EatFish(food) },
            Diet::Herbivorous => {
                let food = thread_rng().gen_range(0, self.seaweeds.len());
                EatSeaweed(food) },
            }
        })
        .collect::<Vec<_>>();

    // Do something with meals
}
+3 -0

Salut @Wizix,

Pour compléter mes vdd: tu as un style beaucoup trop impératif. Ta solution bloque parce que tu ne prends pas en compte la notion de scope de Rust (contrairement à l’exemple de unidan qui cloisonne correctement les accès). En fait, tu conserves beaucoup trop longtemps certains accès qui te deviennent inutiles et font donc râler le compilateur.

Rien de très grave, mais je pense que tu devrais penser davantage fonctionnel et voir comment "scoper" chacune de tes ressources et accès pour éviter ce genre de surprises.

Je ne sais pas si je suis très clair, donc n’hésite pas à me reprendre sur des points que tu ne comprends pas.

EDIT: je n’ai pas testé l’exemple, mais je ne suis pas sûr que .collect::<vec<_>>(); compile @unidan. .collect::<Vec<_>>(); plutôt, non ?

En te souhaitant une bonne journée.

+1 -0

Merci pour vos réponses et désolé pour le retard de la mienne !

En effet, je n’ai jamais fait de fonctionnel donc j’ai encore du mal à me séparer de l’impératif…

@unidan j’essaye de comprendre ta solution mais je vois pas trop ce que tu proposes. Voici ce que j’ai fait :

fn feed_fishes(&mut self) {
    let meals = self.fishes
                    .iter()
                    .filter(|fish| fish.is_alive() && fish.is_hungry())
                    .map(|fish| match fish.get_diet() {
                        Diet::Carnivorous => {
                            let meal = thread_rng().gen_range(0, self.fishes.len());
                        },
                        Diet::Herbivorous => {
                            let meal = thread_rng().gen_range(0, self.seaweeds.len());
                        }
                    })
                    .collect::<Vec<_>>();
    println!("{:#?}", meals);
}

Dans le match, j’imagine qu’il faut que je retourne la nourriture pour l’avoir dans meals. Problème, je vais me retrouver avec un mélange de deux types, ce qui n’est pas possible : seaweed et fish. J’ai vu que tu retournais le résultat de deux fonctions EatSeaweed(food) et EatFish(food) que sont-elles sensées faire ? Parce que je ne peux pas modifier self.fishes[food] (et donc impossible de lui retirer de la vie).

Je vois mal où tu veux en venir.

Merci encore pour votre aide, j’apprends beaucoup !

+0 -0

À la syntaxe employée, je pense qu'@unidan suggérait que ce soit une énumération intermédiaire (plutôt qu’une fonction). Ce qui ne pose aucun problème de type, pour le coup. Quelque chose du style :

enum Action {
    EatSeaweed(weed_id: usize),
    EatFish(fish_id: usize),
}
+0 -0

À la syntaxe employée, je pense qu'@unidan suggérait que ce soit une énumération intermédiaire (plutôt qu’une fonction). Ce qui ne pose aucun problème de type, pour le coup. Quelque chose du style :

enum Action {
    EatSeaweed(weed_id: usize),
    EatFish(fish_id: usize),
}

nohar

Exactement.

Dans l’idée, tu ne peux pas avoir une référence vers un élément de ton vec et une référence mutable vers ton vec en même temps. L’idée est donc de sélectionner tout ce qui sera mangé.

Tu pourras ensuite filtrer les EatFish, ordonner par indice décroissant et supprimer avec swap_remove par exemple pour être efficace.

Si tu ne veux pas utiliser swap_remove et en particulier ne pas trier ta liste, la complexité sera moins bonne, mais tu peux quand même faire ta liste d’indice à supprimer puis filter tous les éléments de ton vecteur en fonction de si l’indice est dans la liste ou non (en utilisant into_iter().enumerate() par exemple pour récupérer l’indice, puis .collect() pour récupérer un vec à la fin).

Connectez-vous pour pouvoir poster un message.
Connexion

Pas encore membre ?

Créez un compte en une minute pour profiter pleinement de toutes les fonctionnalités de Zeste de Savoir. Ici, tout est gratuit et sans publicité.
Créer un compte