Programming in Rust: the good, the bad, the ugly
hackernoon.com
hackernoon.com
Some things occasionally feel too verbose. For example, converting
between str and String [...] seems like something the compiler
could figure out for me. I’m sure there’s a good reason for why it
is the way it is
This in particular has a very good reason — converting from a `&str` to a `String` requires memory allocation. You don't want to start accidentally allocating memory!See also:
- Why is it discouraged to accept a reference to a String (&String), Vec (&Vec), or Box (&Box) as a function argument? (https://stackoverflow.com/q/40006219/155423)
Having to handle every Result from every function is good; it
means the programmer has to think about what’s happening with
every function call. Sometimes it feels tedious. [...] you still
need to explicitly define a case for every type of error that may
occur.
You are not required to create a case for every type of error (although I think it's good to). You can use a trait object (`Box<dyn Error>`) if you truly don't care about the type.See also:
- Recoverable Errors with Result (https://doc.rust-lang.org/stable/book/ch09-02-recoverable-er...)
I find myself frequently writing code similar
to option.as_ref().unwrap().borrow(), which feels icky.
I can't imagine a case where this particular set of methods is needed (and as a sibling comment mentions, you can often avoid `unwrap` via pattern matching), but there is the unstable `Option::deref`, which shortens this code: fn example(a: Option<String>) {
// Before
let b: Option<&str> = a.as_ref().map(|s| &**s);
// After
let b: Option<&str> = a.deref();
}
See also:- `Option::deref` (https://doc.rust-lang.org/std/option/enum.Option.html#method...)
Tooling: Rust takes backwards compatibility very seriously. If you're using multiple version of a crate in your dependency try, it's very likely because those two crate versions have breaking changes between them.
Language - You should very rarely need to unwrap because of pattern matching:
Stuff like this: if (a.is_some()) { let value = a.unwrap()...
Can be rewritten like this: if let Some(value) = a { ...
The simplest example of this is array subscripting. A sufficient bound on n will determine the variant of arr.get(n). The API acknowledges this common case by providing arr[n].
That's way better than my experience. I'd say it works correctly at most 10% of the time. Probably less.
However, in many cases, you probably just want a Box or something instead of a borrow within a struct. Do you have a specific example where you want a borrow?
I understand the guarantees, it’s just the syntactic overhead that annoys me.
Pity that won't last.
The biggest problem I had with Rust was that I found it very, very hard to go back and forth between Rust and C++. Rust forbids or discourages use of semicolons and parentheses in many places where C++ requires them, and it took days to restore the habit of putting them in. I finally had to stop experimenting with Rust because it was interfering with getting paid work done. People coding only Rust, or Rust and (say) Ruby, should be less affected.
At the time, it was quite hard to know where an & was required, and where it was forbidden. There wasn't much middle ground, suggesting maybe it would do better figuring that out for itself. But it is probably too late to change that. Rust left behind a huge number of C's legacy mistakes that C++ had to suck up, but it is increasingly limited by its own early choices.
Maybe it just comes with practice, but I don't find it difficult to switch syntax. I do a lot of Go, Rust, and JavaScript, and each has some peculiar syntax. I do recall it being a bit of a pain when starting out, so perhaps you're still relatively new to Rust?
As for borrows, I guess I haven't had that much of an issue with it. I keep the documentation open while writing code, so I can quickly reference something if I need to. However, usually a borrow is needed if the type isn't expected to implement Copy (i.e. isn't a value type), and that rule of thumb works pretty well. However, whenever I get it wrong, the compiler is there to help.
There are a few things that I wish were different in Rust, but I imagine those differ for each person.