5 ms·
Both exceptions and results are important for software architecture. The lack of the former leads to an oversimplification of interfaces in languages with only
by detrino 11y ago
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 11y agoThe 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.outofmemoryexception%28v=vs.110%29.aspx https://msdn.microsoft.com/en-us/library/system.outofmemorye...
- detrino 11y agoI'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 11y agoMoving 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 11y agoOvercommit 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 11y agoParse 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.