At least for this specific example I don't know why I wouldn't use null instead of option and ?? As it's more clear what's happening as it's standard C#
Also in your example does the function to the right of | execute always?
The example is simplified, but I dislike returning null in my own code. The function will always execute, left or right doesn't matter it's mapped across in the ResultObject class.
The function must return an IResult<T>, the ResultObject analyzes the IResult<T> checking for IFail or IOk. If it's IOk the value of type T is retrieved from the Value property of the IOk<T> object and returned, the Some property defaults to true. If the IResult<T> is an IFail, Some is set to false, it copies the message from the IFail object into the ResultObject, and returns the value the was supplied to its constructor.
I'm just sharing something I find useful, and I hope I can make it useful for others as well. Thanks for the questions.
Null pointers are one thing, C# nulls (with nullable reference types enabled) are another. They behave a lot like an Option monad with the caveat that the static analysis can technically be tricked by incorrect hints.
I'm not that familiar with newer c# code and only recently started with result pattern but tbh, I can't tell what is this code supposed to do. Does opt resolve to true or false in this case? Why do you want TestStringFail to always execute, and what should it return? Why is opt.None true when it was initialized with a valid string value, what does None even mean in this context?
The operator being applied to the ResultObject will always resolve to the Generic type that was specified as 'T in IResult<T>. If the function is not successful the resolved value will be whatever value was supplied to the ResultObject constructor, the opt.None property will true and the opt.Some property will be false.
You can't disagree with the fact that Nullable<T> works a lot like an Option<T>. Returning an error is not idiomatic C# code (which would be to throw an exception usually) but if you wanted that, you'd use a Result<T, TError> type or similar.