9

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?

MadMonkey
  • 609
  • 1
  • 6
  • 13
  • So your biggest issue is the slightly more verbose syntax for type casting in Rust? If you split up the Vector declaration into lines then it probably wouldn't be as unreadable – jonny Nov 16 '17 at 13:09
  • 3
    My biggest issue is learning Rust. What I have so far seems overly complicated for a seemingly simple task so I'm just checking if I'm missing something or being completely unidiomatic here. – MadMonkey Nov 16 '17 at 13:55
  • 2
    I don't think you need the `i32` annotations there, it should default to `i32`. Did you try compiling without them (but with the explicit cast to `f32`)? – interjay Nov 16 '17 at 14:02
  • 1
    Imho rust types are supposed to be explicit in such places, and requiring a cast to `f32` is the reasonable thing to do (or write some custom iterator that allows for float types of start, end and step - have fun defining the exact semantics :) ). – Stefan Nov 16 '17 at 14:31
  • @MadMonkey: The double-casting (to `i16` then `i32`) seems unnecessary; you should be able to directly use `x as f32`. – Matthieu M. Nov 16 '17 at 14:52
  • @MatthieuM. Indeed I am. Don't know what I've been doing wrong before because I could swear it didn't work when I was writing the question. – MadMonkey Nov 16 '17 at 15:22

6 Answers6

13

It is not necessary to specify the i32s 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.

trent
  • 25,033
  • 7
  • 51
  • 90
4

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 });
}
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
3

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};
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Klitos Kyriacou
  • 10,634
  • 2
  • 38
  • 70
  • In my Rust (1.21) the conversion from i32 to f32 is not implemented, unfortunately. Only i16 (or smaller) to i32. – MadMonkey Nov 16 '17 at 13:41
  • 3
    @MadMonkey Conversion from primitive integer types to floating point types is not possible with `From` because `f32` is not a strict superset of `i32`. However, numeric casting with `as` is possible, and has always been since the first stable version of Rust. – E_net4 Nov 16 '17 at 13:47
  • 3
    It's not necessary to cast twice; the compiler will replace `{integer}` with a concrete type by itself. Just `x as f32` works. – trent Nov 16 '17 at 14:18
  • 1
    While your solution works, I would note that the original complaint was that the code to make it work was too verbose... and your code is *extremely* verbose :( – Matthieu M. Nov 16 '17 at 14:21
2

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.

musicmatze
  • 4,124
  • 7
  • 33
  • 48
  • 1
    Unfortunately the version of Rust that I use (1.21) insists that the range values are `i32` if I don't specify them explicitly as `-5i16..5i16`. – MadMonkey Nov 16 '17 at 13:47
  • 2
    @MadMonkey: It doesn't "insists", it "defaults". If rustc cannot infer an integer type, it defaults to `i32`. Note that specifying only *one* of the value types in the range would be sufficient; the other would be inferred to have the same type since ranges are homogeneous. – Matthieu M. Nov 16 '17 at 14:18
2

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.

Matthieu M.
  • 287,565
  • 48
  • 449
  • 722
  • 4
    The `Vec3` components are clearly meant to be of type `f32`, providing a constructor which takes `i16` (why `i16` and not any other integer type?) seems very wrong to me. – Stefan Nov 16 '17 at 14:21
  • @Stefan: If you have `f32`, you can use `Vec3 { x, y, z }` already, there's no need for any constructor then; you only need a constructor if you wish to apply further logic, such as conversion. I used `i16` because it's simpler here (it's the biggest signed type that is guaranteed to convert to `f32` cleanly); but as I noted you can use generics, like [this answer](https://stackoverflow.com/a/47332032/147192), I just didn't want to overload the OP with too much new information and keep to the simplest solution that worked for their issue (while hinting at more, if they are curious). – Matthieu M. Nov 16 '17 at 14:24
1

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
            }
        }
    }
}
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
user25064
  • 2,020
  • 2
  • 16
  • 28
  • but how to call something like `Vector3::new(1, 1, 1)`? It is interpreted as i32 and there's no `std::convert::From` for `f32` – tga Dec 29 '18 at 16:03
  • This is very interesting, I'm not sure why that impl doesn't exist. These are the ones that [exist](https://doc.rust-lang.org/std/primitive.f32.html#impl-From%3Cu8%3E). There is probably a good reason for that in rust as usual, but I can't think of it off of the top of my head. All i32 can be represented exactly within f32. – user25064 Jan 02 '19 at 13:20