1

I am trying to write a test to determine whether struct A has a property a and its type is i32.

pub struct A {
    a: i32,
}

#[test]
pub fn test_A() {
    assert!(A.hasattr("a"));
    assert_is_type!(A.a, i32);
}
Hao Li
  • 1,593
  • 15
  • 27
  • Does `a` has to be a string, or can it be a ident token? – mcarton Oct 07 '20 at 20:34
  • @mcarton Sorry for confusing. `a` doesn't have to be a string, it is just a place holder. – Hao Li Oct 07 '20 at 20:36
  • 1
    Related: [How to introspect all available methods and members of a Rust type?](/q/39266001/3650362) and [Is there is a way to get the field names of a struct in a macro?](/q/29986057/3650362) – trent Oct 07 '20 at 21:01

3 Answers3

6

In Rust, unlike some languages like Python, the types of all values must be known at compile time. This test doesn't make sense because in order to compile the code you must already know the answer.

If a field in a struct is optional, put it in an Option.

pub struct A {
    a: Option<i32>,
}

If you have common functionality between multiple structs, make a trait for it.

pub trait ATrait {
    fn get_a(&self) -> i32;
}
trent
  • 25,033
  • 7
  • 51
  • 90
Locke
  • 7,626
  • 2
  • 21
  • 41
  • It does make a lot of sense, and we actually want the compiler checking this during compile time. In my case, I need to make sure that a struct "inherits" from C struct for FFI. Specifically, I need to check that a type implements a trait AND it has exactly the first field of an expected type AND it should have repr(C). traits don't allow expressing all of these, so I need to split this into parts. One of the parts is making sure the first field is of the expected type. – Anton Dyachenko Aug 28 '23 at 02:58
3

Note: This answer is insufficient in some cases. See rp123's answer for a better solution.


You can assert that a type has a property of a specific type at compile time by trying to use it in a context that requires that type:

macro_rules! assert_is_type {
    ($t:ty, $i:ident: $ti:ty) => {
        const _: () = {
            fn dummy(v: $t) {
                let _: $ti = v.$i;
            }
        };
    }
}

pub struct A {
    a: i32,
}

assert_is_type!(A, a: i32);

// do not compile
assert_is_type!(A, b: i32);
assert_is_type!(A, a: i64);

(Permalink to the playground)

mcarton
  • 27,633
  • 5
  • 85
  • 95
  • This is probably the best solution given the question, but it does require that the test be done in the same module or the field be marked as public. However, I am still struggling to think of a use case for this sort of test. – Locke Oct 07 '20 at 20:53
  • 3
    @Locke the only use case I can think of is testing that the public API doesn't change. – mcarton Oct 07 '20 at 20:57
  • 1
    Note that this solution can be wrong in the case of implicit coercions. For example, it accepts a field of type `&'static Box` as being a field of type `&i32`, see https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=dd2b2659bc75603f1bd5d0d1d34d5303. – rp123 Feb 03 '22 at 20:31
  • @rp123 very good point! I would downvote myself if I could :) – mcarton Feb 03 '22 at 22:01
1

mcarton's answer is close, but in some special cases defeated by implicit type coercions (https://doc.rust-lang.org/reference/type-coercions.html). Here is an example where it gets the field type wrong: https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=dd2b2659bc75603f1bd5d0d1d34d5303.

This can be fixed by creating a modification that avoids an implicit coercion context, see playground

pub trait EqType {
    type Itself;
}

impl<T> EqType for T {
    type Itself = T;
}

fn ty_must_eq<T, U>(_: T) where T: EqType<Itself=U> {}

macro_rules! assert_is_type {
    ($t:ty, $i:ident: $ti:ty) => {
        const _: () = {
            #[allow(unused)]
            fn dummy(v: $t) {
                ty_must_eq::<_, $ti>(v.$i);
            }
        };
    }
}

pub struct A {
    a: &'static Box<i32>,
    b: u32,
}

// Correctly accepted:
assert_is_type!(A, a: & Box<i32>);
assert_is_type!(A, b: u32);

// Correctly rejected:
assert_is_type!(A, a: &i32);
assert_is_type!(A, b: i32);
rp123
  • 3,623
  • 1
  • 18
  • 20