Announcing Rust 1.9
blog.rust-lang.org
blog.rust-lang.org
I love this. Not just my code compiling more quickly, but the underlying implementation is super interesting.
EDIT: The PR that added the change claims it was done well, though :) https://github.com/rust-lang/rust/pull/32062
The issue is just how degenerately bad "most" code is -- and I think that hinges a lot on how excited the word "higher" gets you vis-a-vis expressing your programs. If you think C# is a pretty cool type system that doesn't afraid of anything you'll probably be fine. If you think scala or haskell with All The GHC Extensions is where it's at... watch out.
You are the best Gankro. <3
What's nice about coherence is that there's little risk in modifying the constraint solving algorithm to optimize for the common use cases (because it can't be backward incompatible). What's nice about crater is that it's comparatively easy to test that optimization.
But I do plan to keep improving it.
> 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
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.
On thread panics: Suppose you have a long-running thread that's updating shared variable. You don't call join on it; just drop it, so it's detached, running in the background. If that thread crashes (and isn't in e.g. a mutex so it can't poison), your process will continue on in an invalid state. Yes, you could add code to notice the panic and abort the process, but by default, it'll silently fail, right?
> and isn't in eg a mutex so it can't poison
Well, Rust makes sure that you have _some_ kind of concurrency primitive in place to make this safe.It's also worth mentioning that Rust is concerned about preventing memory safety bugs, not every single kind of bug ever. I mean, we want to encourage robust code, but we don't make the claim that Rust solves every kind of bug possible with the type system.
It is the opposite of robust, more toward "on error resume next", to be rude. Just feels like an odd choice, that's all.
Edit: As an example: Suppose a background thread opens a file and writes out its pid and date every minute. If there's any panic on that thread, then the process will just go on, oblivious to the error. I find it hard to believe that's remotely close to the majority of intentions when spawning a detached background thread. For the few cases when you want "fire-and-forget-and-don't-care-if-it-fails", explicitly stating so seems wise.
The idea is not to make panics silent, but to be able to go from "this process is crashing" to "here's an error code".
It's not, but it beats it silently ending up in a bad state.
However, stabilizing `?` is not blocked on any particular work as much as on the relevant team deciding its a good feature without big bugs that they are happy to see made permanent. Given that a minority of the community opposes `?` vehemently, this process will probably move slowly.
Personally, I opt into question mark in all of my nightly code and I absolutely love it. I had a case last night actually where I really wanted `catch` also.
Just in general, it is a huge, and widely debated new feature that only landed recently. It needs more time before we can consider stabilizing it. One of the longest-running and most-commented RFCs in Rust history deserves to not just be made stable as quickly as possible, but to take it slow and make sure we get it right.
Why would stabilizing the Result version of '?' lock out the possibility of supporting it on Option or other types in the future?
In any case, I look forward to it whenever it becomes available in a stable release.
> Why would stabilizing the Result version
Well, because in stabilizing something, you have to declare exactly what is stable, and what the semantics are. "? only works with Result" is not a detailed enough semantic, so you have to dig into details. Is it based on a trait? If so, and the trait gets designed incorrectly, it could cause problems. Is it not based on a trait? Now the language needs to know about Result, specifically. > I just didn't realize that the '?' notation in particular was still under debate.
Well, we have never had a case yet where an accepted RFC failed to end up actually being stable. We have had some RFCs that have had final details take a very long time. But in theory, it could happen.I guess, my ultimate point is this: we have not made very many significant language changes in this first year of Rust. Now, we're starting to. It's new territory. Things can happen that may not have happened before, including these significant new features not actually landing.
It wouldn't necessarily but it could be done in a way where it does lock it out. For example, `?` could be implemented by defining an attribute which is applied to Result in libcore. If that attribute were stabilized, it would make it hard to migrate to using a trait to define the behavior of `?`, because you've made a guarantee that this attribute makes a type use `?`.
All of this has been avoided though, I think. Stabilization or deprecation of `?` is pretty much a political issue now and not a technical one (and I don't mean that the question is frivolous, just that its about what people want now).
There's still the Carrier trait which has unresolved technical questions, and the catch construct I don't think even has a PR.
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.
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.
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.
Yeah there is[1], unless I'm being really stupid...
[1]: https://doc.rust-lang.org/std/primitive.slice.html#method.ge...
In Rust, if your API uses monadic error handling (Result or Option), this is easily chained with other errors and propagated up. Additionally, callers can choose to discard errors they feel should not happen by using unwrap/expect to convert them into panics.
If your API uses panics, this can't be cleanly turned into non-panicky things without using catch_unwind (you can, but you shouldn't). In the rare case you make the decision to have an ergonomic panicky API, try to provide a slightly less ergonomic non-panicky one.
Also, in languages with finally and/or defer, catch isn't supposed to be a problem.
Not performance, ergonomics. `array[5]` and `array.get(5).unwrap()` do the same thing (with a different panic message) and have the same performance when optimized (since it should inline). It would be annoying to have to handle the error every time you index, and would kind of ruin the syntactic sugar of indexing. Thus, indexing panics, and you have `.get()` if you want to not panic and use monadic errors (but as you can see, you can make `.get()` panic too by unwrapping it).
The same goes for other panicky APIs, they're panicky because 99% of the time you don't want to deal with that error since it's an "unexpected" one which means something went horribly wrong and there's no sane way to continue. For the 1% of the time you do need to deal with that error, you can use the alternate Result/Option-based API.
> and there's no sane way to continue
Would like to hear more about this.
There's no same way to continue in cases where an out of bounds access is a program bug. What should a program do when it knows it is in an inconsistent state? Crash, in many cases.
This is a question of how the software was designed and what assumptions were made.
In a case where the programmer feels that out of bounds may happen (or wishes to program defensively), of course the usual monadic error APIs can be used instead of indexing directly.
Let me illustrate using my example. The mid-level consumer is a crop function that takes the image and coordinates. It has no reason to assume someone wont check the image bounds so it uses the panicky API to access the array, assuming "Ok, they'll check if they are out of image dimensions beforehand".
But another level up, we're passing user-generated data right to it, after the face detection crop. Clearly thats wrong. Unfortunately the mid-level consumer didn't provide a non-panicky API so we had to write our own wrapper.
The question is, is it crashy-wrong? If shared mutable state is cleaned up via RAII/defer/finally, there is nothing wrong with the "exception" being caught and the process can continue running. The reason why we don't know if something went horribly wrong is that we don't have the cleanup contract for handling these types of things in the first place.
So why do we want this extremely unsafe "cleanupless" contract to begin with? I can't think of any other reason than performance, because syntax sugar can always take care of the rest.
that's a straw man example, its not the kind of API I'm talking about.
Let me repeat: this is for cases where the library developer has written code where it won't go out of bounds, and if it does it's a bug in their own library. Not the input. Rust APIs are not designed the exception-way where unexpected input is supposed to be caught like exceptions.
For example, I may allocate a buffer of size n, and index it willy nilly at various points I know to be less than n (perhaps, for example, values from another array which was constructed with values not exceeding n). If this doesn't work, it's a bug in your program and there no two ways about it. The assumptions you made about your own program when writing it were wrong. This can happen if you make a mistake when programming, or mess up a refactoring, or rely on a library that changes behavior (which itself shouldn't happen but the world isn't perfect).
Almost all of the indexing panics I've seen in Rust have been bugs in the code that was indexing; where continuing the program somehow would probably make things worse since the state is inconsistent (according to the assumptions the programmer made about their own code, not the input). People tend to use `.get()` when it's possible that the input may trigger an out of bounds error.
Its not a performance issue; the bounds check happens either way, please stop making it one. Its an ergonomics issue. Rust tries to offer both imperative and functional styles, and also tries to be palatable to people from C++. This leads to some C++-like defaults like the indexing syntax. If people had to unwrap/try on every index in rust half the community would probably revolt.
(note that indexing isn't that common; rust uses iterators and other safer, bounds-check-contained abstractions, but it was determined that when you want to use direct indexing in Rust you usually are designing your code such that the out of bounds is a bug)
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.
There are no current plans for a 2.0 at this time.
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.
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.
In general, we're very happy with monadic error handling and don't want exceptions.
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.