
Exceptional results: error handling in C# and Rust - steveklabnik
https://ruudvanasseldonk.com/2015/06/17/exceptional-results-error-handling-in-csharp-and-rust
======
jeswin
> I have written plenty of C# code that deals with exceptions, and it is a
> pain to do it correctly. Apart from null, it might very well be the biggest
> design mistake of the language.

In user code (as opposed to framework code), Typed Exceptions are overrated.

Let's take these two cases:

1\. End-User: Doesn't care if the Exception is a ParseException or an
InvalidVersionException. Exception("Invalid number") and Exception("Version
should be higher") is just as good enough for a message. Basically, for the
end-user, the app crashed and that's it.

2\. Programmer: Will have to open the debugger or check the code anyway.

I get it that it's useful in some cases, when you wanna send back some
structured data to the caller. But in the vast majority of cases, the plain
Exception class and Stack Traces suffice.

~~~
AndrewDucker
Agreed. I rarely need a typed exception. The purpose of the exception is to
say "Stop doing what you were doing, and head up the stack until you find a
point where the work can recover from this and carry on."

Generally speaking, that point doesn't care why the bunch of stuff five levels
down went wrong. It just wants to report the error, and then either carry on
with something, or let the user know what the problem was and decide if they
want it to carry on.

Very occasionally, the actual type of the exception is useful. But mostly I
just don't care about typed Exceptions.

~~~
vitalyd
So this is more akin to panics, where you don't really care about handling the
error but rather want to stop dead in the tracks. But take something like
.NET's SocketException, which contains a SocketErrorCode property; you may
very well want to take different action depending on the type of error this
is. This may be as simple as changing the text of the error shown to user
(with suggestions on how to fix), but could also be some code based recovery
that takes different action depending on the type.

------
Khao
The author is painting a pretty bad picure of C# error handling that isn't
close to reality.

In reality, you would never call uint.Parse and let the exception bubble up,
you'd call uint.TryParse and handle the logic for failure in the method. There
is almost always a way to write your code so it doesn't throw exceptions. I'm
working on a pretty large C# codebase for a SaaS website and by doing "find in
all files" the keyword "catch" I came up with 174 references. For hundreds of
thousands of lines of code, we have had to manually catch and handle
exceptions only 174 times.

~~~
verinus
imho the author paints a very accurate picture of c#. exception handling
remains a mystery to many programmers and is a constant source of errors.

error handling in general is hard to do; to have good, what i would call,
"error partitioning"\- catching errors/exceptions where they can be fixed is
difficult- regardless of exceptions.

perhaps you got your error handling right and don't need more catch clauses,
but if I was you I would investigate :)

~~~
WorldWideWayne
Bad programmers write bad code in general, so basing your opinion of C#'s
error handling system on their use of it seems unwarranted. Exception handling
does not remain a mystery to many programmers, just many bad programmers.
Aside from that, the author is a contributor to Rust so I suspect there could
be some small amount of bias here.

> ...catching errors/exceptions where they can be fixed is difficult...

What's difficult? When your program is about to do something like say open a
file - if you don't want an exception to be raised from that code, you wrap
that code in a try...catch. In the catch block, you take corrective action.

The very simple bottom line with error handling in .NET apps is that you
handle/log/notify the user at process and thread entry-points like _main_ and
_AppDomain.UnhandledException_. Other than that, you use a try...catch block.

~~~
kibwen
It's a bit of a reach to sling accusations of bias. The Rust project makes it
trivial for people to contribute, so anyone with even a passing interest in
the language can become a "contributor to Rust" in seconds. If you try and
disqualify all of the 1,000+ Rust contributors from writing blog posts about
the language, then you most likely also disqualify everyone qualified to write
about the language from writing about the language. :P

~~~
WorldWideWayne
I didn't sling an accusation, I said "I suspect there could be some small
amount of bias". If I were accusing, I would have said "this person _is_
biased.". Despite what you say, I still think the suspicion is valid.

~~~
kibwen
That's fair, although I suspect that your suspicion of bias may be biased due
to your usage of C#. :)

~~~
WorldWideWayne
The irony here is that people on HN do judge you if you live mostly in the
C#/.NET world, but you're arguing against my suspicion of people who
contribute to Mozilla.

Do you think it's a far stretch to say that people who actively use open
source languages and libraries and contribute to Mozilla projects are much
more likely to be biased against anything that Microsoft does?

~~~
kibwen
You're still leaping to conclusions, you're just trying to justify it behind a
veneer of persecution. I'm a Rust contributor and I think C# is a fantastic
language; it's my dream to tempt Anders Hejlsberg away to Rust. :P I'm also
typing this from a Windows 7 netbook, my sole computer, though I'm looking to
upgrade to a Surface once Windows 10 is here. We have several former Microsoft
employees in our ranks and one of our core contributors is currently interning
at Microsoft. It is possible to be critical without there being a hidden
agenda!

~~~
WorldWideWayne
It is possible, just not probable. You're an outlier.

------
dfkf
Huh, author even uses exceptions as a part of normal workflow, instead of
checking if "previousVersions" list is empty before calling Min on it he
instead catches InvalidOperationException. I don't want it to look like an ad
hominem argument, but... Anyway, C# authors had Java in sight when designing
the language and after some contemplation they didn't include checked
exceptions in the language. And their main argument is that in the vast
majority of cases ignoring the exception and allowing it to propagate further
up the stack is exactly what you want to do, simply because there is nothing
you can do about it but to log it and tear down the entire object graph or
write an error message to the user.

~~~
stinos
> propagate further up the stack is exactly what you want to do

This is an important point imo. It is actually one of the ways you can
describe to a non-programmer why it can be so hard to write typcial desktop
software which never ever crashes (as in 'unhandled exception' dialog): _huge_
amounts of time would be wasted to figure out for each and every single
statement what can go wrong and how to handle it. Seriously, I've been at
meetings where more time was spent on discussion on how to deal with/present
to the user some obscure once-in-a-lifetime error than on actually fullfilling
requirements.

~~~
wvenable
I have a desktop app that has a single exception handler at the event loop.
It's amazingly robust. That file you're trying to save is locked? Network
location went away? Doesn't matter. It displays the error in a dialog box (so
you know what went wrong) and returns the user to the app. Whatever operation
they were doing, they can just try again. Any cleanup is performed
automatically and the application, without really any effort, is always in a
stable state.

That's how exceptions are supposed to work. The wack-a-mole attempt at
ensuring you "handle" every case is wrong.

The problem, in my opinion, is that most programmers have been so brain
damaged by Java that anyone exposed to that way of programming can't see how
exceptions are supposed to work. Java not only makes you try/catch/declare
everything but also makes you use exceptions for non-exceptional operations. A
perfect storm.

------
noelwelsh
Good post. Not capturing a stack trace is a Rust specific thing -- this is
easy to do in Scala, for example, and still maintain the benefits of monads +
algebraic data types. I'm sure Rust will get similar utilities with time.

The main issue I have with this technique is the higher mental overhead when
getting started. That goes away with a bit of practice.

~~~
seanmcdirmid
Scala gets stack traces automatically from the JVM though, along with much of
its debugging support. The Rust team will have to do this all by themselves,
being a natively compiled language and all.

~~~
masklinn
You can get stack traces on panics by running with RUST_BACKTRACE=1.

I don't know that it makes much sense on Error objects though, there's no
requirement that they're even able to store backtraces, and collecting
backtraces any time an error object is created would be really expensive and
would make the runtime a dependency of errors.

~~~
seanmcdirmid
Most VMs put most of the overhead of stack creation at where the error is
created, so it usually works out fine (throw is expensive, everything else is
cheap). Stack traces are usually dependent on whatever debug symbols are
loaded, so in the CLR you'll get huge blank spaces if an exception propagates
through some dynamically generated or even native code.

Just printing stack traces is a distraction though. The only thing that is
required that on an exception, the entire stack above the the exception site
can be inspected in the debugger.

~~~
masklinn
That's a completely different concern, and it only makes sense on panics, not
any time an Err result is created.

~~~
seanmcdirmid
And how do you tell where an error originated without a panic being called? Is
the programmer expected to just instrument the their code to put the panic in
as soon as possible? Anyways, this is a well-known problem with monadic error
handling, and one of the main reasons it doesn't get more widespread adoption.

------
raphaelj
I love monadic error handling too, but not for the reason that the author
cited.

The fact that you know exactly what errors a function can throw is not an
unique feature of monadic errors: in Java, you are required to explicitly list
exceptions that a function can throw (checked exceptions), and you must handle
every exception when calling the function.

The real feature of monadic error handling is that error handling become
_composable_.

Imagine that you want to write a function that accepts three character-encoded
integers and that you want this function to return the first integer it
successfully parsed.

With exceptions, you will probably use a _parseInt_ function with a type
similar to:

    
    
        fun parseInt(String) -> Int throws ParseException
    

And you will write the function like this:

    
    
        fun getFirstInt(String a, String b, String c) -> Int throws ParseException:
            try: 
                return parseInt(a)
            catch e1:
                try:
                    return parseInt(b)
                catch e2:
                    return parseInt(c)
    

Now take a language supports algebric data types (like Haskell, Rust, Scala or
OCaml). The _parseInt_ function will probably have a type similar to:

    
    
        fun parseInt(String) -> Result<Int, ParseException>
    

You will probably define a simple operator to manage the pattern _if it fails,
try that instead_ before implementing the _getFirstInt_ function. Lets call
this operator _< |>_ (it's its name in Haskell):

    
    
        fun operator <|>(Result<r, e> left, Result<r, e> right) -> Result<r, e>:
            match left:
                Ok(r)    -> return Ok(r)
                Error(e) -> return right
    

With this operator, _getFirstInt_ could be implemented as simply as

    
    
        fun getFirstInt(String a, String b, String c) -> Result<Int, ParseException>:
            return parseInt(a) <|> parseInt(b) <|> parseInt(c)
     

I think that the fact that you can write you own error handling operator is
the killer feature of monadic error handling (and monadic programming in
general). It makes the enforced handling of errors really painless (while it's
like hell in Java where every exception must be handled).

It's almost impossible to create this kind of operators with exceptions
because they are not a return value, and thus can't be handled in expression.
With monadic error, handling operators are just plain old functions.

~~~
masklinn
Just a note, Rust's error handling is not monadic because Rust doesn't have
monads because it doesn't have HKTs, I guess we could call it "reified"
though.

~~~
GolDDranks
Actually it is. Rust Option and Result types implement .and_then(), which is
equivalent to bind. They are also equipped with many of the other things you'd
expect of a monadic types.

The thing is, Rust's type system is currently incapable of expressing an
GENERAL monadic trait/interface, which it would be, if it had HKT. (That would
allow implementing all the additional monadic util methods as trait default
implementations, and having generic functionality applying to any monad.)
Hopefully we get there someday.

------
detrino
Both exceptions and results are important for software architecture. The lack
of the former leads to an oversimplification of interfaces in languages with
only the latter. The author hit upon an example that is probably better
written using results. But guess what? A C# programmer would write it that way
too. See the TryParse methods found all over the C# standard library. On the
other hand, what would a Rust programmer do to handle an out of memory
situation? That would be too pervasive with the result approach so it has been
culled from all interfaces. Also, nullable reference types are a separate
issue.

~~~
kibwen
The point that you're glossing over it that Rust forces you to acknowledge the
existence of errors, either by working with Err types or by using .unwrap() to
terminate the thread in the event of an error. The existence of both Parse and
TryParse in C# indicates a trend of allowing to errors pass silently by
default, which ultimately results in surprise runtime failures.

    
    
      > On the other hand, what would a Rust 
      > programmer do to handle an out of memory 
      > situation?
    

Let's ask MSDN what a C# programmer would do:

 _" This type of OutOfMemoryException exception represents a catastrophic
failure. If you choose to handle the exception, you should include a catch
block that calls the Environment.FailFast method to terminate your app and add
an entry to the system event log"_

[https://msdn.microsoft.com/en-
us/library/system.outofmemorye...](https://msdn.microsoft.com/en-
us/library/system.outofmemoryexception%28v=vs.110%29.aspx)

~~~
detrino
I'm not glossing over it, I said that this is better written using results.

Complaining about the existence of Parse is like complaining about the
existence of unwrap in Rust.

C++ (another systems language) is probably a better comparison for out of
memory.

~~~
kibwen
Moving my ninja edit down here into a reply:

On modern systems it's not possible for an application to know when memory has
been exhausted, because of virtual memory and overcommit
([http://www.win.tue.nl/~aeb/linux/lk/lk-9.html#ss9.6](http://www.win.tue.nl/~aeb/linux/lk/lk-9.html#ss9.6)).
The system will happily tell your process that memory is available even when
it's not, and relies on the OOM killer to free memory by force when necessary.
The only context in which an application can reliably detect OOM is when that
application is the kernel itself.

Fortunately, Rust has a stripped-down version of the standard library, called
libcore, that exposes features which don't require allocation and thus don't
have to worry about OOM. This profile is perfectly usable for writing kernels,
and could also be used to write an alternate kernel-specific stdlib which make
it possible to handle OOM.

    
    
      > Complaining about the existence of Parse is like 
      > complaining about the existence of unwrap in Rust.
    

This is mistaken. Complaining about Parse would only be like complaining about
unwrap if unwrap were the implicit and default behavior. Parse throws
implicitly, but unwrap doesn't get called implicitly. The fact that the
possibility is impossible to accidentally overlook is an enormous and crucial
difference.

~~~
detrino
Overcommit is just one configuration on Linux, it is a good default, but there
are domains where you would disable it.

How is Parse any less explicit than unwrap ?

~~~
kibwen
Parse is less explicit than unwrap because the following C# code is legal:

    
    
      uint foo = uint.Parse("3");
    
    

But the following Rust code is not legal:

    
    
      let foo: u32 = "3".parse();  // error: mismatched types
    

It's impossible to use the return value of parse as though it's a u32, because
it's not a u32. You can't accidentally add it to some other number or use it
to index an array, because it's the wrong type entirely. You invariably must
acknowledge the possibility of failure, and if you're okay with terminating
your thread in the event of an error you can do this via an unwrap:

    
    
      let foo: u32 = "3".parse().unwrap();  // just fine
    

Rust's parse is even more explicit than C#'s TryParse, because even with
TryParse it's possible to forget to check the return value:

    
    
      uint foo;
      uint.TryParse("Hello", out foo);  // this compiles and runs!
    

And it's not just about string parsing. The overarching point here is that not
only does the compiler protect you from errors that you know about, it also
brings your attention to errors that you didn't know about. The OP has a great
example in that it's very easy to forget that the `min` method on vectors can
fail if that vector is empty.

Finally, this assurance extends in the other direction as well: knowing that
libraries are architected to make errors explicit via return types means that
if I don't get yelled at for not handling a return type, then I have peace of
mind knowing I'm not overlooking anything. This is somewhat analogous to the
considerable assurance given in knowing that types aren't implicitly nullable.

Now, if you're looking for something that can still go wrong, there's a better
example then OOM: dividing by zero in Rust will implicitly panic. So while
it's unlikely that a library author will use unwrap in their code (there's a
strong culture against Rust libraries causing panics), it's possible that a
library author could accidentally cause a panic by dividing by zero.

~~~
detrino
Both Rust and C# provide throwing/failing interfaces for parsing, it's just
that Rust's is written:

    
    
        let foo: u32 = "3".parse().unwrap();
    

and C#'s is written:

    
    
        var foo = uint.Parse("3");
    

Both require familiarity with the stdlib (Parse throws, TryParse doesn't,
unwrap fails).

I agree that TryParse is worse than Rust's ADT approach, but that's a
different issue, much like nullable references. The author is asserting that
results are superior to exceptions and I am asserting that no, that is just a
bad application of exceptions. Additionally, I am asserting that without
exceptions, you limit what programmers are willing to propagate through their
interfaces because errors become viral.

------
SigmundA
"Can this method throw? Should I catch a PathTooLongException here? And while
the .NET framework is documented to remarkable detail, this is rarely the case
for third-party libraries or in-house code. At the bottom of the call stack,
you just have to assume everything may throw. "

Isn't this true for all languages in one form or another? Go and Rust have
"panics" Any code might fail exceptionally, stack overflows and out of memory
conditions come to mind as relatively common ones.

The difference here is that C# treats more kinds of things exceptionally than
Rust or Go. This seems to have more to do with Rust and Go ability to more
easily return multiple types from a method/function and a philosophy of opt-in
vs opt-out on handling an error.

I am used to C#/javascript so exceptions with try catch are very natural to
me. Writing robust programs always involves handling everything one way or
another. Even out of memory conditions can be handled and recovered if your
always thinking that everything can throw.

The null issue is interesting, I like null, I like undefined with null even
better in javascript. I always seem to need to encode something has no value
or something was never even set vs picking a valid value as a special "no
value" i.e. int.MaxValue or whatever. The only thing I would change in C# and
javascript is null lifting object.property should not throw if object is null
same with foreach over null object. They are adding the ?. for that but you
need to explicitly do it, NullReferenceException is far to common in C# from
those two situations explicitly. I belive Objective-C did some kind of null
lifting where sending messages to null objects don't throw.

~~~
noelwelsh
The issue is this:

Things will go wrong. Some subset of things that can go wrong are things you
want to do something about (e.g. file doesn't exist -- maybe prompt the user
for a different file). The remainder are things you don't want to deal with
(e.g. out of memory -- in most cases this is very rare so you're ok to just
crash).

You want to arrange your program in such a way that you _must_ deal with the
errors you want to deal with. Your code won't compile if you don't. That is
what this technique brings.

More here (though you probably won't understand many of the terms if you
haven't used a modern FP-inspired language before):
[http://underscore.io/blog/posts/2015/02/23/designing-fail-
fa...](http://underscore.io/blog/posts/2015/02/23/designing-fail-fast-error-
handling.html)

~~~
wvenable
> You want to arrange your program in such a way that you must deal with the
> errors you want to deal with.

Which would be fine but there is no way to do that without also having being
forced to deal with the infinite errors you don't want to deal with.

~~~
kibwen
If you don't want to deal with an error then you unwrap it, which will
terminate your program just as well as an unhandled exception would. The
salient difference is that you can't accidentally unwrap something, whereas
you can accidentally forget to handle an exception.

~~~
wvenable
The set of exceptions that can be handled is vanishingly small. With
exceptions you should be catching exceptions very infrequently and in key
places. Talking about accidentally forgetting to handle an exception at a
method-call site completely missed the point. That's not how one should code
or think about coding when using exceptions.

------
wvenable
I started to think about everything that's wrong with the authors code example
and the more I got into it the more I realized the entire problem is wrong.

"Our goal is to write a function that takes a list of previously released
versions and a user-provided version string, which then returns the parsed
number or reports an error."

A function named CheckNextVersion should never even raise an exception because
it _expects_ bad input. It probably also shouldn't double up as the
ParseNextVersion function as well. But I understand the author wanting to
combine these operations and conveniently get an error message out that can be
displayed to the user. But as a damning example of exception handling, it's
just bad design.

So even if we accept the design, his method is horribly over-thought with
regards to error handling. Either it should catch within it's body all
potential exceptions and raise a single InvalidVersionException or it should
catch nothing and the code above should catch everything and display the
message to the user. All the work catching and rethrowing different exception
types adds no value given the purpose of the method.

------
wvenable
I find the opinions presented in this article are far too common these days in
completely misunderstanding exceptions.

"The consequence, when you want to write robust software in C#, is that you
have to keep an MSDN tab open at all times. Can this method throw?"

First of all, assume every method can throw. Just do it. It's either true or
could be true in the future when someone changes that method. Maybe it doesn't
use the network today but tomorrow it calls out to a webservice. So all
methods throw. And forget knowing what exceptions they might throw. That's an
implementation detail and we want to hide implementation details.

"Should I catch a PathTooLongException here?"

No, you shouldn't. What are you going to do any differently with that than you
would FileIsLockedException or NetworkIsDownException or
HarddriveIsCorruptException? You wouldn't do anything differently. Show the
user the ErrorMessage in the dialog and allow them to restart whatever
operation was in affect. Let them choose a shorter path, close Excel, plug
their Ethernet cable back in, or whatever. Trying to play wack-a-mole is a
fools errand. Put MSDN away, you don't need it.

~~~
dcsommer
> No, you shouldn't. What are you going to do any differently with that than
> you would FileIsLockedException or NetworkIsDownException or
> HarddriveIsCorruptException? You wouldn't do anything differently. Show the
> user the ErrorMessage in the dialog and allow them to restart whatever
> operation was in affect. Let them choose a shorter path, close Excel, plug
> their Ethernet cable back in, or whatever.

I don't think this approach works in general. Server software, at least, needs
to automatically handle all these different failure modes in potentially
unique ways.

~~~
wvenable
If you are aware of particular situation, you can handle it. If you aren't
aware of a particular situation at best you log the problem and skip the
operation. That's pretty straight forward and is still the same approach.

For example, I might just catch every single Network exception, log it, add a
timeout, and retry the operation. I'm not playing wack-a-mole and I don't care
in the handler which of the thousands of methods called in my operation
actually raised the exception.

The point is one should _not_ be looking at what exceptions the methods throw
but instead what exceptions can be handled. One set is very large the other
set is very small. This applies equally well to server software.

------
pornel
Interesting case is that Rust's cmp::max() doesn't support floating-point
numbers, because maximum is undefined for Inf and NaN.

For floats there's partial_max() which returns Option<f32>.

It makes sense, but it never crossed my mind before that a simple max()
function can fail.

It's also PITA to error-check such a simple thing, but somewhat surprisingly
there's also f32.max() that works on garbage-in garbage-out principle.

------
mamcx
I have building a toy language and about this I have wondering if is better to
think in errors exactly with normal values.

So, my functions are ALL like this:

    
    
      fun sample= StdOut(Str) | StdErr(Int) do
        if GoodLuck() do
          return "Yeah!"
        fail 1
    

ie: fail is exactly like return, is just a way to split between Ok values and
Err values.

If the client wanna catch them:

    
    
      result = sample() # Get the Ok value. if the Err value is returned, then trow like in exceptions
    
      result != sample() # Get the Err value, not trow
    
      result, err != sample() #Get both
    

What do you think?

~~~
masklinn
> I have building a toy language and about this I have wondering if is better
> to think in errors exactly with normal values.

That's exactly what Rust (to keep with TFA) does. Result is not really
special, its core is just

    
    
        enum Result<T, E> {
            Ok(T),
            Err(E)
        }
    

> What do you think?

That the code doesn't make sense, the result and the error should be exclusive
propositions, why would you get both a result and an error when the function
should return either one or the other?

~~~
mamcx
> why would you get both a result and an error when the function should return
> either one or the other?

Is modeled like unix IO (with stdin/out/err). Anyway, I think, a function/call
get a value or a exception, so why not be more explicit about that?

~~~
masklinn
> Is modeled like unix IO (with stdin/out/err).

Those are used for IO or user signaling, not error reporting (that's the
process's return status, stderr may be used to augment it with a cause, unix
utilities are centered about the status signaling correct/incorrect
execution).

> I think, a function/call get a value or a exception, so why not be more
> explicit about that?

It's fine to be explicit about it (again that's what Rust does), the problem
is your example isn't really explicit about it, and the second sample is
downright weird. It's like Go except even more screwy.

edit: in an other subthread you mention following dynamically typed languages,
if we go with that and assume no type support it makes slightly more sense,
but the logic is still screwy: assuming the second snippet is a function which
doesn't have a result (so it can only have an error) it should use the same
system as the third snippet, the faulting assignment (first snippet) is the
one which should use a different syntax.

And then there'a the question of what semantics you get when not assigning but
just nesting expressions.

An option might be to attach the decision to the call instead (kinda like
Swift) e.g. the result of a regular function call would be either `error` or
`result, error` (with the language enforcing that one of result and error must
be nil) but by "banging" the function the error is raised instead of returned.
So you'd get something along the lines of:

    
    
        # no result, possibly an error
        err = foo()
        # a result or an error
        result, err = foo()
        # no result, panics in case of error
        !foo()
        # a result or panics in case of error
        result = !foo()
    

the language would enforce that either the error fetched or the function is
bang-ed, so

    
    
        foo()
    

would be illegal, because it neither receives the error nor raises on error.

Not sure that's great, but it might be workable.

~~~
mamcx
Yep, as I have think, is more or less like you say.

>if we go with that and assume no type support

Wonder why is harder with a type system (I say follow the syntax, not the type
system of julia/python... so I wanna be as static as possible, or at least
try)

~~~
masklinn
> Wonder why is harder with a type system

A type system doesn't make it harder, it makes it pointless: when you have a
good type system sum types[0] are a safer and all around better solution.

[0] possibly, in some cases, polymorphic variants
[https://realworldocaml.org/v1/en/html/variants.html#polymorp...](https://realworldocaml.org/v1/en/html/variants.html#polymorphic-
variants)

~~~
mamcx
Ok, was a big mistake to show the idea with this on-the-fly-invented syntax
this way (because: I still not settle on it), I just try to convey the idea
not the implementation.

I wanna build a F#-like type-system with sum/products
([https://www.reddit.com/r/fsharp/comments/30ywjy/question_how...](https://www.reddit.com/r/fsharp/comments/30ywjy/question_how_implement_a_subset_of_f_in_f/))
for my language, and be the result of the function already a Result<T, E>,
without the user need to annotate it manually. So, I'm thinking in the kind of
development that happened in reactive library but as a intrinsic model of the
language.

Of course, I'm a total noob in this area, so still researching about this!

------
charlieflowers
I'd love it if some Rust insiders could comment on how Rust _might_ implement
the stack trace feature (assuming they did decide to implement it). Perhaps a
trait such as "GeneratesStacktrace" that can be implemented with "Derives"?
And the stack trace is only available in a Debug build when you compile with
the appropriate flags?

~~~
kibwen
I've barely given it any thought, but one approach would just be to modify the
try! macro to dump a backtrace to stderr when an Err is encountered in debug
mode. The backtrace itself could be generated the same way that they're
generated for C++.

------
louthy
Whilst some of the issues with C# are valid, it's totally possible to do
monadic error handling in C#, I wrote an entire library to facilitate that
[1]. In fact the big win of monadic error handling is the composable nature of
monads:

Here's a function that throws an error on odd numbers:

    
    
        Try<int> Number(int x) => () =>
        {
            if( x % 2 == 0 )
                return x;
            else
                throw new Exception("Any exception");
        };
    

Here the monadic computation succeeds, and 'res' holds 1000:

    
    
        var res = match(from x in Number(10)
                        from y in Number(10)
                        from z in Number(10)
                        select x * y * z,
                        Succ: x => x
                        Fail: 0);
    

Here the monadic computation fails, and 'res' holds 0:

    
    
        var res = match(from x in Number(10)
                        from y in Number(9)
                        from z in Number(10)
                        select x * y * z,
                        Succ: x => x
                        Fail: 0);
    

If you don't want to use it in a LINQ expression then you can just say:

    
    
        var res = Number(10).IfFail(0);
    

There's also mention of the null problem, which obviously is a major problem
with the language (and framework), but it can be tamed somewhat with Option<T>
and TryOption<T>:

    
    
        TryOption<int> Number(int x) => () =>
        {
            if( x % 2 == 0 )
            {
                if( x > 10 )
                    None;
                else 
                    return Some(x);
            }
            else
            {
                throw new Exception("Any exception");
            }
        };
    

Here the monadic computation succeeds, and 'res' holds 1000:

    
    
        var res = match(from x in Number(10)
                        from y in Number(10)
                        from z in Number(10)
                        select x * y * z,
                        Some: x  => x,
                        None: 0,
                        Fail: 0);
    

Here the monadic computation fails, and 'res' holds -1:

    
    
        var res = match(from x in Number(10)
                        from y in Number(9)
                        from z in Number(10)
                        select x * y * z,
                        Some: x  => x,
                        None: 0,
                        Fail: -1);
    

Here the monadic computation also fails because a None is returned, so 'res'
holds 0:

    
    
        var res = match(from x in Number(10)
                        from y in Number(20)
                        from z in Number(10)
                        select x * y * z,
                        Some: x  => x,
                        None: 0,
                        Fail: -1);
    

So as you can see, using monadic error handling is doable in C# with the right
tools. It certainly takes a bit more discipline from the programmer, but it's
definitely worth it. It also makes your code declarative. A function that
looks like this:

    
    
        TryOption<string> DoStuff();
    

... clearly declares that the function may: Succeed, return 'no value', or
fail. So you should deal with all 3 possibilities. In contrast, this doesn't
give that information to the programmer:

    
    
       string DoStuff();
    

[1] [https://github.com/louthy/language-
ext](https://github.com/louthy/language-ext)

