I intend to create a set of dozens of Julia functions routines that will operate on objects a, b, c and d, where the following conditions must always be verified:

a is an 8-bit unsigned integer such that 1 < a < 250;
AND a can be incremented or decremented by arbitrary amounts, as long as the new value remains within that range;
AND operations that would generate a result outside of that range warp the result around: for instance, if a = 199 then a+1 = 1.

b is a 32-bit unsigned integer such that 500 < b < 200,000;
AND b can be incremented or decremented by arbitrary amounts, as long as the new value remains within that range (otherwise, throw an error).

c is a 16-bit unsigned integer within the set {4, 50, 300 or 2400}, but no other value is allowed, and no arithmetic operation is meaningful on that object.

d is a string that must be either â€śd1â€ť, â€śd2â€ť, or â€śd3â€ť.

I want to avoid checking the validity of each input argument at the start of each function and would rather rely on defining custom types for those four objects, so that once they have been properly defined and initialised, they can be safely manipulated by the functions (which would require those inputs to be of the right type). How could I achieve this?

Please see the Julia doc Constructors Â· The Julia Language. Inner constructor will disable the default constructor. You can then enforce your restrictions inside this constructor.

struct A <: Integer
a::UInt8
function A(a)
a = mod(a, 250)
return new(a)
end
end
struct B <: Integer
b::UInt32
function B(b)
(500<b<200_000) || error("No!")
return new(b)
end
end
struct D <: AbstractString
d::UInt8
end
string(d::D) = "d$(D.d)"

Thank you both, liuyxpp and gustaphe, for your inputs: Iâ€™m making progressâ€¦

After playing around with those concepts, I think it makes sense to include checks on the validity of input variables within the definition of custom-designed types, though verification of the validity of operations on those variables should not be part of the type definition.

The type definition for the first case mentioned in the original message now looks like

julia> struct A <: Integer
a::UInt8
function A(a)
if a < 1
return "Argument can't be smaller than 1."
end
return new(a)
end
end
julia>
julia> a1 = A(3)
A(0x03)
julia> a2 = A(a1.a - 3)
"Argument can't be smaller than 1."
julia> a2 = A(a1.a + 3)
A(0x06)