I'm not an Ada expert, but it has excellent support for range-restricted integer types.[0]
Ada's 'discriminated types' are also fun. They let you create members which only exist when they're applicable. [1]
> Do they also allow you to define your own operations on those types
Looks like Ada supports operator overloading, yes. [2]
[0] https://www.adaic.org/resources/add_content/standards/05rm/h...
[1] https://www.adaic.org/resources/add_content/standards/05rat/...
[2] https://www.adaic.org/resources/add_content/standards/05aarm...
I'm not too experienced with this though, so this is pretty much the extent of my knowledge on this topic.
struct PositiveInteger(u32);
Then give it a constructor (which in rust is just a regular static function) that checks the non-zero variant. You can define method on this type, and make it implement traits (which are kind of like interfaces).The best bit: there is zero runtime cost to this, the memory-representation of this type is identical to that of the underlying u32.
Rust-style enums which can contain data, and also have method implemented on them are even better. Doesn't mean classes (structs in Rust) aren't useful, but once you use a language that allows you to define other kinds of custom types, they seem very restrictive when they're the only available tool.
Having the functions that operate on the struct attached directly to the struct declaration, vs having some functions that the first parameter is the struct on which the function operates, doesn't seem like a particularly meaningful distinction to me. OK, you like C-style programming in favor of C++-style programming, congrats. It's still a class either way.
Enums are are the better example of non-class types. For example, you can have:
enum StringOrInt {
String(String),
Int(u32),
}
And you can go ahead and implement methods on that type. Classes have "AND-state", not "OR-state". But a Type in general can have either kind of state.With design by contract you can put in whatever fancy constraints you want on function parameters and return values, and those will be enforced.
As far as objects go, they're much more useful for me as just a means of passing state. Rather than using a bunch of global variables or having to pass in a ton of function arguments, I can just use an object which contains all the state I need.
Of course, having lots of state can usher in its own set of problems, and there's something to be said for trying to make your code as stateless as possible. But sometimes you need state, and maybe even a lot of it.