4 ms·
I recently wrote a few projects in Rust (C/C++/Go/JavaScript/Java/Python as background), and very much like the language. My 2 cents from my endeavors with Rust
by arghwhat 9y ago
I recently wrote a few projects in Rust (C/C++/Go/JavaScript/Java/Python as background), and very much like the language. My 2 cents from my endeavors with Rust
I felt like all type errors are backwards. That is, "got" was the target you are giving your type to, not the type that you are passing. This may only happen in some cases, but I just started tuning the content of those errors out and instead adjusted randomly until things worked or the message changed.
I was often getting obscure type errors that were not at all related to the issue, and sometimes the compiler just insisted that just one more burrow would do, no matter how many burrows you stack on. This is definitely because I did stupid things, but the compiler messages were only making matters worse.
String vs. str is a pain in the arse. My code was littered with .as_str() and .to_string(). I never had the right one.
Enums are super nice, but it's very annoying that you cannot just use the value as a type. My project had a lot of enums (user-provided query trees), and it was causing a lot of friction.
There are also many trivial tasks where you think "Of course there is an established ecosystem of libraries and frameworks for this", and end up proven wrong. I mostly did find one library for the thing I needed, but often immature. The HTTP server + DB game seems especially poor.
In the end, I had to quit the fun and get work done (and others did not find playing with new tools as fun as I did), so I ported the project to Go and got productive. I took a fraction of the time to write in Go, the libraries are just so much more mature, it performs significantly better than the Rust implementation (probably because of better libraries—definitely not stating that Go is faster than Rust here), compile takes 1 (one) second rather than minnutes, and there is in general just much less friction.
On the flipside, it takes about 2-3 times as much Go than Rust to do the same task, even if it was way easier to write the Go code. The code is also a lot uglier. As an especially bad case, something that was a serde macro call in the Rust version is 150 lines of manually walking maps in the Go version.
- Yoric 9y ago> Enums are super nice, but it's very annoying that you cannot just use the value as a type. My project had a lot of enums (user-provided query trees), and it was causing a lot of friction. Note that OCaml has this feature of using-a-enum-value-as-a-type (or using a subset of enum values as a type, etc.). It works very well, but it quickly produces impossibly complicated error messages. I'd like Rust to have this feature, eventually, but not before there is a good story for error messages.
- arghwhat 9y agoI am not very familiar with OCaml, but why would such a feature result in complicated error messages? I can only really imagine two new error scenarios: "Expected Enum::ValueA, got Enum" and "Expected Enum::ValueA, got Enum::ValueB".
- GolDDranks 9y agoI’m interested in this feature because of the ”incremental” static case analysis it enables. What’s the problem with the error messages?
- kibwen 9y ago> My code was littered with .as_str() and .to_string(). PSA: If you have a variable that's a String, you can easily pass it to anything that expects a &str just by taking a reference to it: fn i_take_a_str(x: &str) {} let i_am_a_string = "foo".to_string(); i_take_a_str(&i_am_a_string); Every variable of type &str is just a reference to a string whose memory lives somewhere else. In the case of string literals, that memory is in the static data section of your binary. In this case, that memory is just in the original allocation of your String.
- arghwhat 9y agoAh, I found that out later but had forgotten all about it. :) I don't remember how I found out, but it seemed oddly magical until I just now read the docs: String implements Deref<Target=str>. Makes more sense now. I still had a bunch of to_strings()'s, though, as things tended to take String whenever I had &str's. I found this to be a very unexpected nuisance. EDIT: Maybe I needed as_str() as the & trick doesn't work if the target type cannot be inferred as &str?
- steveklabnik 9y agoFWIW, this is why we go over this stuff in the book now; lots of people struggle with it, it's not just you. And yeah, Deref doesn't kick in everywhere, so you may need the .as_str() in those situations. It should be the extreme minority case, generally. Same with .to_string(), though moreso. Most stuff should take &str, not String.
- jonreem 9y agoIt's relatively rare that APIs should be taking ownership of `String`s from you; the majority of the time arguments should be borrowed. I'm curious what cases you ran into most frequently that required `String`.
- danieldk 9y agoas things tended to take String whenever I had &str's Functions should prefer &str or perhaps T where T: AsRef<str>. Note that if you write code that needs an owned String, you could consider taking some T where T: Into<String>, because this allows you to take many kinds of string types, such as &str, String, Box<str>, and Cow<'a, str>.
- katastic 9y ago>There are also many trivial tasks where you think "Of course there is an established ecosystem of libraries and frameworks for this", and end up proven wrong. I mostly did find one library for the thing I needed, but often immature. The HTTP server + DB game seems especially poor. That's my situation with D. It's a beautiful language but you have to rely on bindings for everything, and many times, people start a project... and never maintain it. So you'll find a great lib... and then realize it hasn't been updated since 2012 and wonder whether you should risk adding a possibly bug-ridden (and unsupported) project to your project.
- arghwhat 9y agoOh, and added thing that bugged me a lot: The error part of Result<T, E>. During my short time of coding Rust (I'll get back to it later), I never really found a way to ergonomically handle errors. I find it really awkward that the error is a concrete type, making it so that you must convert all errors to "rethrow". Go's error interface, and even exception inheritance seems to have lower friction than this.
- retrocat 9y agoI'm quite fond of error-chain (https://github.com/rust-lang-nursery/error-chain https://github.com/rust-lang-nursery/error-chain), which helps mitigate it somewhat. You can do things like: use error::{Error, ErrorKind, Result, ResultExt}; fn some_func(v: &str) -> Result<u32> { v.parse::<u32>().chain_err(|| ErrorKind::ParseIntError) } The purpose of `chain_err` here is to add on top of the previous error, to explain what you were trying to do, instead of passing up the previous error (in this case, `std::num::ParseIntError`). If you don't like that, you can do something like this: use std::boxed::Box; use std::error::Error; fn some_func(v: &str) -> Result<u32, Box<Error>> { v.parse::<u32>().map_err(|e| Box::new(e)) } But then you'd have to box every error.
- steveklabnik 9y ago? should help a lot with this, but libraries like error-chain, or the newer (and, IMO much better) failure can help even more.
- arghwhat 9y agoBut ? only worked if the function it was used in had the same error type as the function you called, which was mostly not the case... ... I think. I don't remember it that vividly.
- foldr 9y agoIt doesn't have to be the same type, there just has to be a suitable implementation of the 'From' trait to perform the conversion if the types don't match.