proj-oot-ootErrorNotes3

 btilly 1 day ago [-]

One common anti-pattern is that "event driven" can lead to a push architecture that works on paper, but is unreliable in practice.

The idea looks deceptively simple. If you need something to happen, you just broadcast the right event, someone else acts on it and everything is fine. You code it, it works in the demo, and you deploy to production.

And then you run across network failures. Missed events. Doubled events. Events that arrived while a database was down. Events that were missed due to a minor software bug. And so on. You layer on fixes, and the system develops more complex edge cases, and more subtle bugs.

---

https://en.wikipedia.org/wiki/Command%E2%80%93query_separation

---

ArgumentNullException?

---

" Error Handling

Go’s error handling has definitely been a boon to the stability of Juju. The fact that you can tell where any specific function may fail makes it a lot easier to write code that expects to fail and does so gracefully.

For a long time, Juju just used the standard errors package from the stdlib. However, we felt like we really wanted more context to better trace the path of the code that caused the error, and we thought it would be nice to keep more detail about an error while being able to add context to it (for example, using fmt.Errorf losing the information from the original error, like if it was an os.NotFound? error).

A couple years ago we went about designing an errors package to capture more context without losing the original error information. After a lot of bikeshedding and back and forth, we consolidated our ideas in https://github.com/juju/errors. It’s not a perfect library, and it has grown bloated with functions over the years, but it was a good start.

The main problem is that it requires you to always call errors.Trace(err) when returning an error to grab the current file and line number to produce a stack-trace like thing. These days I would choose Dave Cheney’s github.com/pkg/errors, which grabs a stack trace at creation time and avoid all the tracing. To be honest, I haven’t found stack traces in errors to be super useful. In practice, unforeseen errors still have enough context just from fmt.Errorf(“while doing foo: %v”, err) that you don’t really need a stack trace most of the time. Being able to investigate properties of the original error can sometimes come in handy, though probably not as often as you think. If foobar.Init() returns something that’s an os.IsNotFound?, is there really anything your code can do about it? Most of the time, no. "

---

" Stability

For a huge project, Juju is very stable (which is not to say that it didn’t have plenty of bugs… I just mean it almost never crashed or grossly malfunctioned). I think a lot of that comes from the language. The company where I worked before Canonical had a million line C# codebase, and it would crash with null reference exceptions and unhandled exceptions of various sorts fairly often. I honestly don’t think I ever saw a nil pointer panic from production Juju code, and only occasionally when I was doing something really dumb in brand new code during development.

I credit this to go’s pattern of using multiple returns to indicate errors. The foo, err := pattern and always always checking errors really makes for very few nil pointers being passed around. Checking an error before accessing the other variable(s) returned is a basic tenet of Go, so much so that we document the exceptions to the rule. The extra error return value cannot be ignored or forgotten thanks to unused variable checks at compile time. This makes the problem of nil pointers in Go fairly well mitigated, compared to other similar languages. "

---

   3. 428 Precondition Required .......................................2
   4. 429 Too Many Requests ...........................................3
   5. 431 Request Header Fields Too Large .............................4
   6. 511 Network Authentication Required .............................4

---

many APIs, when they have an error, return a JSON error object that gives an error code (sometimes an Ascii string, like a Python Exception subclass classname) and a human-readable message

---

ways to resume from an exception in the debugger in Lisp Machines:

" In debugging a problem, you would normally employ several other processes, from editor to online documentation to mail system, and so on. Finally, you would select the process that had entered the Debugger and decide what to do next. The choices are many:

    Just aborting that execution attempt
    Choosing one of the proceed options provided by the original program designer
    Returning a plausible value from the erroneous function and resuming execution
    Changing the value of an argument and reinvoking the function
    Editing and recompiling the erroneous function and invoking it again on the current arguments" [1]

---

https://compileandrun.com/stuggles-with-rust.html discusses some issues with Rust:

The blog post author tries a number of things with error handling and none of them are quite right, and he also notes that it's hard to find documentation on this, noting that a quick glance at the official error handling documentation ([2]) wasn't satisfying. The person who wrote that official error handling documentation responded on HN and says that the doc chapter e wrote was intended to motivate error handling techniques by first introducing naive techniques and then showing how they can be improved, and the blog post author mistook these naive examples for suggestions [3] [4].

To reiterate, the link to the current version of the error handling chapter is http://wayback.archive.org/web/20170316000152/https://doc.rust-lang.org/book/error-handling.html -- i wouldn't be surprised if they rewrote it in light of this problem, but for designing another language the current 'motivated' version sounds useful.

"All of the error handling strategies in that chapter are interoperable to some degree (with perhaps "panic on error" being the odd duck out)." "The purpose of the error handling chapter is start with someone who might not even know what `Option<T>` is, and take them all the way through `try!`, the `Error` trait and automatic `From` conversions from first principles. More than that, it's supposed to teach you why using `String` or `Box<Error>` for your error type can be bad, even if it is ludicrously convenient." [5]

discussions at: https://news.ycombinator.com/item?id=14284734 https://www.reddit.com/r/rust/comments/69i105/the_grass_is_always_greener_my_struggles_with_rust/

some other quotes from the discussion:

---

burntsushi 9 days ago

parent flag favorite on: My Struggles with Rust

Note that you only get a stacktrace if you set the environment variable `RUST_BACKTRACE=1`. Otherwise, you get a standard panic message. The message from `unwrap` is probably unhelpful, which is why a lot of folks advocate using `expect`.

---

GolDDranks? 9 days ago

parent flag favorite on: My Struggles with Rust

If I know that the unwrap is 100% safe I tend to write: `.expect("Invariant: $REASON_WHY_THIS_NEVER_FAILS")`

---

thegeomaster 9 days ago

parent flag favorite on: My Struggles with Rust

The `error-chain` crate [1] exists to get rid of precisely the error handling boilerplate the author has encountered. That's not ideal, though, as I believe that a place for such functionality is in the core language, not a separate library, https://crates.io/crates/error-chain

JoshTriplett? 9 days ago [-]

I'd like to see error-chain standardized into the standard library as well. It seems by far the most sensible approach to building Error types.

reply

shepmaster 9 days ago [-]

Conversely, I really hope it doesn't. error-chain doesn't implement `PartialEq?` which means that I can no longer write tests for my failure types. I'm a big believer in quick-error instead.

reply

JoshTriplett? 8 days ago [-]

There are some good `assert_match` macros that allow you to pattern-match error cases instead.

Might also be possible for error-chain to implement PartialEq? if you didn't want some of its generalized error-boxing bits. If that's something you need, you might file an issue.

reply

twic 9 days ago [-]

I'd prefer it if that didn't happen, or at least not for a few years. error-chain involves some pretty hairy macros, which would feel out of place in the standard library. I'd like to wait and see if the community can come up with something more transparent before canonising error-chain.

reply

---

QuercusMax? 9 days ago

parent flag favorite on: My Struggles with Rust

Was the expect method​ supposed to be named except, as in exception? That would make a lot more sense.

erickt 9 days ago [-]

If I recall correctly we just couldn't come up with a great name for it. To me "expect" is a positive action, but the argument is about it failing to meet the expectation. Semantically I like `thing.unwrap_or(

panic!("failure message"))`. It feels more like what I would want to say, but it just is so wordy.

AlphaSite? 9 days ago

parent flag favorite on: My Struggles with Rust

The java optional api uses orElseThrow which I think is quite clear.

bluejekyll 9 days ago [-]

Agreed, I've never liked 'expect' either; 'or_else_panic(msg)' would be much clearer

Edit: 'or_panic(msg)' would be shorter and also good.

reply

erickt 9 days ago [-]

I think that was one of the proposed variations, but we ended up picking the shorter .expect to cut down on repetition. We expected (ha) that this function would be used in these one-offs, so we wanted something more efficient.

reply

---

std io error types in Rust:

https://doc.rust-lang.org/std/io/enum.ErrorKind.html

---

burntsushi 9 days ago

... If you'll allow me to summarize very briefly (perhaps at the expense of 100% accurary):
parent flag favorite on: My Struggles with Rust

All of this stuff was available at Rust 1.0. (Except for `?`, which is today an alias to `try!`.) It all falls out of the same fundamental building blocks: an `Error` trait with appropriate `From` impls.

The one exception to this is that, recently, there has been a surge in use of crates like error-chain to cut down on the code you need to write for defining custom error types and their corresponding `From` impls. But it's still all built on the same fundamental building blocks.

---

pixel_fcker 8 days ago

parent flag favorite on: My Struggles with Rust

...then don't use unwrap(). Instead check the error, do whatever handling you want and exit gracefully.

unwrap()'s behaviour is essentially the same thing as an uncaught exception in Python, only you can actually check for where they occur in the code with a simple grep rather than hoping your test suite caught every possible failure case.

burntsushi 9 days ago

parent flag favorite on: My Struggles with Rust

One of the hardest things about teaching error handling in Rust is that there is a lot of nuance. It's fun to say "unwrap is evil, don't use it," and more than that, it's not exactly wrong either. The nuanced version is that you shouldn't use unwrap for error handling, but:

1. If you're prototyping or writing a quick throwaway program, then unwrap will neither pick your pocket nor break your leg.

2. If you have an invariant that you either can't (or won't) move into the type system, then unwrap/expect/panic'ing can be appropriate precisely because if that unwrap gets tripped, then that will indicate a bug in your program that should be fixed.

The more succinct advice that I like to use is this: "if end users of your Rust application see a panic, then you have a bug." But this is slightly harder advice to follow because it's a statement about the user experience.

p0nce 9 days ago [-]

> 2. If you have an invariant that you either can't (or won't) move into the type system, then unwrap/expect/panic'ing can be appropriate precisely because if that unwrap gets tripped, then that will indicate a bug in your program that should be fixed.

Absolutely but using the same mechanism (unwrap/panic) for both types of errors - recoverable and recoverable - can creates confusion. panic'ing for wrong user input for example as can be seen in example code.

reply

kibwen 9 days ago [-]

panics aren't for recoverable errors, in any circumstances. It is technically possible to "halt" the unwinding from a panic, but 1) that's intended for C interop, 2) the feature has been deliberately designed to make your life hell if you try to use it to emulate recoverable exceptions, and 3) Rust makes no guarantees that panics will unwind at all, it is entirely legal for a user to configure panics so that they all abort instead.

reply

---

[–]ksion 42 points 10 days ago

This is almost exclusively about error handling, and I agree the way Rust introduces it is pretty discouraging. There is an extensive chapter about it in the book, but that's pretty much the problem with it: it's extensive, and requires the reader to absorb many concepts before they can properly write even simple programs.

A better option, in my opinion, would be to ease the newcomers more gracefully into the error handling routine, and guide them through a several approaches of increasing sophistication:

    The yolo way -- unwraps everywhere.
    The lazy way: Result<T, Box<Error>> and use ? when needed. This is probably the best approach for people coming from other languages to start with. It is reasonably similar to slapping throws Exception on every function in Java. It also establishes a base for future refinement, distinguishing legitimate unwraps from error propagation.
    The wrapping way: define your errors with error_derive or error-chain, and use these instead of Box<Error>. Liken it to exceptions having a cause() in Java/etc., and throwing higher level exceptions that contain the lower level ones. Bonus for Rust for doing the conversion automatically.
    The advanced way: Error trait, what it is, how to implement it.
    permalinkembedsavereportgive goldreply

[–]burntsushiripgrep · rust 16 points 10 days ago*

The error handling chapter you refer to literally walks readers through the various states of error handling, almost exactly as you laid out. :-) (With one exception: it doesn't talk about the various error crates.) The case study even does it again with a real task: https://doc.rust-lang.org/book/error-handling.html#case-study-a-program-to-read-population-data

    permalinkembedsaveparentreportgive goldreply

[–]robinst 4 points 10 days ago

Is the chapter about it in the second edition of the book better?: https://doc.rust-lang.org/nightly/book/second-edition/ch09-02-recoverable-errors-with-result.html

    permalinkembedsaveparentreportgive goldreply

[–]Johnny_Bob 2 points 9 days ago

The new book is really nice, but AFAICS it doesn't tell you how to define your own error type or to use Result<V,Box<Error>> , so right now it doesn't solve the specific problem on the blog post.

    permalinkembedsaveparentreportgive goldreply

[–]jcdyer3 2 points 9 days ago

That's actually my favorite chapter in the first edition. It really made idiomatic rust click for me in a big way.

    permalinkembedsaveparentreportgive goldreply

---

    permalinkembedsavereportgive goldreply

[–]its_boom_Oclock 5 points 10 days ago

    Despite its verbosity, I actually like this approach quite a bit. You basically create one Error struct for your crate or program, implement a lot of From's for each type you need, and then finally implement Display on it so it can be printed out properly. It's a lot of boilerplate, especially if I plan on replacing my small Python scripts with Rust programs, but it's genuinely clean code and it ticks a lot of boxes with me.

Well this is what I always use and the structure itself is clean and easy to use, the boilerplate is just huge but this is a common problem for Rust in general in my opinion. There is for many things a huge amount of boilerplate.

If we look into the referenced code:

impl fmt::Display for EsError? { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self { EsError::EsError?(ref s) => fmt::Display::fmt(s, f), EsError::EsServerError?(ref s) => fmt::Display::fmt(s, f), EsError::HttpError?(ref err) => fmt::Display::fmt(err, f), EsError::IoError?(ref err) => fmt::Display::fmt(err, f), EsError::JsonError?(ref err) => fmt::Display::fmt(err, f) } } }

There really has got to be a way to do that in three lines.

    permalinkembedsavereportgive goldreply

[–]windshook 2 points 10 days ago

I have a very satisfying diff in an old project from when I migrated from hand-written error structs like that to quick-error.

    permalinkembedsaveparentreportgive goldreply

[–]theindigamer 1 point 10 days ago*

I think using patterns with

should work: https://doc.rust-lang.org/book/patterns.html#multiple-patterns So the code would end up looking like

impl fmt::Display for EsError? { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self { EsError::EsError?(ref s)

EsError::EsServerError?(ref s) =>
                fmt::Display::fmt(s, f),
            EsError::HttpError(ref err) | EsError::IoError(ref err) | EsError::JsonError(ref err) => 
                fmt::Display::fmt(err, f),
        }
    }}
    permalinkembedsaveparentreportgive goldreply

---

twic 9 days ago

parent flag favorite on: My Struggles with Rust

It might be worth pointing out that this Rust:

  1. [derive(Debug)] enum ConfigError? { Io(io::Error), Parse(ParseIntError?), }
    impl From<io::Error> for ConfigError {
        fn from(err: io::Error) -> ConfigError {
            ConfigError::Io(err)
        }
    }
    
    impl From<ParseIntError> for ConfigError {
        fn from(err: ParseIntError) -> ConfigError {
            ConfigError::Parse(err)
        }
    }
    
    fn read_config() -> Result<i32, ConfigError> {
        Result::Ok(parse_int(read_config_file()?)?)
    }
    
    // given the following
    fn parse_int(str: String) -> Result<i32, ParseIntError> { ... }
    fn read_config_file() -> Result<String, io::Error> { ... }

Is, in a sense, the equivalent of this Java:

    int readConfig() throws IOException, ParseException {
      return parseInt(readConfigFile());
    }
    
    // given the following
    int parseInt(String str) throws ParseException { ... }
    String readConfigFile() throws IOException { ... }

The reason i say that is that this:

    throws IOException, ParseException

Is essentially a sum type. It says that if this method results in a failure value, it can fail with one of two types of failure values. It might not look like a new type, because in Java, types are almost always nominal, and this is structural, but that's what it is. I think that throw and catch clauses are the only place that Java will let you define an ad-hoc sum type. You have to use polymorphism everywhere else you want a variety of types.

Whereas in Rust, there are no structural sum types, and so the only way to make something resembling a sum type is:

    enum ConfigError {
        Io(io::Error),
        Parse(ParseIntError),
    }

Which means you also have to write the machinery to convert between the types.

I wonder if it would help to have a compiler- or library-defined From impl for all newtype enum variants (or all newtype structs more generally), that makes the variant from its argument. Or maybe it could be derived. It would wipe out a lot of this boilerplate.

Rusky 9 days ago [-]

I would love a `#[derive(From)]` for newtype structs and enum variants. Would bring Rust error handling back below Java in boilerplate levels. :)

reply

killercup 9 days ago [-]

There are some crates that add a derive for errors, but most people tend to use error-chain or quick-error. Both give you the boilerplate pretty much for free, with various other advantages. E.g., using error-chain, you can just write the above code as

    error_chain! {
        foreign_links {
            Io(io::Error);
            Parse(ParseIntError);
        }
    }

and it'll even generate an aliased `Result` type as well as fancy chaining support.

reply

Rusky 9 days ago [-]

Right- error_chain is just a bit too much magic for me compared to what #[derive(From)] would do.

reply

steveklabnik 9 days ago [-]

Check out https://crates.io/crates/newtype_derive

reply

twic 8 days ago [-]

Very nice. Now we just need enum variants to be first-class types, and some syntax for deriving on them.

reply

[–]Manishearth 22 points 10 days ago

I think error_chain is perhaps the best recommendation for folks who want to start writing error handling code in Rust, especially if you come from scripting languages.

    permalinkembedsavereportgive goldreply

[–]rushmorem 18 points 10 days ago

I love error-chain but, believe it or not, it took me a long time (longer than I would like to admit actually) to understand how to use it. Now that I understand it I'm actually embarrassed that I couldn't get it the first time. For a while I used quick-error when I really wanted to use error-chain instead. So my point is error-chain can be confusing for beginners too. Eventually I wrote my own error handling boilerplate crate using Macros 1.1.

    permalinkembedsaveparentreportgive goldreply

[–]ForeverAlot? 1 point 9 days ago*

I think I originally misunderstood the problem error-chain tries to solve. I thought it was about chain_err enabling me to chain multiple operations that can fail in different ways but after struggling with that for a while I ended up with far more lets than I expected. Now I think the point is really conversion of errors (the error_chain! macro), and chain_err seems more like a glorified try!/expect mixture that ends up working like exceptions; chain_err is not even in the documentation on docs.rs.

[Edit] This is for an executable, not a library.

    permalinkembedsaveparentreportgive goldreply---

kazagistar 9 days ago

parent flag favorite on: My Struggles with Rust

In case someone cares to understand what this means:

    trait From<T> {
        fn from(T) -> Self;
    }

---

...

Why does every library implement its own Error type? It feels like reinventing the wheel, and it takes a lot of boilerplate code.

If you need to distinguish different kinds of errors, why not, for example, have a lot of useful pre-defined error types like Python does?

reply

Rusky 9 days ago [-]

You can definitely reuse other library's error types. I often do that while prototyping.

The benefit of lib-specific error types, though, is that they can be far more specific.

reply

burntsushi 9 days ago [-]

As an example of the kind of specificity you might want, take a look at the error type for parsing a regular expression: https://docs.rs/regex-syntax/0.4.0/regex_syntax/enum.ErrorKi...

Now, in most cases, callers don't care at all about which specific error happened. They just want to print the error and be done with it. And that works just fine, because of the error handling machinery. But if you do want to drill down, then the option is there for you.

reply

---

pvg 8 days ago [-]

... If anything, Python's exception hierarchy started getting sorted out relatively recently.

reply

---

"In Python, you write the main case, and then you write an exception handler to deal with the error case. This works well in practice. Python has an exception class hierarchy. If you catch EnvironmentError?, you get almost everything that can go wrong due to a cause external to the program. If you catch IOError, you get all I/O-related errors, including all the things that can go wrong in HTTP land. "

---

jackcviers3 9 days ago

parent flag favorite on: My Struggles with Rust

It didn't invent any of these. This is railway-oriented-programming[1], using Either, leftMap and bind/flatMap, a conversion from Either to Try, with forced recover calls and explicit implicit conversions via typeclasses.

Other than the enforcement by the compiler, this is common in scala and f#, two other languages where raising errors is common.

Railway-oriented-programming is nice because it typechecks, and because it reduces cyclomatic complexity by short-circuiting without forcing you to handle the error until the end of the chain. Now, it is unfamiliar to those outside the FP community, but much of the rust language seems to be unfamiliar mixes of FP and low-level optimisation techniques.

With the Either type you don't even need to do that, but then errors on the left becomes convention instead of an enforced habit. The only odd part is the try macro. That should wrap the Val in a right, to preserve type information. I guess I could be convinced that Either = Error

Id [A].

[1]: https://fsharpforfunandprofit.com/rop/

---

[–]sepease 33 points 10 days ago

This is some pretty valid criticism. Here's how I would write the above example (with the addition of a main function).

extern crate serde;

  1. [macro_use] extern crate serde_derive; extern crate toml;
  2. [derive(Debug)] enum Error { OpenError?(std::io::Error), ReadError?(std::io::Error), ParseError?(toml::de::Error), }

type Result<T> = std::result::Result<T, Error>;

  1. [derive(Deserialize)] struct MyConfiguration? { jenkins_host: String, jenkins_username: String, jenkins_token: String, }

fn gimme_config(i_filename: &str) -> Result<MyConfiguration?> { use std::io::Read; let mut f = std::fs::File::open(i_filename) .map_err(Error::OpenError?)?; let mut s = String::new(); f.read_to_string(&mut s) .map_err(Error::ReadError?)?; toml::from_str(&s) .map_err(Error::ParseError?) }

fn main() { use std::io::Write; match gimme_config("jenkins.toml") { Ok(c) => writeln!(&mut std::io::stdout(), "{}@{}#{}", c.jenkins_username, c.jenkins_host, c.jenkins_token), Err(e) => writeln!(&mut std::io::stderr(), "Unable to read config - {:?}", e), }.expect("Unexpected error") }

    permalinkembedsavereportgive goldreply

[–]burntsushiripgrep · rust 12 points 10 days ago

    This is a lovely, readable and powerful idiom which doesn't appear either in the old or the new books' chapters on error handling.

It's in the old one, but not particularly emphasized: https://doc.rust-lang.org/book/error-handling.html#defining-your-own-error-type

It's not emphasized for a reason. It looks nice in a short example like this, but writing out the From impls quickly becomes a lot more economical.

[–]sepease 3 points 10 days ago

I generally try to avoid from impls where possible because I feel like that leads to too much depth of the interface - you have to start digging in to the helper libraries to understand how to key off of a specific error, and if one of those helper libraries changes, it breaks your external interface. Last but not least, it also doesn't convey the context of the error in that function.

E.g. If a function opens a config file and then a resource, getting a generic Error::IOError(missing file error) doesn't help much to narrow down the issue. Whereas Error::ResourceOpenError?(missing file error) is something more useful for higher order logic and error handling. As long as someone keys off Error::ResourceOpenError?(_) they don't need to rewrite their code of it changes to a hyper Error or something.

This is an ideal and time constraints don't always allow it, in that case I've resorted to the from impl. I still need to take the time to understand error_chain.

    permalinkembedsaveparentreportgive goldreply

[–]burntsushiripgrep · rust 2 points 10 days ago

    I generally try to avoid from impls where possible because I feel like that leads to too much depth of the interface - you have to start digging in to the helper libraries to understand how to key off of a specific error, and if one of those helper libraries changes, it breaks your external interface.

"Too much depth" is a decent one. Unless the error is defined in std, I generally don't expose error types from other libraries unless it makes sense for that library to be a public dependency. (In which case, a semver bump in a public dependency requires a semver bump from you if you want your library to work the the public dep update anway.)

    Last but not least, it also doesn't convey the context of the error in that function.

Right. If you need additional context (like a file path name when an io::Error occurs), then you can't use From.

    permalinkembedsaveparentreportgive goldreply

---

burntsushi 9 days ago

parent flag favorite on: My Struggles with Rust

> Another way would be having more syntactic sugar for Result-style monadic error handling [...] Another issue raised by the original post is the fact that Rust has no top-level concrete error type that is convertible from all of the specific error types.

Well... Rust has both. Any type that satisfies the `Error` trait can be converted to `Box<Error>`, which is provided by the `impl<T: Error> Error for Box<T>` and `impl<E: Error> From<E> for Box<Error>` impls. And it so happens that this conversion can be done for you automatically using Rust's `?`. For example, this:

    use std::error::Error;
    use std::fs::File;
    use std::io::{self, Read};
    fn main() {
        match example_explicit() {
            Ok(data) => println!("{}", data),
            Err(err) => println!("{}", err),
        }
    }
    fn example_explicit() -> Result<String, Box<Error>> {
        let file = match File::open("/foo/bar/baz") {
            Ok(file) => file,
            Err(err) => return Err(From::from(err)),
        };
        let mut rdr = io::BufReader::new(file);
        let mut data = String::new();
        if let Err(err) = rdr.read_to_string(&mut data) {
            return Err(From::from(err));
        }
        Ok(data)
    }

can be written as this without any fuss:

    use std::error::Error;
    use std::fs::File;
    use std::io::{self, Read};
    
    fn main() {
        match example_sugar() {
            Ok(data) => println!("{}", data),
            Err(err) => println!("{}", err),
        }
    }
    
    fn example_sugar() -> Result<String, Box<Error>> {
        let file = File::open("/foo/bar/baz")?;
        let mut rdr = io::BufReader::new(file);
        
        let mut data = String::new();
        rdr.read_to_string(&mut data)?;
        Ok(data)
    }

The problem is that the conversion to `Box<Error>` winds up making it harder for callers to inspect the underlying error if they want to. This is why this approach doesn't work well in libraries, but for simple CLI applications, it's Just Fine.

---

kstenerud 9 days ago

parent flag favorite on: My Struggles with Rust

Java's checked exceptions were a disaster. It essentially handcuffed you, limiting what you could do in an overridden method (because you can't add more exceptions to the throws list). So you end up wrapping in RuntimeExceptions? and then later having the whole app fall over because the framework that's expecting your class was only designed to handle the checked exceptions.

So yeah, want your implementation to consult a database? Sorry, the interface you must implement doesn't declare any checked exceptions, so say hello to app-killing RuntimeException? wrapped SQLExceptions :/

concede_pluto 9 days ago [-]

> interface you must implement doesn't declare any checked exceptions

It verges on malpractice to design an interface and declare that no implementation of it could possibly ever fail. I'm looking at you, Runnable.

reply

scott_s 8 days ago [-]

I ran into this when writing an ANTLR parser. I wanted to throw specialized exceptions based on what went wrong in the parse, but these couldn't be checked exceptions for the above reason. So I had to derive them from RuntimeException?, and then I wrapped the call to the parser so that it caught just this kind of exception, and re-threw it as a non-RuntimeException? so that it was checked throughout the rest of the code. I think this is an ANTLR-idiom, which I found to be an odd quirk - but one that's unavoidable because of how ANTLR and Java are designed.

reply

---

f1b37cc0 9 days ago

parent flag favorite on: My Struggles with Rust
  > import json
  > with open("config.json") as f:
  >  contents = f.read()
  > config = json.loads(contents)

translates to:

  extern crate serde_json as json;
  fn read_json() -> Result<json::Value, Box<std::error::Error>> {
      let file = std::fs::File::open("config.json")?;
      let config = json::from_reader(&file)?;
      Ok(config)
  }

And

  > import configparser
  > config = ConfigParser()
  > config.read("config.conf")

can be translated to:

  extern crate config;
  use config::{Config, File, FileFormat};
  fn read_config() -> Result<Config, Box<std::error::Error>> {
      let mut c = Config::new();
      c.merge(File::new("config", FileFormat::Json))?;
      Ok(c)
  }

Difficult stuff indeed.

stable-point 9 days ago [-]

I think this highlights that while there are easy solutions to the problem the OP faced, they're difficult for a newcomer to discover. (I have been using Rust for a couple of months and I'd also have reached for serde and maybe serde_derive to solve the problem).

Hopefully this is something the Libz Blitz[0] will solve with their Rust Cookbook[1]. (You could almost but not quite arrive at as simple a solution from chapters 1 and 2).

[0] https://blog.rust-lang.org/2017/05/05/libz-blitz.html

[1] https://brson.github.io/rust-cookbook/intro.html

reply

leshow 9 days ago [-]

The curious thing is that OP linked to the error handling docs, and the solution with Box<Error> is right there.

reply

---

erickt 11 days ago

parent flag favorite on: My Struggles with Rust

Hello Justin Turpin! Sorry to hear your struggles with rust. It's always going to be a bit more verbose using rust than Python due to type information, but I think there are some things we could do to simplify your code. Would you be comfortable posting the 20 line code for us to review? I didn't see a link in your post.

Anyway, so some things that could make your script easier:

    let mut file = File::open("conf.json")
        .expect("could not open file");

(Aside: I never liked the method name `expect` for this, but is too late to do anything about that now).

    let config: Value = serde::from_reader(file)
        .expect("config has invalid json");
    let jenkins_server = config.get("jenkins_server")
        .expect("jenkins_server key not in config")
        .as_str()
        .expect("jenkins_server key is not a string");

There might be some other things we could simplify. Just let us know how to help.

[0]: https://docs.serde.rs/serde_json/enum.Value.html

[1] https://docs.serde.rs/serde_json/de/fn.from_reader.html

---

Inufu 9 days ago

parent flag favorite on: My Struggles with Rust

My main gripe with Rust so far has been the unnecessary profusion of Result<> types, making it hard to process and forward errors.

Case in point: the example in the article from the rust documentation that converts errors to strings just to forward them: https://doc.rust-lang.org/book/error-handling.html#the-limit... https://doc.rust-lang.org/book/error-handling.html#the-limits-of-combinators

In practice, I find a type like Google's util::StatusOr? (https://github.com/google/lmctfy/blob/master/util/task/statu...) a lot easier to use (I've written >100kloc c++ using it). This uses a standardized set of error codes and a freeform string to indicate errors. I've yet to encounter a case where these ~15 codes were insufficient: https://github.com/google/lmctfy/blob/master/util/task/codes... https://github.com/google/lmctfy/blob/master/util/task/statusor.h https://github.com/google/lmctfy/blob/master/util/task/codes.proto

(((my note: statusOr appears to be an Either<Status, T> where the error side of the Either is of type Status, and the value side is of type T))

bascule 9 days ago [-]

> Case in point: the example in the article from the rust documentation that converts errors to strings just to forward them

This section:

Rust errors can be forwarded as simply as "?". The conversions can be handled automatically with "From" traits. The "error-chain" crate takes care of these conversions for you, wrapping the original errors so they're still available (including stack traces), but aggregating them under a set of error types specific to your crate:

https://github.com/brson/error-chain

reply

---

"It has now been two years, and nearly all of my objections went away as the language evolved and as I adapted to using it. Two years later, I just have two notable things to grumble about...1. No condition system. This is a big deal. Clojure piggy-backs on Java's exceptions, and currently lacks a good mechanism for user-defined error handling. Unfortunately, Java exception handling also happens to suck, since it throws away data in all stack frames from the throw to the catch. Implementing better error handling is under discussion on the Clojure development page (http://dev.clojure.org/display/design/Error+Handling). (Steve complains about the lack of non-local transfer of control, and he has a point: it could be used to make an arbitrarily powerful condition system, see http://www.nhplace.com/kent/Papers/Condition-Handling-2001.html). ... "

---

" Runtime delegation: this is one of the reasons unit testing is really easy. You guys know about Smalltalk, uh, method-missing? [doesNotUnderstand actually] It's method_missing in Ruby. It's the... if you call an object — I think Objective C probably has something like this too. "

---

Matthias247 12 days ago [-]

I think having Result alone does not make error handling complicated, but having different error types for each operation (and concrete result) instead of using one generic error type for all of them does by pushing the job of unifying erros towards the user.

Go works around the problem by Error being an interface, which means any function can return any kind of error without needing to transform it to another form. However Go benefits from the Garbage Collector here - I totally understand why Rust libraries don't want to return heap allocated errors.

Maybe C++ std::error_code/error_condition provides some kind of middle ground: It should not require a dynamic allocation. And yet the framework can be expanded: Different libraries can create their own error codes (categories), and error_codes from different libraries can all be handled in the same way: No need for a function that handles multiple error sources to convert the error_codes into another type.

The downside is that the size of the error structure is really fixed and there's no space to add custom error fields to it for error conditions that might require it. A custom error type in Result<ResultType?,ErrorType?> can be as big or small as one needs.

reply

pcwalton 12 days ago [-]

> However Go benefits from the Garbage Collector here - I totally understand why Rust libraries don't want to return heap allocated errors.

This doesn't have to do with the GC or lack thereof. Instead it's part of the philosophy of zero-cost abstractions: idiomatic C libraries don't require heap allocations to return errors, so neither does Rust.

reply

leshow 12 days ago [-]

Note that Rust's Error is also a trait (similar to an interface) and Box<Error> does return heap allocated errors, and you're free to use that if you like. Being a trait object, it will erase the type of the error, but you don't have to write any of the accompanying From or Error implementations you normally would with your own ErrorType?.

reply

whyever 12 days ago [-]

> instead of using one generic error type for all of them

But you can do that in Rust, just use Box<Error>. No library does that though, because it is not a zero-cost abstraction.

reply

pornel 12 days ago [-]

With the addition of `?` operator I think it's no longer the problem. It keeps error handling explicit, but the syntax is small enough that it doesn't make code noisy or tedious to write.

Note that you can also make your functions return `Box<Error>` which works like a base class for all common errors, so you don't have to worry about converting error types.

reply

Inufu 12 days ago [-]

Sure, but if you want to actually check for some error condition (say distinguish between file opened ok, file not found, or some other error) - which is the whole point of having an error type to begin with, otherwise you could just use optional - you still have to look up the definition of the actual error type used every time.

A standardized error type used by everything removes that need - I know I can just call util::IsNotFoundError?(..), no matter which library I'm using.

reply

---

You may be interested in something like https://github.com/tailhook/quick-error. It lets you easily implement From traits for errors so that you can convert between error types.

reply

Const-me 12 days ago [-]

> I've yet to encounter a case where these ~15 codes were insufficient

Insufficient on Windows.

There’re thousands error codes you can get from any Windows-provided API.

You can pack each of them into a single int32 value (using HRESULT_FROM_WIN32 macro for old-style error codes, the newer APIs already return HRESULT), but still, significantly more than 15.

reply

jsolson 12 days ago [-]

Google's util::Status and util::StatusOr? are actually quite flexible. While the generic error space is recommended for most work (and really, rather a lot fits in that space), it does support the notion of other error spaces like POSIX or Windows. At Google† I do quite a bit of interacting with the kernel, so my code makes fairly heavy use of the POSIX space.

That said, in the vast majority of cases any error I might be reporting from the POSIX space can be just as if not more usefully expressed (for the consuming software) using one of those ~15 generic codes. If their semantics are properly adhered to, those codes give good guidance on when an operation is guaranteed to have failed (but can be retried), when it's guaranteed to have failed (but cannot be retried without changing the request), when its fate is unknown, and when it has succeeded. In many cases this allows for generic error handling policies that fit a given application well. With enormous error spaces that is much more challenging.

In the cases where the underlying error deliveries clear value and I'm communicating across an abstraction boundary (I find the intersection of these is relatively rare), the Status type supports (albeit somewhat awkwardly) nesting. That allows the basic error to be one of the canonical types and the precise error to be communicated as a nested Status.

† I work on Google Compute Engine's on-host network devices and dataplane.

reply

Const-me 11 days ago [-]

I mostly work on desktop and mobile software. “Unable to open the file: access denied” is helpful for end-user, will cause them to go fix filesystem permissions on the file they are trying to open, or restart the app elevated. “Unable to open the file: failed precondition” translates to “this software is broken, we don’t know why”

> With enormous error spaces that is much more challenging.

Not sure I understand the problem. On Windows, APIs are typically designed as reliable (this applies to both OS API, and the way third-party developers design stuff). If something is failed but the condition is temporary and might have fixed with retry, well-designed API will retry itself, possibly accepting timeout argument. That’s why you can do generic error handling just fine: FAILED() macro is enough for 99% cases.

reply

---

" DOMException - INDEX_SIZE_ERR = 1 - DOMSTRING_SIZE_ERR = 2 - HIERARCHY_REQUEST_ERR = 3 - WRONG_DOCUMENT_ERR = 4 - INVALID_CHARACTER_ERR = 5 - NO_DATA_ALLOWED_ERR = 6 - NO_MODIFICATION_ALLOWED_ERR = 7 - NOT_FOUND_ERR = 8 - NOT_SUPPORTED_ERR = 9 - INUSE_ATTRIBUTE_ERR = 10 - INVALID_STATE_ERR = 11 - SYNTAX_ERR = 12 - INVALID_MODIFICATION_ERR = 13 - NAMESPACE_ERR = 14 - INVALID_ACCESS_ERR = 15 - VALIDATION_ERR = 16 - TYPE_MISMATCH_ERR = 17 - SECURITY_ERR = 18 - NETWORK_ERR = 19 - ABORT_ERR = 20 - URL_MISMATCH_ERR = 21 - QUOTA_EXCEEDED_ERR = 22 - TIMEOUT_ERR = 23 - INVALID_NODE_TYPE_ERR = 24 - DATA_CLONE_ERR = 25 "

---

"

---

" This too might feel like a surprise, but I'm not convinced that we've "solved" error management, in general, in any language. We have a few credible schools of design-philosophy mostly hold together well enough to ship a language: algebraic effects and handlers, checked and unchecked exceptions, crash-failure with partitions and supervision trees, monads, result sum-types, condition / restart systems, transactions with rollbacks; but none of them completely solves the design space well enough that the problem feels "done". Even committing to one or another such regime -- and it really does involve not just a few mechanisms, but a whole set of interlocking protocols that support error management -- there are serious abstraction leakages and design tradeoffs in nearly every known approach (modularity, compositionality, locality, synchronicity, soundness, cognitive load, implementation costs, interface costs with different regimes). Errors are absurdly hard to get right.

Daira Hopwood has some notes and design material in hir work on the Noether language, and there's (again) a lot to read before deciding how to proceed. " [6]

---

http://www.cybertec.at/why-favor-postgresql-over-mariadb-mysql/

0, empty string, and null should be different and not autocoerced

when inserting values into tables or altering the type of entries in tables, if some value cannot be exactly translated to the new type, there should be an error

---

tiehuis 1 day ago [-]

I've been writing a fair bit since a few months ago, and have written a few things for the stdlib. Here are some examples I like about it.

Hassle-free error management

Consider you write a function

  fn div(a: u8, b: u8) -> u8 {
      a / b
  }

If later you find an error case that needs to be handled updating the code usually will not require much extra addition.

  error DivideByZero;
  fn div(a: u8, b: u8) -> %u8 {
      if (b == 0) {
          error.divideByZero
      } else {
          a / b
      }
  }

The caller can choose to ignore errors using `%%div(5, 1)` or can propagate errors to the caller, similar to Rust's `try!`, `?` using `%return div(5, 2)`.

I find this so easy that I'm much more inclined to think about edge cases and handle errors up front. I find when writing Rust the extra setup and management of errors adds a fair bit of tedium (although to be fair, with error-chain and proper setup at the beginning of a project this isn't too bad).

reply

bluejekyll 1 day ago [-]

>The caller can choose to ignore errors using `%%div(5, 1)` or can propagate errors to the caller, similar to Rust's `try!`, `?` using `%return div(5, 2)`.

>I find this so easy that I'm much more inclined to think about edge cases and handle errors up front. I find when writing Rust the extra setup and management of errors adds a fair bit of tedium (although to be fair, with error-chain and proper setup at the beginning of a project this isn't too bad).

How is this different than say:

   5_u8.checked_div(1).unwrap()

That unwrap() is performing the same thing as your %% example, if I understand it correctly.

Are the Error types in Zig on the stack or the heap? By default Rust puts everything, including errors, on the stack, which means that the size of the return type always needs to be known. To make this easier you can return Boxed errors:

   fn this_errors() -> Result<u32, Box<Error>> { ... }

And then error types can be very simple. Also, I recommend people getting into Rust really checkout error_chain!, which is a macro that helps in combining all the errors that your library might need to deal with: https://docs.rs/error-chain/0.11.0/error_chain/

reply

tiehuis 23 hours ago [-]

Yes you have it right, `unwrap()` is equivalent to `%%`. It will panic if the result is an error.

Error values under the hood are just unsigned integers and are returned on the stack. In fact, the granularity at which allocators are exposed in the stdlib makes any possible dynamic allocation very explicit in the language.

This link [1] provides an overview of errors and some of the surrounding control flow.

[1]: http://ziglang.org/documentation/#errors

reply

bluejekyll 23 hours ago [-]

Ah. Thanks for the link. I didn't see anything that section talk about passing values with the Errors, based on your comment, is that even possible?

If you had an error you wished to pass a reason string for, would you need to use a global static or something?

reply

tiehuis 23 hours ago [-]

Not with the builtin error type. Its pretty much analogous to a c error code.

If you wanted to send data you would need some other means like you suggest. I'd be interested in finding an ergonomic solution to this but it probably wouldn't be at the language level.

reply

---

from [7]:

Python3 'chained exceptions' show the original exception when caught and then re-raised:

    Situation: you catch an exception with except, do something, and then raise a different exception.

Problem: You lose the original traceback

Python 3 shows you the whole chain of exceptions:

You can also do this manually using raise from

raise exception from e

---

Python3 new OSError subclasses

5.2.1. OS exceptions

The following exceptions are subclasses of OSError, they get raised depending on the system error code.

exception BlockingIOError?

    Raised when an operation would block on an object (e.g. socket) set for non-blocking operation. Corresponds to errno EAGAIN, EALREADY, EWOULDBLOCK and EINPROGRESS.
    In addition to those of OSError, BlockingIOError can have one more attribute:
    characters_written
        An integer containing the number of characters written to the stream before it blocked. This attribute is available when using the buffered I/O classes from the io module.

exception ChildProcessError?

    Raised when an operation on a child process failed. Corresponds to errno ECHILD.

exception ConnectionError?

    A base class for connection-related issues.
    Subclasses are BrokenPipeError, ConnectionAbortedError, ConnectionRefusedError and ConnectionResetError.

exception BrokenPipeError?

    A subclass of ConnectionError, raised when trying to write on a pipe while the other end has been closed, or trying to write on a socket which has been shutdown for writing. Corresponds to errno EPIPE and ESHUTDOWN.

exception ConnectionAbortedError?

    A subclass of ConnectionError, raised when a connection attempt is aborted by the peer. Corresponds to errno ECONNABORTED.

exception ConnectionRefusedError?

    A subclass of ConnectionError, raised when a connection attempt is refused by the peer. Corresponds to errno ECONNREFUSED.

exception ConnectionResetError?

    A subclass of ConnectionError, raised when a connection is reset by the peer. Corresponds to errno ECONNRESET.

exception FileExistsError?

    Raised when trying to create a file or directory which already exists. Corresponds to errno EEXIST.

exception FileNotFoundError?

    Raised when a file or directory is requested but doesn’t exist. Corresponds to errno ENOENT.

exception InterruptedError?

    Raised when a system call is interrupted by an incoming signal. Corresponds to errno EINTR.

exception IsADirectoryError?

    Raised when a file operation (such as os.remove()) is requested on a directory. Corresponds to errno EISDIR.

exception NotADirectoryError?

    Raised when a directory operation (such as os.listdir()) is requested on something which is not a directory. Corresponds to errno ENOTDIR.

exception PermissionError?

    Raised when trying to run an operation without the adequate access rights - for example filesystem permissions. Corresponds to errno EACCES and EPERM.

exception ProcessLookupError?

    Raised when a given process doesn’t exist. Corresponds to errno ESRCH.

exception TimeoutError?

    Raised when a system function timed out at the system level. Corresponds to errno ETIMEDOUT.

New in version 3.3: All the above OSError subclasses were added.

See also

PEP 3151 - Reworking the OS and IO exception hierarchy

---

KerrickStaley? 97 days ago [-]

One big thing that's missing from this list is the __traceback__ on exceptions, which pretty much does what you think it does. In Python 2, there's no way to access the traceback for an exception once you've left the `except:` block. This matters when you're using things like gevent; if one of your gevent greenlets throws an exception and you inspect the .exception attribute on it, you'll be able to get the exception message but won't know what line it came from.

N.B. This is absent from Python 2 due to concerns with creating self-referential loops. The garbage collector got better in the meantime and the feature was never backported to Python 2.

---

"

Error handling

Explicit vs. implicit error handling is a contentious issue, but it is a safe bet that if you have chosen to use Go you strongly favor explicit error handling. The language does not make it easy to avoid handling your errors.

Unfortunately, there is one place where even a strong supporter of explicit error handling can admit the process is tedious: when writing “all or nothing” scripts. That is, programs that either follow the narrow success path completely, or if they step off the path even slightly, exit immediately in error. Small Python scripts follow this process by default, and bash script authors often do by placing set -e at the top of their scripts.

Indeed, a common source of consternation for Java or Python programmers coming to Go is discovering that the small program they attempted to write to try out Go ended up needlessly wordy. This:

  1. !/usr/bin/python f = open("hello.txt","w" f.write("Hello, World!") f.write("Next line.") f.close()

Becomes:

package main

import ( "os" "log" "fmt" )

func main() { f, err := os.Create("hello.txt") if err != nil { log.Fatal(err) } if _, err := fmt.Fprintf(f, "Hello, World!\n"); err != nil { log.Fatal(err) } if _, err := fmt.Fprintf(f, "Next line.\n"); err != nil { log.Fatal(err) } if err := f.Close(); err != nil { log.Fatal(err) } }

This is not quick to write and is one of the few places where explicit error handling in Go is unhelpful. Our script is no more robust for handling these errors explicitly.

Neugram is designed to help with scripts like these.

Neugram is set -e for Go:

  1. !/usr/bin/ng

import "os" import "fmt"

f := os.Create("hello.txt") fmt.Fprintf(f, "Hello, World!\n") fmt.Fprintf(f, "Next line.\n") f.Close()

The elided errors in the Neugram program will turn into panics if they are non-nil. The result is a script which is not too much wordier than the python version, while taking advantage of all my Go knowledge. If you are a Go programmer, this may be interesting to you. "

---

" Feature 3: Chained exceptions

    Situation: you catch an exception with except, do something, and then raise a different exception.
    ...
    Problem: You lose the original traceback
    ...
    Python 3 shows you the whole chain of exceptions:

" [www.asmeurer.com/python3-presentation/slides.html#72]

---

" Feature 4: Fine grained OSError subclasses

    The code I just showed you is wrong.
    It catches OSError and assumes it is a permission error.
    But OSError can be a lot of things (file not found, is a directory, is not a directory, broken pipe, ...)... Python 3 fixes this by adding a ton of new exceptions. " [www.asmeurer.com/python3-presentation/slides.html#72]

---

" Feature 6: No more comparison of everything to everything

    In Python 2, you can do
    >>> max(['one', 2])

" [www.asmeurer.com/python3-presentation/slides.html#72]

---

starting here is the Rust tutorial on error handling using Result types (which is an Option but with an error instead of just a None). I think this is broadly how we want to do error handling in Oot (this link starts in the middle of the chapter; earlier the chapter talked about just using 'panic', and about using Options and Option.unwrap; but we don't want either of those (mainly) for Oot, instead we want something like Result):

https://doc.rust-lang.org/nightly/rust-by-example/error/result.html

---

gonum's policy for panic vs error in Golang:

" Rule #1: NO SILENT BUGS - Input is either clearly correct or it is wrong. Panic: Error is easy to check before call (eg "Add(dst, s []float64) Panics if the lengths of dst and s do not match"). Error: Impossible to check without performing the operation (eg "If A is singular or near-singular, a Condition error is returned.") " [8]

this guy likes it: "gonum has a good policy of when errors are returned and when panics happen" [9]

---

Golang's syntax for something that might have an error:

func (m *Dense) Solve(a, b Matrix) error { ... }

---

Summary of my conclusions from [10] (see my notes in ootErrorNotes3Old):

---

https://blog.ionelmc.ro/2014/08/03/the-most-underrated-feature-in-python-3/

---

https://kotlinlang.org/docs/reference/null-safety.html

(almost the entire page quoted here; see summary notes in next section)

" Null Safety Nullable types and Non-Null Types

Kotlin's type system is aimed at eliminating the danger of null references from code, also known as the The Billion Dollar Mistake.

...

Kotlin's type system is aimed to eliminate NullPointerException?'s from our code. The only possible causes of NPE's may be:

    An explicit call to throw NullPointerException();
    Usage of the !! operator that is described below;
    Some data inconsistency with regard to initialization, such as when:
        An uninitialized this available in a constructor is passed and used somewhere ("leaking this");
        A superclass constructor calls an open member whose implementation in the derived class uses uninitialized state;
    Java interoperation:
        Attempts to access a member on a null reference of a platform type;
        Generic types used for Java interoperation with incorrect nullability, e.g. a piece of Java code might add null into a Kotlin MutableList<String>, meaning that MutableList<String?> should be used for working with it;
        Other issues caused by external Java code.

In Kotlin, the type system distinguishes between references that can hold null (nullable references) and those that can not (non-null references). For example, a regular variable of type String can not hold null:

var a: String = "abc" a = null compilation error

To allow nulls, we can declare a variable as nullable string, written String?:

var b: String? = "abc" b = null ok

Now, if you call a method or access a property on a, it's guaranteed not to cause an NPE, so you can safely say:

val l = a.length

But if you want to access the same property on b, that would not be safe, and the compiler reports an error:

val l = b.length error: variable 'b' can be null

But we still need to access that property, right? There are a few ways of doing that.

CHECKING FOR NULL IN CONDITIONS

First, you can explicitly check if b is null, and handle the two options separately:

val l = if (b != null) b.length else -1

The compiler tracks the information about the check you performed, and allows the call to length inside the if.

...

Note that this only works where b is immutable (i.e. a local variable which is not modified between the check and the usage or a member val which has a backing field and is not overridable), because otherwise it might happen that b changes to null after the check.

SAFE CALLS

Your second option is the safe call operator, written ?.:

b?.length

This returns b.length if b is not null, and null otherwise. The type of this expression is Int?.

Safe calls are useful in chains. For example, if Bob, an Employee, may be assigned to a Department (or not), that in turn may have another Employee as a department head, then to obtain the name of Bob's department head (if any), we write the following:

bob?.department?.head?.name

Such a chain returns null if any of the properties in it is null.

To perform a certain operation only for non-null values, you can use the safe call operator together with let:

val listWithNulls: List<String?> = listOf("A", null) for (item in listWithNulls) { item?.let { println(it) } prints A and ignores null }

A safe call can also be placed on the left side of an assignment. Then, if one of the receivers in the safe calls chain is null, the assignment is skipped, and the expression on the right is not evaluated at all:

If either `person` or `person.department` is null, the function is not called: person?.department?.head = managersPool.getManager()

ELVIS OPERATOR

When we have a nullable reference r, we can say "if r is not null, use it, otherwise use some non-null value x":

val l: Int = if (b != null) b.length else -1

Along with the complete if-expression, this can be expressed with the Elvis operator, written ?::

val l = b?.length ?: -1

If the expression to the left of ?: is not null, the elvis operator returns it, otherwise it returns the expression to the right. Note that the right-hand side expression is evaluated only if the left-hand side is null.

Note that, since throw and return are expressions in Kotlin, they can also be used on the right hand side of the elvis operator. This can be very handy, for example, for checking function arguments:

fun foo(node: Node): String? { val parent = node.getParent() ?: return null val name = node.getName() ?: throw IllegalArgumentException?("name expected") ... }

THE !! OPERATOR

The third option is for NPE-lovers: the not-null assertion operator (!!) converts any value to a non-null type and throws an exception if the value is null. We can write b!!, and this will return a non-null value of b (e.g., a String in our example) or throw an NPE if b is null:

val l = b!!.length

Thus, if you want an NPE, you can have it, but you have to ask for it explicitly, and it does not appear out of the blue.

SAFE CASTS

Regular casts may result into a ClassCastException? if the object is not of the target type. Another option is to use safe casts that return null if the attempt was not successful:

val aInt: Int? = a as? Int

COLLECTIONS OF NULLABLE TYPE

If you have a collection of elements of a nullable type and want to filter non-null elements, you can do so by using filterNotNull:

val nullableList: List<Int?> = listOf(1, 2, null, 4) val intList: List<Int> = nullableList.filterNotNull()

"

---

notes on the previous section:

" Kotlin's type system is aimed to eliminate NullPointerException?'s from our code. The only possible causes of NPE's may be:

    An explicit call to throw NullPointerException();
    Usage of the !! operator that is described below;
    Some data inconsistency with regard to initialization, such as when:
        An uninitialized this available in a constructor is passed and used somewhere ("leaking this");
        A superclass constructor calls an open member whose implementation in the derived class uses uninitialized state;
    Java interoperation:
    ..."

it is a compile-time error to directly access or assign from a variable of nullable type. By default, types are not nullable; to make a nullable type, append '?' to the type name, eg:

var b: String? = "abc" b = null ok

Some operations can be appended with '?' to make them return null in case of failure; for example, 'as?' is the 'safe cast' operation which, if the attempt to cast is unsuccessful, returns null.

To access values of nullable variables, the options are:

" val l = b?.length ?: -1

If the expression to the left of ?: is not null, the elvis operator returns it, otherwise it returns the expression to the right. Note that the right-hand side expression is evaluated only if the left-hand side is null.

Note that, since throw and return are expressions in Kotlin, they can also be used on the right hand side of the elvis operator. This can be very handy, for example, for checking function arguments:

fun foo(node: Node): String? { val parent = node.getParent() ?: return null val name = node.getName() ?: throw IllegalArgumentException?("name expected") ... } "

note that some of these are the same as ideas i already had for Oot: namely putting ' before or after things, depending on context, mimics:

putting ' on one side of an expression is like Kotlin's !!, but putting ' on the other side of an expression catches any exceptions from evaluating that expression and turns them into nulls

i was also planning to check for nulls in conditions.

so it looks like we should add:

---

parts of code that could have a type error (eg by doing typecasting) must declare it as a thrown exception, if they throw exceptions

---

exceptions are objects, and if you have a bunch of different types of exceptions which are all subclasses of one superclass, it suffices to declare the superclass

---