0

So I have the following snippet of code where I want to accept a Vecof arbitrary values and cast those values into f32. This is to work with floats, ints, unsigned values, etc.

pub struct Vals {
    // Our concrete values
    mat: Vec<Vec<f32>>,
    shape: (usize, usize),
    name: String,
}

impl Vals {
    pub fn from_vec<T: Into<f32>>(vec: Vec<T>, name: Option<String>) -> Self {
        let s1 = vec.iter().len();
        let mut as_mat: Vec<Vec<f32>> = Vec::new();

        let recasted = vec.iter().map(|&x| x as f32).collect(); 

        as_mat.push(vec);

        Vals {
            mat: as_mat,
            shape: (s1, 1),
            name: name.unwrap_or(String::from("")),
        }
    }

I'm running into an issue on the following line:

let recasted = vec.iter().map(|&x| x as f32).collect(); where it is telling me an as expression can only be used to convert between primitive types or to coerce to a specific trait object, but I thought that my specifying the Into trait would have handled that issue?

I've read through the From and Into Rust Documentation, but nothing there seems to help me address my issue. I see that it mentions how to create itself from another type, hence providing a very simple mechanism for converting between several types but I would have thought I could handle this. I'm mostly looking for advice on either restructuring my code to make it possible, or directly addressing the bug.

Thank you!


Edit: I've restructured the from_vec function with the recommendation from @drewtato and now have


    pub fn from_vec<T: Into<f32>>(vec: Vec<T>, name: Option<String>) -> Self {
        // `Vec` has a `len` method already
        let s1 = vec.len();
        let recasted = vec.into_iter().map(|x| x.into()).collect();
        // Don't need to create the empty `Vec`
        let mat = vec![recasted];

        Vals {
            mat,
            shape: (s1, 1),
            name: name.unwrap_or_default(),
        }
    }

but in my test case I've got

#[cfg(test)]
mod tests {
    use super::*;
    
    #[test]
    fn test_vec(){
        let v1= vec![1,2,3];
        let res = Vals::from_vec(v1, None);
    }
}

but it doesn't seem to be working the way I intend. I'm getting

the trait bound `f32: std::convert::From<i32>` is not satisfied
[E0277] the trait `std::convert::From<i32>` is not implemented for 
`f32` Help: the following other types implement trait 
`std::convert::From<T>`: <f32 as std::convert::From<bool>> <f32 as 
std::convert::From<i16>> <f32 as std::convert::From<i8>> <f32 as 
std::convert::From<u16>> <f32 as std::convert::From<u8>> Note: 
required for `i32` to implement `std::convert::Into<f32>` Note: 
required for `f32` to implement `std::convert::TryFrom<i32>`

which I don't understand. I'm reading this as the cast is not implemented, but if I were to implement the cast wouldn't that defeat the purpose of the generic?

IanQ
  • 1,831
  • 5
  • 20
  • 29
  • Related: https://stackoverflow.com/q/55867367/5397009 – Jmb May 02 '23 at 07:14
  • and also: https://stackoverflow.com/a/35766805/5397009 – Jmb May 02 '23 at 07:16
  • 2
    @IanQ ordinarily I'd roll back your edits since they invalidate some answers and that's not allowed for edits, bit I can't since that now would invalidate drewtatos answer, in the future please create a new question and link to the old one if applicable. – cafce25 May 02 '23 at 18:20

2 Answers2

4

as is a special builtin that can convert primitives and a few other things, but it's not connected to any trait, so you can't use it on generics. This is unlike nearly all other operators, which are connected to a trait. + corresponds to the std::ops::Add trait, * is the std::ops::Deref trait, and even for loops use the std::iter::IntoIterator trait.

Into is a totally normal trait, not connected to any operator syntax. Being a trait, you can use it to restrict generics, which you've done, and then use its methods on generic values. But it does not know about as at all.

In order to use your T: Into, you need to use Into::into:

let recasted = vec.into_iter().map(|x| x.into()).collect();

If you want to be able to take any value that can be converted with as, you can use the num-traits crate, which provides the AsPrimitive trait. That trait still doesn't know what as is, but they have a method as_ which has been implemented for every possible conversion to call as internally. x.as_() is roughly equivalent to x as _.

use num_traits::AsPrimitive;

impl Vals {
    pub fn from_vec<T: AsPrimitive<f32>>(vec: Vec<T>, name: Option<String>) -> Self {
    ...
        let recasted = vec.into_iter().map(|x| x.as_()).collect();
    ...
    }
}

This is slightly different from Into, since you can convert from f64 to f32 with as but not Into, and types outside the standard library may implement Into<f32> but would rarely implement AsPrimitive.


Your other error is because Into::into takes ownership of the value it's converting. For types that implement Copy, like the number primitives, you can just dereference them to get an owned value. You can make that work in your function by further restricting the generic.

pub fn from_vec<T: Into<f32> + Copy>

Note that if you do that, you should take your parameter by reference.

(vec: &[T], name: Option<String>)

The other option is that if you want to be able to accept values that are not Copy, you can use into_iter to have owned values all the way through. I used that in the first snippet, and here it is again.

let recasted = vec.into_iter().map(|x| x.into()).collect();

However, this restricts you into taking ownership of Vec, and can't use &[T] in its place.

There's some more changes you can make that are direct equivalents. Here's the whole function cleaned up.

pub fn from_vec<T: Into<f32>>(vec: Vec<T>, name: Option<String>) -> Self {
    // `Vec` has a `len` method already
    let s1 = vec.len();
    let recasted = vec.into_iter().map(|x| x.into()).collect();
    // Don't need to create the empty `Vec`
    let mat = vec![recasted];

    Vals {
        // This is the same as `mat: mat,`
        mat,
        shape: (s1, 1),
        // The default of `String` is an empty string
        name: name.unwrap_or_default(),
    }
}
drewtato
  • 6,783
  • 1
  • 12
  • 17
  • Wow, than you for your answer! That was quite enlightening. As a follow up, I've updated my question. Would you be able to take a look at the bottom and give me an idea of how to move forward? – IanQ May 02 '23 at 04:01
  • Thank you so much! One last thing - I added an edit to my question, but TL;DR the tests don't seem to want to work with the code you have posted. Could you share your insight? – IanQ May 02 '23 at 04:49
  • The one with `&[T]`? You would need to call it like `from_vec(&vec, None)` or `from_vec(vec.as_slice(), None)` instead of `from_vec(vec, None)`. @IanQ – drewtato May 02 '23 at 05:00
  • Hmm, I don't see a `&[T]`. I'm referring to the most snippet you had pasted. – IanQ May 02 '23 at 05:19
  • I have no idea then. It's likely that the problem lies elsewhere. – drewtato May 02 '23 at 05:23
1

You can use into to convert your values into f32

let recasted: Vec<f32> = vec.into_iter().map(|x| x.into()).collect();