Its use case would make sense for your client_open function. If you make it type Client = Void* then the compiler would know that you can’t just pass any Void* to say client_close. It would only allow the return value of client_open since that’s the only function that returns a Client explicitly.
I agree that being able to define custom types wrapping Ints has it’s uses.
You can use unsafe_as thought it seems to have some rough edges.
Given,
lib L
type T = Int32
end
Although the following complains
0.as(L::T)
^
Error: can't cast Int32 to L::T
The following works
0.unsafe_as(L::T)
The rough edges are:
v = 0.unsafe_as(L::T)
pp! typeof(v) # => Int32 (but is a L::T 🐛)
pp! v.class # => Int32 (but is a L::T 🐛)
And L::T#to_s will not work
v = 0.unsafe_as(L::T)
pp! v
There was a problem expanding macro 'macro_4743351584'
Code in src/int.cr:185:5
185 | {% begin %}
^
Called macro defined in src/int.cr:185:5
185 | {% begin %}
Which expanded to:
> 2 | if other == 0
> 3 | raise DivisionByZeroError.new
> 4 | elsif self < 0 && self == L::T::MIN && other == -1
^--------
Error: undefined constant L::T::MIN
And L::T are autocasted as Int32 if needed during def matching but then expanded using the original type.
def x(a : Int32)
a
end
v = 0.unsafe_as(L::T)
x(v)
This works, but x(v) : L::T as shown if you do pp! x(v). Because you will get the previous error (not having a L::T::MIN).
Depends on what you do with it. For example, I’ve found bugs when replacing alias with type as it no longer saw pointers to several different struct types defined as Void as equal. Generally I’d recommend wrapping the raw C bindings with more convenient things when possible instead of exposing them to end users. The C API often don’t make sense from a crystal perspective anyhow.