The time complexity of comparing variables for equivalence during type unification is reduced from O(n!) to O(n). As a result, some programming patterns compile much, much more quickly.
I love this. Not just my code compiling more quickly, but the underlying implementation is super interesting.
Is this something seen frequently in user code, or just an edge case? I'd love to get even faster with my libraries compiling, but honestly the continuous improvements in compile speed over the last half a year have already made me happy :)
Excited about unwinding becoming stable. I am hacking on postgres-extension.rs, which allows writing postgres extensions in rust. This will mean that postgres could call into rust code, then rust could call back into postgres code, and that postgres code could throw an error, and rust could safely unwind and re-throw. Cool!!
Very much pleased to see you working on this. I've been thinking that Rust would make an excellent language for those of us that want to get a bit closer to C-like backed functions and such, but aren't really C programmers. While Rust has it's own learning curve to be sure and isn't magic, the compiler can save those of us familiar with less systemy languages from mistakes we wouldn't otherwise be sensitive to.
> Altogether, this relatively small extension to the trait system yields benefits for performance and code reuse, and it lays the groundwork for an "efficient inheritance" scheme that is largely based on the trait system
I don't understand the announcement on panics. Hasn't it always been the case that thread boundaries (via spawn) could contain panics?
It's also used to have the incorrect default that such panics were silently ignored. .NET made this same mistake: background threads could silently die. They reversed and made a breaking change so any uncaught exception kills the process. I'd imagine Rust will do so by encouraging a different API if they haven't already. (I opened an RFC on this last year, but I didn't understand enough Rust and made a mess of it. But still a great experience, speaking to the very kind and professional community. In particular several people were patient, but firm, in explaining my misunderstandings.)
> Hasn't it always been the case that thread boundaries (via spawn)
> could contain panics?
Yes. This lets you catch an unwinding panic from inside the thread itself, rather than observing it get killed from the outside.
In Rust, they're not silently ignored: `join()` returns a `Result`, which has a `must_use` attribute, which will warn if you don't do something with it.
I don't know how this unexpected vs expected errors philosophy gets propagated, but to me it always looked suspicious. Take array bounds for example: what if you have an API that lets users send a list of image transformations, and the user requests a face-detect crop, followed by a fixed crop with a given x, y, w, h.
Clearly your code can get out of (2D) array bounds with the fixed crop (if the image is such that the face-detect crop ends up small enough). Suddenly the thing that was "unexpected" at the array level becomes very much expected at the higher API level.
So the API provider can't decide whether an error is expected or not. Only the API consumer can do that. Applying this further, a mid-level consumer cannot (always) make the decision either. Which is why exceptions work the way they do: bubble until a consumer makes a decision, otherwise consider the whole error unexpected. Mid-level consumers should use finally (or even better, defer!) to clean up any potentially bad state.
I think Swift got this right. What you care about isn't what exactly was thrown (checked, typed exceptions) but the critical thing is whether a call throws or not. This informs us whether to use finally/defer to clean up. The rest of error handling is easy: we handle errors we expect, we fix errors we forgot to expect, but either way we don't crash for clean up because finally/defer/destructors take care of that.
This is why almost every panicky API has a non-panicky variant.
Non-panicky APIs can be used in a panicky way via unwrap() or expect(). There are very few panicky APIs, reserved for things where 99% of the time you want it to panic and it would be annoying to have to handle errors all the time. Array indexing is one of these cases, where it would be super annoying to handle errors on each indexing operation.
But if you're in a situation where you expect an index to fail, just use `get()` which returns an Option instead.
APIs do not make the decision for you. At most they decide if the panicky API is easier to use than the monadic error one. Which is where the fuzzy notion of "unexpected" and "expected" errors at the API level comes up, it's good enough to be able to determine what the default should be. Callers still have the power to do something else.
Your example doesn't make sense. Keep in mind that an out of bounds array access can cause a lot of problems; in the best case, it would cause a segfault, and in the worst it would allow for code injection. So there isn't really a case where accessing an array out of bounds makes sense. That's where the idea of "expected vs unexpected errors" comes from.
Another way to think about it is that some errors can be handled and some can't. For example, most of the time you can't "fix" a segfault, so it makes sense for that to just crash the program.
I don't understand your example. The API provider has to determine the contract and decide whether out-of-bounds crops are supported or not. If they are supported, then an out-of-bounds panic is a bug in the API. If they are not supported, then an out-of-bounds panic is a bug in the caller. In either case, the exception is unexpected and due to someone not following the contract.
In cases where the caller might want to handle an error, Rust almost always uses an Option/Result. For vectors, the `get` method lets you try to get an element out without panicking if your index is bad. (Or of course you can just check the index manually. This is guaranteed to be threadsafe, which is nice.) The try! macro is the standard way to say "I don't want to handle errors here -- you take care of it."
Exceptions, at last! Not very good exceptions, though. About at the level of Go's "recover()". If this is done right, so that locks unlock, destructors run, and reference counts are updated, it's all the complexity of exceptions for some of the benefits.
I'd rather have real exceptions than sort-of exceptions.
These are not exceptions, and I guarantee that you and your users will be very, very miserable if you try to use them as such. :P This is primarily for preventing undefined behavior at FFI boundaries, there are no affordances in the language that make this mechanism anywhere comparable to a real exceptions system from, say, Python.
Users are strongly and emphatically discouraged from using this for expection-style control flow. This is for managing crashes at the thread or FFI boundary.
In general, we're very happy with monadic error handling and don't want exceptions.
I must say I came into Rust and didn't like the lack of exceptions - and these still aren't. The community explained it to me, and now I'm noticing more cases in other code (C,C#) where the forced error code system (handle or panic) would be far superior.
Having it at the thread boundary is good enough so you can write, say, a webserver, without letting user code or a mishandled request killing the process.
These are not exceptions, and are not intended to be used like exceptions. They may have an implementation that's kinda sorta similar to exceptions, but that doesn't make them exceptions.
[+] [-] asp2insp|9 years ago|reply
I love this. Not just my code compiling more quickly, but the underlying implementation is super interesting.
[+] [-] kvark|9 years ago|reply
[+] [-] jeffdavis|9 years ago|reply
[+] [-] greatidea|9 years ago|reply
[+] [-] sbuttgereit|9 years ago|reply
[+] [-] shmerl|9 years ago|reply
> Altogether, this relatively small extension to the trait system yields benefits for performance and code reuse, and it lays the groundwork for an "efficient inheritance" scheme that is largely based on the trait system
[+] [-] bryanray|9 years ago|reply
[+] [-] MichaelGG|9 years ago|reply
It's also used to have the incorrect default that such panics were silently ignored. .NET made this same mistake: background threads could silently die. They reversed and made a breaking change so any uncaught exception kills the process. I'd imagine Rust will do so by encouraging a different API if they haven't already. (I opened an RFC on this last year, but I didn't understand enough Rust and made a mess of it. But still a great experience, speaking to the very kind and professional community. In particular several people were patient, but firm, in explaining my misunderstandings.)
[+] [-] steveklabnik|9 years ago|reply
In Rust, they're not silently ignored: `join()` returns a `Result`, which has a `must_use` attribute, which will warn if you don't do something with it.
[+] [-] JoshTriplett|9 years ago|reply
[+] [-] steveklabnik|9 years ago|reply
[+] [-] spion|9 years ago|reply
Clearly your code can get out of (2D) array bounds with the fixed crop (if the image is such that the face-detect crop ends up small enough). Suddenly the thing that was "unexpected" at the array level becomes very much expected at the higher API level.
So the API provider can't decide whether an error is expected or not. Only the API consumer can do that. Applying this further, a mid-level consumer cannot (always) make the decision either. Which is why exceptions work the way they do: bubble until a consumer makes a decision, otherwise consider the whole error unexpected. Mid-level consumers should use finally (or even better, defer!) to clean up any potentially bad state.
I think Swift got this right. What you care about isn't what exactly was thrown (checked, typed exceptions) but the critical thing is whether a call throws or not. This informs us whether to use finally/defer to clean up. The rest of error handling is easy: we handle errors we expect, we fix errors we forgot to expect, but either way we don't crash for clean up because finally/defer/destructors take care of that.
[+] [-] Manishearth|9 years ago|reply
Non-panicky APIs can be used in a panicky way via unwrap() or expect(). There are very few panicky APIs, reserved for things where 99% of the time you want it to panic and it would be annoying to have to handle errors all the time. Array indexing is one of these cases, where it would be super annoying to handle errors on each indexing operation.
But if you're in a situation where you expect an index to fail, just use `get()` which returns an Option instead.
APIs do not make the decision for you. At most they decide if the panicky API is easier to use than the monadic error one. Which is where the fuzzy notion of "unexpected" and "expected" errors at the API level comes up, it's good enough to be able to determine what the default should be. Callers still have the power to do something else.
[+] [-] blaisio|9 years ago|reply
Another way to think about it is that some errors can be handled and some can't. For example, most of the time you can't "fix" a segfault, so it makes sense for that to just crash the program.
[+] [-] nickm12|9 years ago|reply
[+] [-] oconnor663|9 years ago|reply
[+] [-] wyldfire|9 years ago|reply
Speaking of which, DBC [1] would be an awesome feature for consideration. It's one of relatively few areas where D is superior to Rust IMO.
[1] https://github.com/rust-lang/rfcs/issues/1077
[+] [-] mtgx|9 years ago|reply
[+] [-] Nekit1234007|9 years ago|reply
[+] [-] steveklabnik|9 years ago|reply
There are no current plans for a 2.0 at this time.
[+] [-] Animats|9 years ago|reply
Exceptions, at last! Not very good exceptions, though. About at the level of Go's "recover()". If this is done right, so that locks unlock, destructors run, and reference counts are updated, it's all the complexity of exceptions for some of the benefits.
I'd rather have real exceptions than sort-of exceptions.
[+] [-] kibwen|9 years ago|reply
[+] [-] vvanders|9 years ago|reply
Error handling is something Rust does really well. Having used exception languages quite a bit I much prefer the forced handled return values.
Exceptions don't map well to the type of handling patterns like and_then(...), or_else(...) and the like which I find much more ergonomic and clean.
[+] [-] tatterdemalion|9 years ago|reply
In general, we're very happy with monadic error handling and don't want exceptions.
[+] [-] MichaelGG|9 years ago|reply
Having it at the thread boundary is good enough so you can write, say, a webserver, without letting user code or a mishandled request killing the process.
[+] [-] steveklabnik|9 years ago|reply