Type restrictions and style

Apart from the fact that each individual can program in Crystal however they like, would it be true that the recommended/preferred/encouraged style for type restrictions is to write as less of them as possible and leave to the compiler to figure out correctness? As if you pursued dynamic-languages-looking signatures, but with guaranteed type correctness?

Would like to confirm (or refute! :) that for a talk I am giving next week, since my personal take on type restrictions comes from a different angle. Would like to make sure I transmit the original intent, even if I then explain mine.

I’m the opposite, I try to use them as often as possible. It not only makes the intent of the code more clear. I.e. def some_method(ctx : HTTP::Server::Context) makes it a heck of a lot clearer what ctx is. Without the restriction it becomes a lot harder for the user to grasp what types they are dealing with.

Type restrictions also make the errors better, as instead of like undefined method x on Y you would get a more standard could not find overload for xx error.

The generated API docs also work best with more type restrictions, as it’ll automatically add them to the docs as clickable links to go to that type (if it’s defined in your code as well).

This isn’t to say that you should always 100% use type restrictions, there are certain cases where it makes sense to leave something untyped. In these cases I like to type it as _ to make it clear that “this argument should allow any type” versus “this argument just doesn’t have a restriction”.


My opinion is that types are good for public APIs because users can better understand how to use them. Then for private APIs, throw-away code, prototypes, etc., you can omit types for brevity. For example in the compiler’s code types are omitted to make it less verbose, and mostly when an argument name already reflects the type (var_name would be a String, var would probably be an Var, call would be a Call, etc.). But in many cases we added restrictions when getting errors when passing incorrect types and the errors were harder to understand.

The way Crystal was developed, we started without type restrictions and added them as a way to do what you can do with Ruby with if x.is_a? ... elsif x.is_a? ... but in a more organized, optimal way. I don’t think we ever thought about the language as “This is the preferred/recommended way to program”, at least regarding type restrictions.

So I guess it boils down to personal preference.

What I do see is that in most public APIs I see, types are everywhere. And I like that.


@Blacksmoke16 thanks, I think we see them in a similar way.

When you read source code, I believe it makes a lot of difference to have the types there. You have to know what does the method receive, and what does the method return. Also, would be ideal to know what does the method can raise, but we are not there here. That could be a class type, or a duck typing declaration (interface), you need to know if nil is accepted or not, etc.

In Ruby, I systematically annotate method signatures, see for example any file in my Zeitwerk gem.

So, to me, type restrictions are similar to doctests. Languages that have doctests give you a way to ensure example code in the docs is correct.

The value I personally find in type restrictions is that I can verify my annotations are correct. So, I prefer to use them unless the method specifically needs to be generic.


@asterite perfect, thank you!

Ah! By pure chance I think I realized why did I have the impression that the idea was to not use type restrictions. It’s the opening sentence in the reference section:

Crystal’s philosophy is to require as few type restrictions as possible. However, some restrictions are required.

Should that paragraph be reworded perhaps? Maybe just say nothing on that matter?

Oh, but I realize it was my mistake. It is saying that the language is designed in a way that does not force you to write most type restrictions. It does not mean you should not, though I guess I kind of saw it implied.