
Programming in Rust: the good, the bad, the ugly - letswritecode
https://hackernoon.com/programming-in-rust-the-good-the-bad-the-ugly-d06f8d8b7738
======
shepmaster

        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](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...](https://doc.rust-
lang.org/stable/book/ch09-02-recoverable-errors-with-result.html))

    
    
        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...](https://doc.rust-
lang.org/std/option/enum.Option.html#method.deref))

~~~
millstone
Why should converting a &str to a String require memory allocation? It seems
like String could just reference the &str and use COW?

~~~
GolDDranks
That needs a garbage collector to work – the lifetime of String isn't limited,
but the lifetime &str's is. That means that the backing storage of String can
be deallocated under it's feet. There has been some discussion about allowing
Strings to use COW in case of &'static str which doesn't have this problem,
but I'm not sure whether that proposal has any momentum at the moment.

~~~
pjmlp
In C++ using COW for std::string was a common implementation approach, before
C++11 made it invalid.

------
swsieber
A couple of notes:

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 { ...

~~~
millstone
The gratuitous unwrapping comes from APIs like `Mutex::lock` and `write!`

~~~
edflsafoiewq
IME unwrapping usually comes from the variant of an enum being determined by
the ambient invariants in the program. For example, if you have a dependent
pair, (x,y), x determines the variant of y. Similar consideration for
switching between a array of options of pairs and a pair of arrays of options.

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].

------
IshKebab
> [RLS] suggestions seem to be right only about 75% of the time

That's way better than my experience. I'd say it works correctly at most 10%
of the time. Probably less.

------
andrenth
I wish there was a way to escape lifetime annotation propagation in Rust. What
I mean is, if you have a struct with an owned field that you refactor to a
borrow, you have to add <‘a> _everywhere_ that struct name appears in your
program.

~~~
beatgammit
I'm hopeful that the work on non-lexical lifetimes will extend to this in some
fashion, but honestly I'm not sure it's possible while making the same
guarantees since you need to make sure that the borrow expires before the data
becomes invalid.

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?

~~~
steveklabnik
At most, you’d have to propagate a <‘_>; the lifetime is part of the type and
showing it matters for the same reason showing any other type parameter
matters.

------
ncmncm
It sounds like Rust could be a good way to break one's bad habit of depending
too much on their IDE.

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.

~~~
beatgammit
The semicolon thing is just something you need to get used to. In just cases,
you can just write C-style with a statement per line if that makes more sense
to you. However, I've come to love the expression-oriented nature of Rust
because I can sometimes convince myself that I'm doing FP when I'm writing
Rust.

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.

~~~
ncmncm
You mistake me -- omitting parentheses and semicolons was very, very easy to
get used to. Too easy, in fact. It was very hard to get back to putting them
where they are required in C++. I was continually omitting them. (Maybe an IDE
would have helped.)

------
catpolice
A lot of the listed problems are basically verbose repeated patterns, which is
pretty much what macros are for.

------
slaymaker1907
Using Box/Rc for linked lists is generally a bad idea since it can easily lead
to a stack overflow due to the way destructors are called.

