6

I don't want to check whether a type has a certain trait but I would like to be able to differentiate between, e.g. a struct and an integer. Since both a struct and an integer can implement the same trait, I don't know how I could tell them apart.

The reason that I want to do this is because I am using serde_json to convert a generic type to JSON but I only want it to become a JSON Object (which happens when it is a struct) but it should not convert to anything else (like an JSON I64). Since both structs and integers can implement the Serialize trait, there is no way to tell them apart.

Currently, I let the process panic because it is not an error it could recover from, but since I could potentially know this at compile time, I am wondering whether there is any mechanism to determine the type at the compile stage.

I would like to know how I can tell types apart by their "kind" and not by their traits.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
  • Aochagavia just pointed out to me that comparing the types wouldn't solve the problem in my example. Still I hope this question illustrates the problem. I guess there is no elegant solution and that I just have to verify at runtime. Or perhaps it can be solved with a procedural macro, but that seems a bit overkill. – Daniel Fraenkel Dec 24 '16 at 11:37
  • You would need to come up with a way for the compiler to check: "I know that this implementation of Serialize always returns Objects". – aochagavia Dec 24 '16 at 11:42

1 Answers1

4

Even if you managed to compare the types at compile time, nothing prevents a struct from being serialized as a Json::I64. Its Serialize implementation could be anything! I can think of some partial solutions:

Runtime checks

Add a runtime check to see if the result is indeed a Json::Object by pattern matching. You could combine this with an assertion in case you expect this to be always true. I think this is what you are doing now.

Introducing a custom trait

It would be possible to create a new trait:

trait SerializeAsObject : Serialize {}

Which you would then implement only for those data types you are sure will be serialized as objects. However, nothing prevents you from implementing the trait for i64, so there is still room for error here.

A real solution: dependent types

You would probably need a type system supporting dependent types in order to ensure that serialization of a data type always results in a given kind of output. As far as I know, such a type system is so complex that there are no widely used languages out there supporting it (you could take a look at Idris in case you want to know more about this).

Thoughts on compile-time checks

While it is great to have compile-time checks, the compiler can only go so far. In my experience, using dependent types in real world programming is not worth the hassle. For instance, in this case, you would need to provide a mathematical proof so the compiler can understand that the implementation of Serialize always results in serialization of an object.

Even then, there is no way to ensure a program is bug free! Therefore I think in this case the right thing to do would be to use an assertion, document that your function will panic in case the data cannot be serialized as an object, and write unit tests to ensure it is called correctly.

aochagavia
  • 5,887
  • 5
  • 34
  • 53
  • You're absolutely right. So 'type checking' the struct would not be the right solution for the example. The pattern match needs to be done at runtime, which is unfortunate. It's just too bad that I need to let the process panic for something that in principle could be known at compile time. – Daniel Fraenkel Dec 24 '16 at 11:31
  • There are lots of things known at compile time that are just thrown away. Even in a language that supports reasoning about code, imagine how difficult it would be for the *programmer* to explain their intent to the compiler. This gets very complex, very fast. You essentially have to write a mathematical proof that the compiler is able to understand. – aochagavia Dec 24 '16 at 11:37
  • I agree. In this case a more pragmatic runtime solution should not be a problem. – Daniel Fraenkel Dec 24 '16 at 11:46
  • I think that OCaml's GADT count as a real-world use of dependent types. For instance, that allows having a `print` function that is well-typed without using macros or anything like that: its type just depends on the literal string fed as its first argument. – jthulhu May 28 '23 at 19:10