Je souhaite convertir une valeur de {integer} en f32:

struct Vector3 {
    pub x: f32,
    pub y: f32,
    pub z: f32,
}

for x in -5..5 {
    for y in -5..5 {
        for z in -5..5 {
            let foo: Vector3 = Vector3 { x: x, y: y, z: z };
            // do stuff with foo
        }
    }
}

Le compilateur s'étouffe avec une erreur de non-concordance de type (s'attendant à f32 mais obtenant {integer}). Malheureusement, je ne peux pas simplement changer Vector3. J'alimente une C-API avec ça.

Existe-t-il un moyen simple et concis de convertir x, y et z de {integer} en f32?

Je suppose qu'il n'y a pas de conversion intégrée de i32 ou {integer} à f32 car elle pourrait être avec perte dans certaines situations. Cependant, dans mon cas, la plage que j'utilise est si petite que ce ne serait pas un problème. Je voudrais donc dire au compilateur de convertir la valeur de toute façon.

Fait intéressant, les travaux suivants:

for x in -5..5 {
    let tmp: i32 = x;
    let foo: f32 = tmp as f32;
}

J'utilise beaucoup plus qu'un seul foo et un x, donc ça devient hideux très vite.

En outre, cela fonctionne:

for x in -5i32..5i32 {
    let foo: f32 = x as f32;
    // do stuff with foo here
}

Mais avec mon cas d'utilisation, cela se transforme en:

for x in -5i32..5i32 {
    for y in -5i32..5i32 {
        for z in -5i32..5i32 {
            let foo: Vector3 = Vector3 {
                x: x as f32,
                y: y as f32,
                z: z as f32,
            };
            // do stuff with foo
        }
    }
}

Ce que je pense est assez illisible et une quantité déraisonnable de cruauté pour une simple conversion.

Qu'est-ce que j'oublie ici?

2
MadMonkey 16 nov. 2017 à 15:56

6 réponses

Meilleure réponse

Je ne sais pas pourquoi vous avez ressenti le besoin de spécifier les i32 lorsque vous utilisez as, car cela fonctionne bien (terrain de jeu):

for x in -5..5 {
    for y in -5..5 {
        for z in -5..5 {
            let foo = Vector3 { // no need to specify the type of foo
                x: x as f32,
                y: y as f32,
                z: z as f32,
            };
            // etc.
        }
    }
}

Comme le fait remarquer la réponse de Klitos Kyriacou, il n'existe pas de type tel que {integer}; le compilateur donne ce message d'erreur car il n'a pas pu déduire un type concret pour x. Cela n'a pas vraiment d'importance, car il n'y a pas de conversions implicites entre les types entiers et les types à virgule flottante dans Rust, ou entre les types entiers et d'autres types d'entiers, d'ailleurs. En fait, Rust est assez court sur les conversions implicites de toute sorte (l'exception la plus notable étant les coercitions Deref).

Le cast du type avec as permet au compilateur de réconcilier l'incompatibilité de type, et il finira par remplir {integer} avec i32 (les littéraux entiers sans contrainte sont toujours par défaut à i32, pas que le type concret importe dans ce cas).

Une autre option que vous préférerez peut-être, surtout si vous utilisez x, y et z à d'autres fins dans la boucle, consiste à les observer avec des versions f32 au lieu de créer de nouveaux noms :

for x in -5..5 {
    let x = x as f32;
    for y in -5..5 {
        let y = y as f32;
        for z in -5..5 {
            let z = z as f32;
            let foo = Vector3 { x, y, z };
            // etc.
        }
    }
}

(Vous n'avez pas besoin d'écrire x: x, y: y, z: z - Rust fait ce qu'il faut lorsque le nom de la variable est le même que le nom du membre de la structure.)

Une autre option (la dernière, je le promets) est de convertir les itérateurs à la place en utilisant map:

for x in (-5..5).map(|x| x as f32) {
    for y in (-5..5).map(|y| y as f32) {
        for z in (-5..5).map(|z| z as f32) {
            let foo = Vector3 { x, y, z };
            // etc.
        }
    }
}

Cependant, il est un peu plus dense et peut être plus difficile à lire que la version précédente.

8
trentcl 16 nov. 2017 à 16:23

Une autre solution cette fois en utilisant une fonction et des traits. aire de jeux

struct Vector3 {
    pub x: f32,
    pub y: f32,
    pub z: f32,
}

impl Vector3 {
    pub fn new<T: Into<f32>>(a: T, b: T, c: T) -> Vector3 {
        Vector3 {
            x: a.into(),
            y: b.into(),
            z: c.into(),
        }
    }
}

fn main() {
    for x in -5..5i8 {
        for y in -5..5i8 {
            for z in -5..5i8 {
                let foo: Vector3 = Vector3::new(x, y, z);
                // do stuff with foo
            }
        }
    }
}
1
Shepmaster 16 nov. 2017 à 15:09

Comme de nombreux problèmes en informatique, il peut être résolu en appliquant une autre couche d'indirection.

Par exemple, définir un constructeur pour Vec3:

impl Vec3 {
    fn new(x: i16, y: i16, z: i16) -> Vec3 {
        Vec3 { x: x as f32, y: y as f32, z: z as f32 }
    }
}

fn main() {
    for x in -5..5 {
        for y in -5..5 {
            for z in -5..5 {
                let foo = Vector3::new(x, y, z);
                println!("{:?}", foo);
            }
        }
    }
}

Vous pouvez utiliser une pléthore d'autres méthodes (génériques, constructeurs, etc ...); mais un bon vieux constructeur est juste le plus simple.

2
Matthieu M. 16 nov. 2017 à 14:20

From<i16> est implémenté pour f32.

Il devrait donc être possible de

for x in -5..5 {
    for y in -5..5 {
        for z in -5..5 {
            let foo: Vector3 = Vector3 {
                 x: f32::from(x),
                 y: f32::from(y),
                 z: f32::from(z),
            };
            // do stuff with foo
        }
    }
}

Bien sûr, si votre itération utilise des valeurs plus grandes que i16 (i32 ou i64), ce n'est plus possible de manière sûre et vous devez essayer une autre manière.

2
musicmatze 16 nov. 2017 à 13:02

Puisque tout le monde répond, je vais intervenir avec une solution à saveur d'itérateur. Cela utilise Itertools::cartesian_product à la place des boucles for:

extern crate itertools;

use itertools::Itertools;

fn main() {
    fn conv(x: i32) -> f32 { x as f32 }

    let xx = (-5..5).map(conv);
    let yy = xx.clone();
    let zz = xx.clone();

    let coords = xx.cartesian_product(yy.clone().cartesian_product(zz));
    let vectors = coords.map(|(x, (y, z))| Vector3 { x, y, z });
}

Malheureusement, les fermetures ne sont pas encore implémentées Clone, j'ai donc utilisé une petite fonction pour effectuer le mappage. Celles-ci mettent en œuvre Clone.

Si vous vouliez une méthode d'assistance:

extern crate itertools;

use itertools::Itertools;
use std::ops::Range;

fn f32_range(r: Range<i32>) -> std::iter::Map<Range<i32>, fn(i32) -> f32> {
    fn c(x: i32) -> f32 { x as _ }
    r.map(c)
}

fn main() {
    let xx = f32_range(-5..5);
    let yy = f32_range(-5..5);
    let zz = f32_range(-5..5);

    let coords = xx.cartesian_product(yy.cartesian_product(zz));
    let vectors = coords.map(|(x, (y, z))| Vector3 { x, y, z });
}
3
Shepmaster 16 nov. 2017 à 14:45

Les seuls types d'entiers disponibles sont i8, i16, i32, etc. et leurs équivalents non signés. Il n'existe pas de type tel que {integer}. Il s'agit simplement d'un espace réservé émis par le compilateur avant qu'il n'ait déterminé le type réel par inférence à partir du contexte de la méthode entière.

Le problème est qu'au moment où vous appelez Vector3 {x: x as f32, y: y as f32, z: z as f32}, il ne sait pas encore exactement ce que sont x, y et z et ne sait donc pas quelles opérations sont disponibles. Il pourrait utiliser les opérations données pour déterminer le type, s'il était un peu plus intelligent; voir rapport de bogue pour plus de détails.

Il y a une conversion de i32 à f32, vous devriez donc pouvoir faire ceci:

let foo = Vector3 {x: (x as i32) as f32, y: (y as i32) as f32, z: (z as i32) as f32};
3
Shepmaster 16 nov. 2017 à 14:26
47330241