One thing I've found with rust is that you struggle struggle struggle trying to do a simple task, and then finally someone says "Oh, all you need to do is this".
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".
I don't know if that's quite true. While Rust is very deep, I've found that you can get quite far with a few crucial pieces of info (note that I don't speak about generics or macros; haven't worked a lot with them):
* 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.)
I think it is 100% the case that the best way to learn Rust is to be on the IRC channel (#rust or #rust-beginners, either is fine for beginners but the latter is unlogged and more focused if you prefer) and ask lots of questions and also lurk. I'm not sure if that's good in absolute terms for the language, but it's very helpful.
(Which probably means your first project should be something open-source so you can share your code easily)
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:
* 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.
4 chained function calls from config -> read a key from it is quite an ask. Though I realize there's reason for it. There are many cases I'd be happy to have panics occur for invariants.
config.strictString('foo') or something of that nature seems like it could be a more ergonomic choice in cases like thise.
I was under the impression that the (somewhat) verbose syntax for error handling and memory management via the type system was a necessary side effect of Rusts entire point of existence: a compiler-guaranteed safe systems language. Neither Python nor C force you in any way to pay attention to errors, making simple scripts much easier to write.
I guess I'm just surprised people think that Rust should be as simple to use as Python. Maybe I'm wrong.
I think the complaint is more that Rust has seemingly tried very hard to make error handling "simple". But in the process it has managed to invent a whole series of new idioms and special syntax that is alien to pretty much everyone. There's a thread in /r/rust about this same article where you can look and see people suggesting all sorts of ways to write this that are split into clear sedimentary layers depending on when the writer learned the language.
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.
There are many ways to have both enforced error handling AND less boilerplate. Java's checked exceptions are much maligned but would work very well here. Another way would be having more syntactic sugar for Result-style monadic error handling, like the do notation in Haskell or for..yield in Scala.
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.
In an abstract sense, it's possible to offer more functionality without requiring much more friction. This is the premise of languages with good type inference, relative to earlier versions of C++/Java.
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)
C does not force you to check errors, as everything that could fail must return an error code and you could simply forget to check it. Python checks the error for you, as anything that could fail throws an exception, and so you have to go out of your way to actively write code to manage to override that check (with a try/except) to ignore the error.
Being able to port a 20 line Python script to a 20 line Rust is the holy grail. Surely Rust has the ambition to one day achieve that, but it is by no means the main priority nor the original design goal of the language.
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 file_double code was, IMO, quoted completely out of context. If you go read the error handling section, that code is used as a motivation to the next section, where you get gradually introduced to more economical error handling.
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.
Too much kool-aid. Most programmers are not writing system code and they'd be much better served with languages like Go, Nim, and D. In fact, the example the author is trying to port over would have been much easier in Nim.
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.
Rust hasn't made significant incursions into math modelling or industrial processing. It's advantages are slim there. Embedded will fracture into network interfacing and realtime where user input is less hostile, more predictable and doesn't require extensive constraint.
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).
These struggles are real. I don't see a way around them other than just learning them (and then they go away, because you know what code won't work, and don't fight it).
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.
These struggles are indeed real, but at least as far as verbose error handling goes, remember that Rust is forcing you to handle a lot of things that are silently ignored in Python. Truly equivalent Python code would include a bunch of exception handling and checks for nil.
I don't know Rust, or the general direction of the community around it.
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.
The was an easy solution that he failed to use. His return type could have just been Result<MyConfiguration, Box<Error>> and he could've used try!/? freely
> Case in point: the example in the article from the rust documentation that converts errors to strings just to forward them
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:
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.
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.
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.
> 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.
A common theme I find in posts criticizing Rust is that their authors take a problem they've already solved in another language, try to blindly convert it in non-idiomatic Rust and then complain because things get awkward.
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.
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, but it gets the job done. As for the `let mut file` bit, that makes sense to me: a file in the standard library is an abstraction over a file descriptor in the operating system, and the descriptor has a file pointer which must be advanced when you read from it. I don't consider it internal state; the read operation will return new data every time, so it's not a pure function. It follows that in order to behave that way, it has to depend on some pretty explicit state.
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.
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:
Depending on where it crashed, the Python script would raise an exception. It would most likely be an `IoError`, `KeyError`, or `ValueError`. Then it would show an error message with a line number, column number, and traceback. Using a debugger would allow you to step backwards through the traceback to determine if the error was caused by something further up the line or where the exception was raised.
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 find result types to be much easier to understand and work with than exceptions. Result types can be handled by the type system, even when you have checked exceptions in java, there are still exceptions that aren't checked, and the syntax for the checking becomes monstrous.
We nearly had a hostile language fork over mandatory unwinding. It is not something that the language team decided and refuses to acknowledge. Rather, we had to respond to the demands of our embedded users.
Monads are a superior form of error handling than exceptions... The only problem is that AFAIK (but I'm still learning it) Rust is missing something equivalent to Haskell's do notation.
Exception safety is complicated. I didn't believe this, having only used exceptions in higher-level languages, but enough time talking to Rust folks and I get it now. And even in C#, I know I'm getting things wrong here and there with exceptions, but it doesn't have the same impact (safety/mem leaks) due to being GC'd and memory safe.
The author doesn't really justify why he needed to port the python script to rust in the first place.
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
> The author doesn't really justify why he needed to port the python script to rust in the first place.
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).
Before I convinced co-workers that we could use Rust in production (where we'd normally have used Python), I wrote a lot of this kind of script (where, I'd agree, a higher-level language like Python is a better choice) in Rust and Python so that they'd be able to have some idea of what the differences were and how Rust works. It seems like a great exercise for the very conservative or intimidated programmer to ease into more low-level programming.
I don't think it should be as easy and concise as python — it's systems programming language without GC. It is not designed to be used in place of all programming languages, just in place of C and C++.
I think Nim may be the most underdocumented project I've ever used. I spent a week or so with it, but quickly grew extremely frustrated as I was constantly scouring old forum posts to learn how to use the basic features of the language. That is not a tolerable situation for me.
Rust has stressed ergonomics of late, yet I sometimes struggle to read Rust code. Obviously, there is value in elegant code, but my question is, would anybody find value in an extremely simple language that could compete with the likes of c/c++? Does something like this exist?
It makes me sad to see the example. This is why I maintain `.unwrap()` is one of the worst things in rust.
...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. :/
> your 'safe' language then happily crashes and burns everytime something goes wrong
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.
> ... 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
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.
Yeah I found this odd too.
Rust docs say unwrap() shouldn't really be used, but through the rest of the documentation examples it's used everywhere.
I suppose it's to keep the documentation simple and focused
I agree, its not a great idea to use unwrap() in production code, it is bad even when its safe to unwrap.
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) ...
Unwrap doesn't make rust unsafe, it's not a segfault. You also don't have to use the verbose match statement when using options and results; you either propogate the error with ?/try or you provide a default value, or you panic (if the error should not be happening).
This is a staggeringly bad comparison, almost like comparing drawing a line in C# (couple lines) to trying the same thing in C++ (possibly 100+ lines).
Rust is important, it has a great chance being a first modern native system language (memory safety).
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 continues to puzzle me when people think of Rust's syntax as particularly exotic.
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).
The author was using this as an opportunity to learn Rust so while it might look sort of crazy to use a systems programming language for build failure notification there was a reason behind their decision. Nim is a lovely language but in the author's case he doesn't really care about speed for the use case so if they were being strictly pragmatic they could have just stuck with Python.
The author might consider Nim - https://nim-lang.org/. It is a statically-typed compiled language that about equals Rust in performance, but has a much cleaner higher-level Python-flavored syntax, and a very Pythonic parsecfg module in stdlib.
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.
If you're so concerned of imaginary internet points (a.k.a. hard won karma), I'd advise you to stop talking about that. Complaining about downvotes is one of the few reliable ways in HN to get further downvotes. (I mean, you could say "What? That's bollocks!" and stop caring about votes given by these mindless hordes. Or you could follow the social norm and happily gather sweet internet karma. It's up to you.)
kstenerud|8 years ago
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".
allengeorge|8 years ago
* 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".
burntsushi|8 years ago
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
[2] - http://blog.burntsushi.net/rust-error-handling/
cupcakestand|8 years ago
This was my feeling when I got into Ruby on Rails (years too late).
jpfr|8 years ago
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.
steveklabnik|8 years ago
onmobiletemp|8 years ago
Manishearth|8 years ago
geofft|8 years ago
(Which probably means your first project should be something open-source so you can share your code easily)
std_throwaway|8 years ago
erickt|8 years ago
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:
(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:
* Next you could get the config values out with some methods on Value: 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
eriknstr|8 years ago
GolDDranks|8 years ago
huntie|8 years ago
QuercusMax|8 years ago
bpicolo|8 years ago
MikkoFinell|8 years ago
dbattaglia|8 years ago
I guess I'm just surprised people think that Rust should be as simple to use as Python. Maybe I'm wrong.
ajross|8 years ago
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.
Sharlin|8 years ago
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.
rtpg|8 years ago
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)
saurik|8 years ago
gaius|8 years ago
When I need that, I use OCaml.
tinco|8 years ago
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.
burntsushi|8 years ago
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.
dkarapetyan|8 years ago
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.
plinkplonk|8 years ago
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"
chillingeffect|8 years ago
f1b37cc0|8 years ago
stable-point|8 years ago
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
pornel|8 years ago
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:
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.rvense|8 years ago
amelius|8 years ago
In an ideal language, you could decide to ignore low-level constraints and your code would work just fine, although perhaps less efficiently.
tyingq|8 years ago
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.
leshow|8 years ago
Inufu|8 years ago
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...
bascule|8 years ago
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:
https://github.com/brson/error-chain
Matthias247|8 years ago
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.
pornel|8 years ago
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.
kancer|8 years ago
Const-me|8 years ago
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.
msangi|8 years ago
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.
thegeomaster|8 years ago
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.
[1]: https://crates.io/crates/error-chain
JoshTriplett|8 years ago
cdunn2001|8 years ago
(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
dep_b|8 years ago
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.
scriptkiddy|8 years ago
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.
hasenj|8 years ago
doubleplusgood|8 years ago
cousin_it|8 years ago
jimktrains2|8 years ago
pcwalton|8 years ago
marcosdumay|8 years ago
grahn|8 years ago
"Generics may well be added at some point. We don't feel an urgency for them, although we understand some programmers do."[1]
[1] http://golang-jp.org/doc/faq#generics
MichaelGG|8 years ago
djhworld|8 years ago
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
burntsushi|8 years ago
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).
sidlls|8 years ago
omginternets|8 years ago
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.
ungzd|8 years ago
liveoneggs|8 years ago
mikebenfield|8 years ago
unknown|8 years ago
[deleted]
kibwen|8 years ago
gavanwoolery|8 years ago
kevin_thibedeau|8 years ago
shadowmint|8 years ago
...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. :/
jjnoakes|8 years ago
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.
zokier|8 years ago
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.
jayflux|8 years ago
ordu|8 years ago
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...
Ar-Curunir|8 years ago
bombless|8 years ago
[deleted]
ricardobeat|8 years ago
brobinson|8 years ago
alkonaut|8 years ago
vultour|8 years ago
unknown|8 years ago
[deleted]
AlphaWeaver|8 years ago
Dowwie|8 years ago
frik|8 years ago
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.
hsivonen|8 years ago
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).
dep_b|8 years ago
[deleted]
sctb|8 years ago
scriptkiddy|8 years ago
The example the author gave is not the actual script he was using to monitor Jenkins. It's a small portion of it.
Seriously, if someone hasn't used Python before, I can't just assume that they know how Python's exception handling works.
p0nce|8 years ago
vfclists|8 years ago
Symmetry|8 years ago
kibwen|8 years ago
libman|8 years ago
[deleted]
vfclists|8 years ago
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.
yongjik|8 years ago