A string is essentially a sequence of char values ββ(in Haskell, BTW, string is a type alias for [Char] ). Then there will be a more general question if it is possible to statically declare a list as having a given size.
Such a language function is known as Dependent Types , but F # does not have it. Therefore, the short answer is that this cannot be done in a declarative way.
The simplest and probably the most idiomatic way is to define Code as a single discriminatory union:
type Code = Code of string
In the module that defines Code , you also define a function that clients can use to create Code values:
let tryCreateCode candidate = if System.String.IsNullOrWhiteSpace candidate then None else Some (Code candidate)
This function contains runtime logic that prevents clients from creating empty Code values:
> tryCreateCode "foo";; val it : Code option = Some (Code "foo") > tryCreateCode "";; val it : Code option = None > tryCreateCode " ";; val it : Code option = None
What prevents the client from creating an invalid Code value, eh? For example, could the client bypass the tryCreateCode function and simply write Code "" ?
Here are the signature files . You create a signature file ( .fsi ) and in this declare types and functions like this
type Code val tryCreateCode : string -> Code option
The Code type is declared here, but its "constructor" is not. This means that you cannot directly create values ββof these types. This, for example, does not compile:
Code ""
Indicated error:
error FS0039: value, constructor, namespace, or Code type not defined
The only way to create a Code value is to use the tryCreateCode function.
As stated here, you can no longer access the base string value of Code , unless you also provide a function for this:
let toString (Code x) = x
and declare it in the same .fsi file as above:
val toString : Code -> string
This may seem like a lot of work, but in reality itβs just six lines of code and three lines of type declaration (in a .fsi file).