Rust has already reached the point where it leaves the world behind. Only the people who have been there since the early days really understand it, and getting into rust gets harder and harder as time goes on.
Yes, there's some awesome documentation, and the error messaging has gotten a lot better. But the power and flexibility of Rust comes at the cost of it becoming harder and harder to figure out how all the thousands of little pieces are supposed to fit together. What's really needed is a kind of "cookbook" documentation that has things like "How to read a text file with proper error handling" and "what is the proper way to pass certain kinds of data around and why".
Right now there's a lot of "what" documentation going around, but little that discusses the "how to and why".
* All variables are expected to be sized. So, learn what's sized and what isn't.
* Understand traits and how they add functionality to types. Have a small dictionary of common ones (From, Into, Debug, etc.)
* Learn how to write blanket implementations for traits. This can make your code lighter-weight. You also learn to start looking for blanket implementations.
* Encapsulate ownership details when possible. I'm not sure about the best way to explain this, but...at a high level it means "structure your types so that you avoid sharing ownership".
Have you read the book's chapter on error handling?[1] It's being replaced in the second version of the book, but I still plan on maintaining it as a blog post[2]. Any advice you might have to add more of what you want would be helpful. (And I ask this because I tried to attack the "how to and why" angle, so I'm wondering if I got that wrong.)
[1] - https://doc.rust-lang.org/stable/book/error-handling.html
This was my feeling when I got into Ruby on Rails (years too late).
Wonder why the difference?
Have you tried functional programmng? Lisp? Ocaml? The complaints of newbie functional programmers are also nearly the same.
The "struggle" is necessary. If there is no struggle, there is no learning of fundamentally new approaches you are not yet comfortable with.
See it as part of the training regimen that lets people emerge as stronger programmers on the other side.
(Which probably means your first project should be something open-source so you can share your code easily)
My time never really allows me to be on IRC regularly. Point being that if you can't get on IRC, I don't think it's necessary.
And honestly, error handling was the most difficult thing to understand, until you start groking why sized types are required as return types from functions and that every function call up the stack requires its callees to compensate for all it's error possibilities.
error_chain! has made this all so much better.
Meaning that many C++ applications, even nowadays, are actually C compiled with C++ compiler (not even classes are used).
So the transition between skill levels is quite gradual.
For me it is hard to tell, because I know the language since the C++ARM days, which means it had a feature size similar to Object Pascal and actually smaller than Ada.
Back then compiler writers even though it was easier to implement Ada compilers than C++ ones.
So slowly we got used to the new features, being discussed in books or programming magazines.
Anyway, so some things that could make your script easier:
* for simple scripts I tend to use the `.expect` method if I plan on killing the program if there is an error. It's just like unwrap, but it will print out a custom error message. So you could write something like this to get a file:
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).* next, you don't have to create a struct for serde if you don't want to. serde_derive is definitely cool and magical, but it can be too magical for one off scripts. Instead you could use serde_jaon::Value [0], which is roughly equivalent to when python's json parser would produce. * next, serde_json has a function called from from_reader [1], which you can use to parse directly from a `Read` type. So combined with Value you would get:
let config: Value = serde::from_reader(file)
.expect("config has invalid json");
* Next you could get the config values out with some methods on Value: 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. foo().pop().expected("foo length >= 1");
and if it fails, the error is something like "panicked at 'expected foo length >= 1'".[1]https://gist.github.com/edmccard/8898dd397eec0ff3595c28ada52...
let config: HashMap<String, String> = serde_json::from_reader(file)
.expect("config has invalid json");
This means that you can just do let jenkins_server = config.get("jenkins_server")
.expect("jenkins_server key not in config");Ultimately I'm happy we just picked something and moved on, but still mildly annoys me whenever I write it. If only we found that perfect method name way back when...
I could see "required" or "require" as a better name. Or even just break the "positive names" rule and go with "notOptional".
What you're point out is that it's a large bar to ask a new comer to the language to do this because it requires a deeper understanding of the language to use.
Is it not appropriate to show that you can reduce the complexity of a program by using other features of the language?
It's not significantly different from reducing
x + x + x + x
To 4xBut yes, it does appear that the config library needs an extra wrapper that loads from files and returns configs (in a context) that does the common work for you.
I guess I'm just surprised people think that Rust should be as simple to use as Python. Maybe I'm wrong.
At this point the cognitive load required to read and understand Rust implementations of "typical" practical problems is rather higher than it is for C++. And it seems to be getting steadily worse from my perspective on the outside.
As someone that participated in that conversation, I think that's a pretty inaccurate characterization of it. It's not about when the writer learned the language, but rather, what problem you're trying to solve. If you'll allow me to summarize very briefly (perhaps at the expense of 100% accurary):
* Use unwrap/expect when you don't care.
* Use `try!`/`?` with Box<Error> in simple CLI applications.
* Use `try!`/`?` with a custom error type and From impls in libraries.
* Use combinators (e.g., map_err) when you need more explicit control.
You might imagine that you could use any number of these strategies depending on what you're trying to do, which might range from "a short script for personal use" to "production grade reliability."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.
That's been my criticism of Rust error handling. Rust's error handling system is very clever. It's logically sound. It manages to make functional programming and error handling play well together. But it's not user-friendly. For a while, it took far too much code to handle errors. So gimmicks were developed to make the necessary gyrations less verbose. These hide what's going on underneath. Thus the generations of error handling approaches.
Rust tried to avoid the complexity of exception handling, but ended up with something that's more complicated. Python programmers, who have a good exception system, notice this. 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.
With exceptions, if you're using some code that doesn't handle an error well, you can catch the problem at an outer level, get good information about the error, and recover. With error-value returns, after you've come through a few levels of function returns, you're usually down to "something went wrong". (Having written a web crawler, I've found this useful. A huge number of things can go wrong in HTTP, HTML parsing, SSL certificate handling, and the other manipulations needed to read a possibly-hostile web page. A crawler needs to catch all those and deal with them, deciding "try again now", "try again later", "log error and give up", or "try alternative access approach". This makes one appreciate a good exception mechanism.)
Exceptions have a bad reputation because Java and C++ implement them in ways that are inferior to Python's approach. There's no exception hierarchy. Knowing what exception something can raise is very important. Often, you don't.
Rust (and Go) are slowly backing into exception handling, as the panic/recover mechanisms acquire layers of gimmicks to make them more useful. Rust already has unwinding (destructors get run as a panic event moves outward), which is the hard part of exception handling. Thus, exceptions are more a religious issue than a technical issue.
Exceptions(and the runtime/memory costs they incur by pulling in RTTI)
ERRNO(on relevant *nix platforms)
Lifetimes tied to objects when things fail(this is a big one)
Plus any library-specific hackery(I've seen raw strings as errors before)
In contrast I've been writing Rust for ~1.5 years now and each library I've used is consistent and follows common patterns. Much like a lot of people don't grok functional until they understand the common patterns, so it is with Rust too.
I code in Rust and C++ every day, and am mostly equally experienced in both (maybe more Rust now, but this wasn't always the case). I disagree. Rust has some ergonomics issues that C++ does not, but the reverse is true too. Looking at rust from C++ you'll only see one and not the other, because you're used to the other.
(And as burntsushi said your characterization of the thread is inaccurate, people are suggesting things that are best for different use cases)
Rust hasn't really tried "hard" to make error handling simple. We have what we had during 1.0, and then we have the ? operator, which always existed as try!().
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].
No, ".unwrap" turns input errors into bugs, and there is no production code where this is more desirable than an exception.
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. This is also something that could be fixed without compromising other qualities of the Rust type system.
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.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 :/
Though there might be more friction at some points, I imagine the Rust developers are taking these examples as good benchmarks for improvements.
EDIT: This exercise is very similar to the frustration when starting to use Haskell.
A lot of "simple" things feel more difficult because of the functional purity. But then you discover more patterns or libraries that help to handle this.
Design patterns surely exist for Rust that have yet to be discovered, but will turn out to properly encapsulate a lot of the difficulty (when combined with language improvements)
Justin criticizes the file_double function, it being complex with nested maps and conditionals. All of this complexity is also in the Python code, just hidden away in abstractions, the library and the virtual machine. Rust, right now, is still very explicit and revealing of inherent complexities. This code is exactly why you should use Python and not Rust for this kind of little script. One day the Rust developers hope Rust will be comfortable enough for you to consider using Rust in this situation, but it won't be soon.
The point gets softened a little by the remark that it probably would not be a picnic in C either, but I don't think even that is true. C still allows you to be very expressive, it would not encourage using those maps or even half of those conditionals. Rust is just that more explicit about complexity.
That said I honestly believe Rust is the best thing that has happened to programming languages in general in 20 years. Rust is rocking the socks off all the non-web, non-sysadmin fields, soon its community will make good implementations of almost every hard problem in software and Rust will be absolutely everywhere.
The error handling chapter is... Really big. Because it tries to explain everything from first principles. But it does include explanation and examples for using easy error handling as well, whose syntactic noise and level of boiler plate come quite close to Python.
The actual question is then about the author learning a new paradigm and way of expressing system code. If that is the case these are just pains he has to go through because Rust will never be like Python for quick and dirty scripts, nor should it be.
This is very unlikely. I can't see when Rust would solve the problems Julia (for example) does. And vice versa of course.
Nothing wrong in a language tackling a few domains really really well and not trying to solve "every hard problem in software"
I'd like to contribute to many of these packages, but have little interest in learning the languages. Rust could provide a good replacement (and one which is more productive for people used to higher-level languages). There hasn't been much uptake so far, but I'm hopeful.
What do you mean about industrial processing? PLCs?
> 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.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).
It's probably because Rust looks and operates mostly like a high-level language, but still satisfies low-level constraints.
e.g. the confusing difference between `&str` and `String` is equivalent of C's `const char * str = ""` vs `char * String = malloc()`.
In C if you had a code that does:
char *str = foo();
free(str);
you'd know that in `foo()` you can't return `"error"`, since an attempt to free it would crash the program. And the other way, if the caller did not free it, you'd know you can't have a dynamic string, because it would be leaked. In Rust you don't see the `free()`, so the distinction between non-freed `&str` and freed `String` may seem arbitrary.1) the file does not exist
2) the file is not readable
3) the file cannot be parsed
Rust forces you to say you want to panic in these cases (by using unwrap), but beyond that, the behavior is similar.
Again, I'm not a Rust developer, but it's not hard to imagine an abstraction (or even a transpiler) that makes it easy to read a file, parse it as JSON, and do something with the data.
In an ideal language, you could decide to ignore low-level constraints and your code would work just fine, although perhaps less efficiently.
Is there some chance, that over time, most popular functionality will end up in well architected crates that abstract away some of these complaints?
A bad example, perhaps, because they probably go too far with it, but a lot of java's verbosity fades away because there's a rich ecosystem of libraries that already know how to do what you're trying to do. There is, of course, a downside to that...important implementation details become opaque to the users of these libraries.
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...
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...
This section:
- Shows you how to define your own Result types. They have chosen a String as an example of what you could use as an error type. In practice nobody uses "String" as an error type.
- Concludes by defining a custom error type to use instead of a String. I guess you didn't read that far? In practice nobody "converts errors to strings just to forward them". String was just an example they were using as they built up to defining a custom error type.
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:
Personally I found the error handling section of the documentation confusing and frustrating -- it works through three or four different approaches pointing out issues with them as it goes, and it's hard to tell when it's discussing a simple-but-wrong approach as motivation for the following more-complex-but-correct one, and when it's actually recommending you use the approach. Plus it finishes with an approach with nice properties but an awful lot of boiler plate conversion code, which left me thinking 'surely there must be a better way'. IMHO the error handling section of the rust docs should describe just one way to do things, and it should be the standard way everything uses so your code interoperates with library errors nicely, and that way should not require writing a page of boilerplate just to say 'my function might return an error from library foo or one from library bar or this error of its own'. (If error-chain is that one right way then it should be in the standard library and the documentation.) As it is it looks like 'this language isn't finished yet, come back in six months to see if it's any better' :-(
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.
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.
But you can do that in Rust, just use Box<Error>. No library does that though, because it is not a zero-cost abstraction.
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.
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.
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.
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.
I think that it's important to pick the right tool for the job and to follow the patterns of the tool you're using.
Is Rust the right tool for the task described in the post? Probably not, but it could still be used albeit it will always require more work than Python.
What's really missing is a resource showing common problems and their idiomatic solutions.
As the other comment said, Rust needs to make some trade-offs, because you simply can't have an expressive and easy-to-use language that runs so close to the metal and is aimed at being C++-level fast. As such, Rust will never be as easy to write as Python, and for scripts like the author mentioned, I'd say that Python is a much better choice than Rust.
Rust is, by design, a systems programming language and it does have complexities and gotchas that arise from the need to have a lot of control of what actually happens at the machine code level. If we had a Sufficiently Smart Compiler(tm), of course, you wouldn't have to worry yourself about those low-level details and just write what your program needs to do and nothing more. However, in the absence of such an ideal, we must accept that a high-level abstraction must always leak in some way in order to let us control its operation more closely to get the performance we need. In my opinion, it's much better that necessary abstraction leakage is made a deliberate part of the API/language and carefully designed to minimize programmer error, and Rust, I think, does a good job of doing exactly that.
That's not to say that the language cannot be made more ergonomic. For one, I think that rules for lifetime elision are a bit too conservative and that the compiler can be made smart enough to deduce more than it currently does. I'm also excited about the ergonomics initiative, and I hope that the core team will deliver on their promises. In general, as someone who's written more lines in C/C++ in my life than any other language, I'm very excited about the language as a whole, as I think it provides the missing link between those languages that are expressive, high-level, and reasonably safe but slow, and those that are fast, low-level, a bit terse, and allow one to shoot oneself in the foot easily.
(Scroll to the examples.)
An exception would be thrown on error. That exception could be trapped in a simple try/catch block.
Nim is very similar to Python -- but statically typed and compiled (quickly) to machine code. There are many situations where Python is a better choice than Nim, but if you're looking to translate Python code for speed and type-safety, Nim is worth considering.
And if you want to translate Python to Nim gradually, look at this magic:
For calling Nim from Python: * https://github.com/jboy/nim-pymod
For calling Python from Nim: * https://github.com/nim-lang/python/tree/master/examples
My experience with Swift vs Objective-C is that clean Swift is crash free but more verbose when all other things are equal.
If you don't need that level of security because it's just a small script Python was the right choice.
All of Python's exceptions are an instance of `Exception`. In order to catch and handle any exception that can be raised, you can use a `try, except` block with the base `Exception` class. This, however, is bad practice as there may be some exceptions you want to ignore and, generally, you also want to print a different error message depending on which exception was raised.
I can't edit my reply anymore but the question was actually meant to be rhetorical rather than I really wanted an answer.
The compiler will warn you if you if you haven't used a result type, and it's up to the programmer to decide what to do in the case of an error, just like when handling an exception.
If you were going to use the JSON result for something, then you are forced to check if the result was Ok or Error. The only time you'd get a crash is if you just called .unwrap(), and even then you'll also get a stack trace.
I don't see how the Rust approach would avoid this fate but I doubt it will ever be used in these contexts to begin with.
Anyway for me exceptions are way easier to work with, than return codes.
Rust has essentially specialized do-notation for error handling called the `try!` macro, or more recently, `?`. (`try!` and `?` are exactly equivalent in today's Rust.) Actually, it does just a bit more than standard do-notation would: it also tries to convert your error value at the call site to the error type expected by the return type of the current function.
The problems posed in the OP are pedagogical ones IMO that I hope can be solved. I think the current resource on error handling in the book is good for folks who really want to dive in and figure out the complete story, but it's bad for folks who just want to write code that works without spending a couple hours doing a deep dive. So I think there's room for more targeted pedagogy here.
No, they're not, not by a long shot. As soon as you start having multiple monads returned by functions and you need to combine them, you need to introduce monad transformers and your code turns into a giant untractactable spaghetti mess.
Exceptions have issues but they are the sanest way to handle errors today.
"Generics may well be added at some point. We don't feel an urgency for them, although we understand some programmers do."[1]
Pulling down some JSON, doing a bit of transformation and sending alerts seems like a perfect candidate for a high level language, I don't see any reason why you would port it to Rust unless you had significant performance concerns
And they don't need to. When I first learned Rust, I tried to write a `filter` function. Why would I ever do that? I could write `filter` much easier in Python, or heck, just use the `filter` method on iterators that is already in the standard library. I did it because I saw it as an opportunity to learn. I wanted to connect something I knew (`filter`) with something I didn't know (Rust).
A good candidate for trying to learn Rust is to find a script or tool that currently has the problems that Rust claims to fix.
To learn Rust in a well-controlled environment?
Isn't that the usual/sane way to learn a new language? Take something trivial you understand well from language A and port it to new language B?
Note, of course, that this doesn't imply the result should be put into production.
Here are some links you may find useful if you decide to try again:
Documentation index: https://nim-lang.org/docs/theindex.html (everything)
Official Tutorial: https://nim-lang.org/docs/tut1.html
Standard library documentation: https://nim-lang.org/docs/lib.html
Language Manual: https://nim-lang.org/docs/manual.html (information on syntax, type system, GC, etc.)
Compiler user guide: https://nim-lang.org/docs/nimc.html
Backend docs: https://nim-lang.org/docs/backends.html (nim cmpiles to C, C++ and js)
Built in templating docs: https://nim-lang.org/docs/filters.html
How to tune the GC: https://nim-lang.org/docs/gc.html
Development tools docs: https://nim-lang.org/docs/tools.html
More about the compiler: https://nim-lang.org/docs/intern.html
All of these links come from nim-lang.org's documentation page here: https://nim-lang.org/documentation.html
...because people use it; and then say; 'but don't use unwrap...'; and then use it, and your 'safe' language then happily crashes and burns everytime something goes wrong.
Blogs and documentation are particularly prone to it.
Result and option types are good; but if you're gonna have unwrap, you basically have to have exceptions as well (or some kind of panic recovery), because, people prefer to use it than use the verbose match statement. :/
I'm not sure why you put 'safe' in quotes here; nothing about 'unwrap()' (or even 'panic') is unsafe in the context of Rust. In fact, it acts just like Python would in the same circumstances: print a developer-centric message out and exit with a bad return code.
What's unsafe about that?
> if you're gonna have unwrap, you basically have to have exceptions as well
Why do you think that? unwrap() is meant to be the same as throwing an uncatchable exception; if you want to throw an exception that you mean to catch somewhere, you should be using something else.
> people prefer to use [unwrap()] than use the verbose match statement
People may not be aware (which will come with time) but there are more than just those two choices when it comes to error handling in Rust.
Unwrap is a shortcut to let you be lazy; it exists for no other reason, and it causes application level crashes in way that is very much easier to avoid in other languages.
That 'catch_unwind' exists is evidence that some kind of panic recovery is necessary... and I wonder how often you hit it from a real panic, vs. a stray lazy unwrap?
Whats your justification for unwrap? I've never seen a meaningful justification for it other than not wanting to handle errors properly.
An application error (returned null) shouldn't abort your application with a hard error, no logs. Its just plain poor practice to use unwrap().
It might crash, but it doesn't burn, which is kinda the point of panic. Its behavior is well defined and predictable, which is great improvement over typical C UB.
But speaking about blogs... Why not to use .unwrap() there? Its simple, and allows to show some ideas without digging into error handling, just point to places where those handling should be placed.
> but if you're gonna have unwrap, you basically have to have exceptions as well (or some kind of panic recovery) ...
https://doc.rust-lang.org/1.9.0/std/panic/fn.catch_unwind.ht...
require "yaml"
config = YAML.parse(File.open("test.yaml"))
[1] http://crystal-lang.org fn gimme_config(some_filename: &str) -> MyConfiguration
{
toml::from_file(some_filename).unwrap()
}Though, I wish it has less exotic syntax. It's like C++ and Erlang had a baby. Look at modern languages with nice syntax like Go, Julia, Swift and compare it to Rust. Someone coming from C, C++, C#, Java, PHP and JavaScript has to learn a lot of new syntax twists that look uncommon and different for little reason. Sure some overly complex early syntax ideas like different ones for different pointer types vanished in newer Rust releases. Now it's probably too late to improve the syntax.
It's much closer to C/Java/JavaScript than e.g. Python or Bash are. Rust still has curly braces for blocks, uses ampersand and asterisk in ways that aren't too far from C, uses dot in a way that's not too far from C or Java and uses less-than and greater-than to denote generics like C++ and Java.
Personally, what keeps tripping me up when moving between languages is either forgetting to put parentheses around "if" conditions in non-Rust languages after writing Rust or having the Rust compiler complain to me about unnecessary parentheses after writing non-Rust code.
But if new languages couldn't do things like omit unnecessary parentheses around the "if" condition or improve readability by moving the return type to come after the function name, that would seem like too big of a restriction on trying to make some syntactic progress.
Edit: Plus it makes sense to have types after the variable name when they are optional in most cases (and then to have them in the same order in function signatures for consistency).
Since you brought this up, the one thing that really (but irrationally!) winds me up about Rust's syntax is that variables are:
let foo: Bar = ... ;
And functions are: fn foo() -> Bar { ... }
Why not use a colon for the return type of a function as well?Not that I am suggesting that you are the "hater" here.
Who are these mean Rustaceans?
Is Nim considered such a threat to Rust?
PS. To my earlier downvoters can I have my hard won karma back, please??? This is the response I have been advised to proffer after consulting on the Nim forum, after my earlier terse comment.