简体   繁体   中英

Converting {integer} to f32 in Rust

I want to convert a value from {integer} to 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
        }
    }
}

The compiler chokes on this with a type mismatch error (expecting f32 but getting {integer} ). Unfortunately I can not simply change Vector3 . I'm feeding a C-API with this.

Is there any easy and concise way I can convert x , y and z from {integer} to f32 ?

I guess there is no builtin conversion from i32 or {integer} to f32 because it could be lossy in certain situations. However, in my case the range I'm using is so small that this wouldn't be an issue. So I would like to tell the compiler to convert the value anyways.

Interestingly, the following works:

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

I'm using a lot more that just one foo and one x so this turns hideous really fast.

Also, this works:

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

But with my usecase this turns into:

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
        }
    }
}

Which I think is pretty unreadable and an unreasonable amount of cruft for a simple conversion.

What am I missing here?

I'm not sure why you felt the need to specify the i32 s when using as , since this works fine ( playground ):

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.
        }
    }
}

As Klitos Kyriacou's answer observes, there is no such type as {integer} ; the compiler gives that error message because it couldn't infer a concrete type for x . It doesn't actually matter, because there are no implicit conversions from integer types to floating-point types in Rust, or from integer types to other integer types, for that matter. In fact, Rust is quite short on implicit conversions of any sort (the most notable exception being Deref coercions).

Casting the type with as permits the compiler to reconcile the type mismatch, and it will eventually fill in {integer} with i32 ( unconstrained integer literals always default to i32 , not that the concrete type matters in this case).

Another option you may prefer, especially if you use x , y and z for other purposes in the loop, is to shadow them with f32 versions instead of creating new names:

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.
        }
    }
}

(You don't have to write x: x, y: y, z: z -- Rust does the right thing when the variable name is the same as the struct member name.)

Another option (last one, I promise) is to convert the iterators instead using 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.
        }
    }
}

However it is a little more dense and may be harder to read than the previous version.

The only integer types available are i8 , i16 , i32 , etc. and their unsigned equivalents. There is no such type as {integer} . This is just a placeholder emitted by the compiler before it has determined the actual type by inference from the whole-method context.

The problem is that, at the point where you call Vector3 {x: x as f32, y: y as f32, z: z as f32} , it doesn't yet know exactly what x, y and z are, and therefore doesn't know what operations are available. It could use the operations given to determine the type, if it was a bit more intelligent; see bug report for details.

There is a conversion from i32 to f32 , so you should be able to do this:

let foo = Vector3 {x: (x as i32) as f32, y: (y as i32) as f32, z: (z as i32) as f32};

Since everyone else is answering, I'll chime in with an iterator-flavored solution. This uses Itertools::cartesian_product instead of the for loops:

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 });
}

Unfortunately, closures don't yet implement Clone , so I used a small function to perform the mapping. These do implement Clone .

If you wanted a helper method:

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 });
}

From<i16> is implemented for f32 .

So it should be possible to

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
        }
    }
}

Of course if your iteration uses values bigger than i16 ( i32 or i64 ) this is no longer possible in a safe way and you have to try another way.

As many problems in Computer Science, it can be solved by applying another layer of indirection.

For example, defining a constructor for 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);
            }
        }
    }
}

You can use a plethora of other methods (generics, builders, etc...); but a good old constructor is just the simplest.

Another solution this time using a function and traits. playground

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
            }
        }
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM