(Not necessarily relevant or correct thoughts:
- Your language still seems to mark potentially-failed values in the type system, even if it writes them T! not Either Error T or Result<Error, T>;
- The way Haskell’s do-notation [apparently implemented as a macro package in Rust] is centred around name binding seems very close to what you’re doing, although it [being monadic, not applicative] insists on sequencing everything, so fails the whole block immediately once an error value occurs;
- Of course, transparently morphing a T-or-error into a T after a check for an error either needs to be built into the language or requires a much stronger type system; Haskell circumvents this by saying that x <- ... either gives you a genuine T or returns failure immediately, which is indeed not quite what you’re doing.)
Here is an example:
int! x = ...
int*! y = &x;
int**! z = &y;
// If it had been a type then
// int!* y = &x;
// int!** z = &y;
// int*! y = &x;
// means
// int*! y = "if x is err" ? "error of x"
// : "the address holding the int of x"
This also means that `int!` cannot ever be a parameter, nor a type of a member inside of a struct or union.The underlying implementation is basically that for a variable `int! x` what is actually stored is:
// int! x;
int x$real;
ErrCode x$err;
// int*! y;
int* y$real;
ErrCode y$err;
// y = &x;
if (x$err) {
y$err = x$err;
} else {
y$real = &x$real;
}
int z;
// y = &z;
y$err = 0;
y$real = &x;
The semantics resulting from this is different from if `int!` had been something like struct IntErr {
bool is_err_tag;
union {
int value;
ErrCode error;
};
};
Which is what a Result based solution would work like. In such a solution: int! x ... ;
int!* y = &x; // Ok
int z = ...
y = &z; // <- Type error!