Is it possible to check the data type of a variable?

I want to make a function that accepts some unsigned integer type (u8, u64, etc) and makes a temporary variable that is the same type. Is this possible?

Most trivially, you can clone an existing value to make a value with an identical type:

fn with_same_type<T: Clone>(value: T) {
    let tmp = value.clone(); // tmp also has type `T`
}

This works with all primitives (they are Copy too so you can be more restrictive for a tiny bit of implementation convenience). Or you can require Default to get the 0 value of the type. There are endless options, really.

3 Likes

This sounds a lot like an XY problem. Are you coming from a dynamically typed language like Python or JS? What are you actually trying to do?

10 Likes

Half-truth, unfortunately. There are endless options, it's really very true.

On the other hand precisely what people want from templates is not an option.

Most if these questions go back to what Ada, Extended Pascal, PL/I (not sure if Fortran supports it or not) and, of course C++ and Zig do easily and effortlessly: implement some algorithm generically for 2-3 fixed types with, maybe, some small divergence in one place or two.

Rust answer there is, basically, macros, even std does it this way (that's why we have NonZeroI8 and NonZeroIsize and, notably, don't have NonZero generic to be used as NonZero<i8> or NonZero<isize>.

Trying to do that with generics is not impossible, but very quickly turns your code into a mess where for every line of code which is doing some actual work there are ten if not hundred lines of boilerplate code which describes in types things which you don't want to describe at all (you just want to say: it's some kind of int with obvious operations available).

2 Likes

First, this is a gross exaggeration. There's certainly not hundreds of lines of boilerplate required to be generic over integers. There's like two.

Second, maybe you don't want to be explicit about your types. Then feel free to use C++ or Zig. But don't go around complaining about Rust's design here, and expect not to run into brick walls. If you think you know better and want to go against the last 50 years of industry experience, then best of luck with that.

Let's try not to go too deep into off-topic discussions in this thread; I agree with @mdHMUpeyf8yluPfXI that there's a good chance that being generic over integer types may not even be the best approach for the underlying problem that @reidiens is facing (“XY problem”), and even if it does end up the best approach for them, comparing to other programming languages, or addressing NonZero… is probably fairly irrelevant here :slight_smile:

3 Likes

If the compiler can deduce that the variable is initialized before being used, you can even create a temporary variable without any bounds (i.e. without needing Clone or Default):

fn foo<T>(arg: T, b: bool) -> T{
    let tmp: T;
    if b {
        tmp = arg;
    } else {
        tmp = arg;
    }
    tmp
}

fn main() {
    assert_eq!(foo(10, true), 10);
}

(Playground)


Yet another option is to use Option:

fn foo<T>(arg: T, b: bool) -> Option<T> {
    let mut tmp: Option<T> = None;
    if b {
        tmp = Some(arg);
    }
    tmp
}

fn main() {
    assert_eq!(foo(10, false), None);
    assert_eq!(foo(10, true), Some(10));
}

(Playground)

If you want to be generic about numeric types (and actually perform mathematical operations), you might want to check out the num crate, which, for example, lets you set a factor to one (using num::One::one or other conversion methods, such as num::NumCast::from).


Example:

fn fac<T>(mut arg: T) -> T
where
    T: num::Integer,
    T: std::ops::MulAssign + std::ops::SubAssign,
    T: Copy,
{
    let mut tmp: T = num::One::one();
    while arg > num::Zero::zero() {
        tmp *= arg;
        arg -= num::One::one();
    }
    tmp
}

fn main() {
    assert_eq!(fac(6), 720)
}

(Playground)

2 Likes

I'm coming from C, but I'm not like an expert at it. Still pretty new to the whole programming scene. What's an XY problem?
What i'm trying to do is make a function that gets accepts an integer pointer, gets numerical input from the user, and stores it in the pointer. I'm just using the method in the Rust book:

fn get_int(num: &[data type]) {
    let mut temp = String::new();

    io::stdin().read_line(&mut temp)
        .expect("something has gone horribly wrong");

    let num: [data type] = match temp.trim().parse { ... }
}

I'm not sure if this is the right way of going about this, but I am still new to Rust and I don't really have the most solid grasp on how things work yet.

@mdHMUpeyf8yluPfXI wanted to say that maybe your "root problem" is something else (see Wikipedia on "XY problem"). (This terminology isn't related to Rust at all.)

In order to solve your particular problem, it would be interesting to know what exactly you try to achieve.

As shown here, it's trivial to solve "making a temporary variable that is the same type" as a generic variable T. It's as easy as this:

The problem here is that you can't do much with arg and tmp (other than moving and dropping). Hence why you might want to add bounds as shown here. Perhaps if you explain to us the underlying problem, i.e. what you try to achieve in particular, we might be able to give more specific advice.

1 Like

The only purpose of the function I'm writing is to get an integer from the user. I just dont want to have to write

 io::stdin().read_line().expect()

every time.
The reason I want the variable to be of the same type as the pointer passed to the function is to save memory. I only want large values to be stored if I specify that I want a large value. To do that, the function would have to accept any size integer and check what the type is to return the correct value.

Is something like this what you're looking for?

use std::io;

fn read_and_parse<T:std::str::FromStr>()->T {
    let mut temp = String::new();

    io::stdin().read_line(&mut temp)
        .expect("something has gone horribly wrong");

    let trimmed = temp.trim();
    
    let Ok(val) = trimmed.parse() else {
        let tyname = std::any::type_name::<T>();
        panic!("Not a valid representation of {tyname}: {trimmed:?}");
    };
    
    val
}

You can then call it like this:

let x:i32 = read_and_parse();

7 Likes

Then you don't really want "a temporary variable" of any particular type. You want to parse a value from a string. (How specifically you can do that is already shown in the example above.)

@reidiens in case you wonder how to come up with the T: FromStr yourself for code like this, there are two ways. On one hand, if you don’t write it, the compiler often suggest good trait bounds by itself. E.g. in this case

   Compiling playground v0.0.1 (/playground)
error[E0277]: the trait bound `T: FromStr` is not satisfied
  --> src/lib.rs:11:27
   |
11 |     let Ok(val) = trimmed.parse() else {
   |                           ^^^^^ the trait `FromStr` is not implemented for `T`
   |
note: required by a bound in `core::str::<impl str>::parse`
  --> /rustc/84c898d65adf2f39a5a98507f1fe0ce10a2b8dbc/library/core/src/str/mod.rs:2352:5
help: consider restricting type parameter `T`
   |
3  | fn read_and_parse<T: std::str::FromStr>()->T {
   |                    +++++++++++++++++++

For more information about this error, try `rustc --explain E0277`.
error: could not compile `playground` due to previous error

On the other hand, looking at the parse method in question (which is essentially the only operation that needs to ”know“ anything about the particular type in this function), you can see in the docs that it requires FromStr for the returned type, and work based on this knowledge from the documentation to figure out the sensible trait bound yourself.


Note that this kind of approach (of just “writing the right signature”, and possibly relying on compiler suggestions) doesn’t always work; in particular for arithmetic on numbers (especially if the arithmetic involves more than the overloaded operators), trait bounds are hard/impossible to write, without going into solutions using the num crate that was already mentioned above (and writing the implementation/code also in a way that utilizes the methods from that crate’s traits).

1 Like

That's literally exactly what I wanted! thank you so much.

1 Like

Thank you so much for your help and explanations! Looks like I've got some studying up to do because i honestly don't even know what a bound is XD

1 Like

Generally, the Rust Book is a recommended read. It has a chapter about generics and traits which also features trait bounds. The book is of course somewhat of a serial affair, i.e. previous chapters may be useful for better understanding of the kinds of things used in these explanations of the 10th chapter :slight_smile:

Another place to read a little bit about some language features is also in the documentation pages of keywords in the standard library docs (you can search for “trait” of “fn” or even symbolic things like “&” in the search bar there, and often get useful results), e.g. here’s the one about “trait.

2 Likes

thanks so much! i'll get to reading the book and docs

2 Likes