Crate offering type level optionals?


#1

Hi there!

I’m searching for a crate offering something like this:

pub struct NoneType;
pub struct SomeType<T>(pub T);

Recently, I’m doing a lot of work that involves type level magic. And several times already, I needed to express the notion of an optional type. Sure, you could just use () as NoneType, but that doesn’t always work and is not really nice.

So since I needed that a few times already, I would like to use a crate for it. Is there a crate offering this? If not, I would write my own. (Of course, the crate could offer more than those two lines of code. I’m sure one could write many of Option<T>'s methods on type level.)

I tried to search via crates.io and Google, but it’s… not easy since “option” is such a common word around Rust.

Thanks!


#2

Does this work for you? :slight_smile:


#3

Enum variants are not types, unfortunately.

In my crates I use Foo<OptionalType = ()>. IMHO () is good enough for this, especially that non-optional usage is Foo<SomeType> rather than Foo<WrapperType<SomeType>>.


#4

But isn’t that what Lukas wants? What is it, then, that they need? I’m very lost :face_with_monocle:


#5

I am not talking about values, but types. This can be somewhat… novel when one is not used to type level shenanigans :stuck_out_tongue:

I want to be able to say:

fn foo<T>() {}

foo::<NoneType>();
foo::<SomeType<i32>>();

This example is a bit stupid, because it doesn’t serve a purpose. But it sometimes does make sense to have optional type parameters.


Mh… I’d disagree, I think. If you use the same arguments for values, it doesn’t sound convincing at all:

-1 is good enough for this, especially that non-optional usage is foo(27) rather than foo(Some(27)).

And yes, sometimes I really want to distinguish between () and no type. I can’t just always use some type as “none value”. Just as we can’t just use -1 or some value as “none value” for integers.


#6

I think your adding confusion using the term Optional. First time reading I thought of optional but I’m pretty sure that’s not what your after.

No type, also confusing, since can be mistaken for a type that will never exist. ! will eventually land in stable.

None/Empty have meaning in sets. (With None already used in Option)

Default maybe bad, depending on behaviour.

Blank is best word I currently think of. (without much thought.)


#7

I don’t think comparison to int is accurate here, because int values are a closed set, but types are an open set.

If you require T: Trait then due to orphan rules your implementation on () will be distinct from all other implementations, since users will have to use their types or newtypes.


#8

Fair point about ints, but String is also an infinite set. And I think you’d agree that None and Some("") are semantically different things in many situations.

And again, maybe I really want to use all possible types within SomeType. And want to distinguish each of them from the situation where no type is available. Just as I would say “No, I don’t want to use one specific string value to denote the absence of a string value”. (A small example where I can’t use specific types to denote the absence of a type).

So yeah, I think the question is not whether I need it, but simply if there is already a crate offering this.


#9

So you want an uninhabited type basically? Because your NoneType and () are essentially the same thing - a ZST but inhabited. ! would give you a true uninhabited type, but it’s unclear when/if that will land in stable (it was shelved recently).

You can roll your own with an empty enum, but be mindful that you can trigger UB if you attempt to form references to such a thing (via unsafe, or whatnot).


#10

Whether or not the type is inhabited is not that important to me (at least not right now). Again, I’m not talking about values at all. And since the property “inhabited” is only relevant when talking about values of a type… it’s not relevant.

Well, at runtime they sure are. But I’m not interested in runtime behavior, as already explained. And at type level, they are different types.

I feel like I fail to make myself understood :confused:
Imagine that you want to write a function foo that takes no value parameter, only a type parameter (similar to mem::size_of). Now the function is supposed to do different things for different types.

  • foo::<SomeType<i32>>() is supposed to print “yeah! i32 :)”
  • foo::<SomeType<()>>() is supposed to print “yeah! () :)”
  • foo<SomeType<!>>() is supposed to print “yeah! ! :)”
  • foo<NoneType>() is supposed to print “Meh :(”

Similar to this example I posted in my last post.


So again, I’m not really asking for a solution to a problem. I already have a solution and I think it’s not a stupid one :stuck_out_tongue:

I just want to know whether there exist a crate that offers this. I thought that maybe other people might have thought of the same thing already…


#11

Ok, I understand what you’re getting at. I’m personally not aware of such a crate. I’ve seen quite a bit of code using () internally to represent what you’re after, and then they implement whatever trait(s) for it that makes it work with the rest of their code. Perhaps there is such a crate, but I suspect it’s likely not very popular because just defining NoneType and SomeType<T>(pub T) doesn’t really buy you much. It’s the implementations of various usecase-specific traits for them that allows them to be plugged in semantically. And those traits and requirements will vary from project to project.

Just my $.02


#12

A bit of an odd solution, paralleling the usual Option isomorphism:

type NoneType = Result<!, ()>;
type SomeType<T> = Result<T, !>;

They have concrete and differing error types, so there shouldn’t be any overlap issues.