
Rust things I miss in C - heinrich5991
https://people.gnome.org/~federico/blog/rust-things-i-miss-in-c.html
======
chucksmash
Tangential, but if you're looking for a project to do as you learn Rust,
there's an ongoing Operating Systems class being taught at Stanford in Rust
right now[1]. The "Ferris Wheel" portion of assignment #1 was particularly
useful for language knowledge; 25 source files which you must make compile/not
compile/pass tests (along the lines of the Ruby/Kotlin Koans), along with a
test harness you can run locally.

[1]:
[https://web.stanford.edu/class/cs140e/syllabus/](https://web.stanford.edu/class/cs140e/syllabus/)

~~~
eduren
I'm definitely interested in giving it a try. Do you know if the Pi kit they
are using is sold as a complete package somewhere? I found a list of
components in the Assignment 0 description, but that's it.

~~~
chucksmash
Bought a Pi 3 starter kit from a local place near me. It came with:

\- Raspberry Pi 3

\- 1⁄2-sized breadboard

\- 4GiB microSD card

\- microSD card USB adapter

\- CP2102 USB TTL adapter w/4 jumper cables

\- 10 female-male DuPont jumper cables

And I had to buy:

\- 10 multicolored LEDs

\- 4 100 ohm resistors

\- 4 1k ohm resistors

\- 10 male-male DuPont jumper cables

Specifically this kit: [http://tinkersphere.com/raspberry-pi-orange-pi-
boards/1538-r...](http://tinkersphere.com/raspberry-pi-orange-pi-
boards/1538-raspberry-pi-3-starter-kit-raspberry-pi-included.html)

------
phkahler
One thing jumped out for me. No integer overflow. In my embedded work we use
that all the time. We represent angles as a int16_t or uint16_t where you can
add and subtract angles and never have to worry about a discontinuity at 2pi
because 2pi=65536.

Is this a hack that should not be allowed? I don't think so. Every processor
made (that I'm aware of) in the last 30+ years has used 2's complement
arithmetic. I don't think overflow is a bad thing, I think C made a mistake in
calling it "undefined". They had to because when the language came along there
were other options fresh in peoples minds.

I've also worked on systems in asm that had saturating arithmetic, and that
has it's own nice use cases. My preference is still to have the rollover.
People can implement bounds checking when they need to.

~~~
masklinn
> One thing jumped out for me. No integer overflow.

FWIW that's not quite true, Rust will panic on overflow in debug but can (and
currently does on all platforms I think) allow them in release. They're not UB
though, they're defined as 2's complement.

> In my embedded work we use that all the time. We represent angles as a
> int16_t or uint16_t where you can add and subtract angles and never have to
> worry about a discontinuity at 2pi because 2pi=65536.

> Is this a hack that should not be allowed? I don't think so.

Rust provides APIs to make these behaviours explicit decisions rather than
implicit hacks: [https://doc.rust-
lang.org/core/num/struct.Wrapping.html](https://doc.rust-
lang.org/core/num/struct.Wrapping.html), [https://doc.rust-
lang.org/std/?search=wrapping_](https://doc.rust-
lang.org/std/?search=wrapping_), [https://doc.rust-
lang.org/std/?search=saturating_](https://doc.rust-
lang.org/std/?search=saturating_) (and [https://doc.rust-
lang.org/std/?search=checked_](https://doc.rust-
lang.org/std/?search=checked_)).

> People can implement bounds checking when they need to.

Being unsafe by default always ends up in tears. Checking Mitre, just this
year there have already been 11 public CVE for integer overflows.

~~~
phkahler
After reading responses and reflecting a bit I guess rust got it mostly right.
Undefined behavior is bad, so Rust defined it. Overflow is apparently a
problem worth checking for. I don't understand how people are overflowing 32
and 64 bit integers, but OK it's common enough. Having the ability to declare
overflow/wraparound as being OK is a good thing so long as it compiles down to
the same add or subtract instruction and doesn't add overhead. Rust claims to
have zero overhead abstractions, so I'll have to try this some day.

TL;DR the decisions made in the design of Rust seem reasonable.

~~~
steveklabnik
[https://play.rust-
lang.org/?gist=5b5df368283645fbec18c4fbaeb...](https://play.rust-
lang.org/?gist=5b5df368283645fbec18c4fbaeb3c960&version=stable)

Click "release" and then "asm", you'll see that they are compiled to the exact
same thing.

------
adwhit
For more detail into the porting effort, I recommend taking a look at these
slides, taken from a talk the author gave last year:

[https://people.gnome.org/~federico/blog/docs/fmq-porting-
c-t...](https://people.gnome.org/~federico/blog/docs/fmq-porting-c-to-
rust.pdf)

 _" Rust revived my interest in maintainership. It is very empowering to
finally have a good language that I can use at the level of the stack where I
work."_

------
dcow
> Rust generates documentation from comments in Markdown syntax. Code in the
> docs gets run as tests. You can illustrate how a function is used and test
> it at the same time:
    
    
      /// Multiples the specified number by two
      ///
      /// ```
      /// assert_eq!(multiply_by_two(5), 10);
      /// ```
      fn multiply_by_two(x: i32) -> i32 {
          x * 2
      }
    

Okay that's pretty cool.

~~~
ndh2
Python has that as well:
[https://docs.python.org/3/library/doctest.html](https://docs.python.org/3/library/doctest.html)

------
v_lisivka
I'm working on library to bring some Rust features back into C, like
semiautomatic memory management (defer), Vec, Slice, String, Str, Option,
built-in test cases, generics. But I lost interest because I'm allowed to use
Rust in embedded project now. :-)

[https://github.com/vlisivka/crust](https://github.com/vlisivka/crust)

~~~
sitkack
Thank God!

------
hzhou321
It might be off topic but I just had this thought and like to write down…

If you treat your program (and your problem) as math, then it is necessary to
explicitly define all its boundaries. However many (most) problems in real
life are not strictly math problems due to their nature of ambiguity. The
ambiguity has its physical source of unknown boundaries. For example, you may
think you only need 32-bit integer and you may be sure of it today, but your
assessment may change in the future. Similarly, you may think today you should
use arbitrary precision and the constant boundary check does not matter, but
it may matter some day. In the effort to eliminate undefined behavior, we are
projecting our ideas from real world onto math realm. That projection may be
quite lossy.

I am aware that the actual compiled program is always well defined and can be
a perfect mathematical object, but it's source as a language do have ambiguity
-- e.g. compiler implementations and undefined behavior. Ambiguous language
sometimes can express ideas better than a mathematical language -- if we treat
the legacy of our code more in its source rather than in its binary.

------
bluejekyll
TIL: How about this instead, with pattern matching on function arguments.

I didn’t know you could do that. I might need to go back and see what I can
simplify with that trick.

~~~
dilatedmind
I just installed rustc to test this example, turns out you cannot do pattern
matching on function arguments like you can in erlang.

note: `my_func` must be defined only once in the value namespace of this
module

~~~
piinbinary
It might be more accurate to call what Rust can do to function arguments
"destructuring" rather than "pattern matching"

~~~
steveklabnik
Destructuring is a feature of pattern matching, so yeah, this is slightly more
specific. I don't think "pattern matching" is incorrect, though.

------
andrepd
I would just like to point out that many of these features are also features
of C++ :)(RAII, Templates, etc.).

~~~
tdbgamer
C++ does have a lot of the features that Rust has. You won't get any of the
memory safety guarantees or compile time checks though. I will say that to me
a lot of the C++ equivalents to Rust features seem very awkward to use (maybe
not once you're used to C++ I guess).

This guy does a pretty good presentation on Polymorphism in Rust vs C++:
[https://www.youtube.com/watch?v=VSlBhAOLtFA](https://www.youtube.com/watch?v=VSlBhAOLtFA)

~~~
pjmlp
The major issue with C++, is what also helped the language gain market
adoption, copy-paste compatibility with most of C89 code.

So security conscious C++ developers tend to use _enum class_ , string, vector
and array classes, RAII, iostreams, wrapping data access in classes with
invariants,...

Developers with more C oriented mindset, tend to just code away like "C with
C++ compiler" programming style.

Hence the need being discussing since the last three CppCon, to stop using C
style programming in modern C++.

CppCon 2015: Kate Gregory “Stop Teaching C" \-
[https://www.youtube.com/watch?v=YnWhqhNdYyk](https://www.youtube.com/watch?v=YnWhqhNdYyk)

CppCon 2015: Bjarne Stroustrup “Writing Good C++14” -
[https://www.youtube.com/watch?v=1OEu9C51K2A](https://www.youtube.com/watch?v=1OEu9C51K2A)

CppCon 2017: Bjarne Stroustrup “Learning and Teaching Modern C++” -
[https://www.youtube.com/watch?v=fX2W3nNjJIo&t=2s](https://www.youtube.com/watch?v=fX2W3nNjJIo&t=2s)

Of course, preventing copy-paste compatibility with C, already solves many of
those issues from the get go.

~~~
jacquesm
> Developers with more C oriented mindset, tend to just code away like "C with
> C++ compiler" programming style.

The way I've seen it described is 'C with more convenient comments'.

~~~
makapuf
You mean C99 ? What I really miss from C , with some of the things of this
article, is constexpr, compile time checked enums, ...

------
vatotemking
My favorite thing in rust is the error handling and propagation.

Using JS for comparison.

Example, this code in JS

function decode(path){

    
    
        try {
    
            return parse(path);
    
        } catch (err){
    
            throw err; // rethrow
    
        }
    

}

Could be compacted into this using try! macro or "?".

pub fn decode(path: &File) -> Result<Image>{

    
    
        try!(parse(path))
    

}

For error propagation, I could easily consume the error of a third-party lib,
and convert it into my own using the From trait:

/// Convert std::io::Error to RasterError::Io

impl From<IoError> for RasterError {

    
    
        fn from(err: IoError) -> RasterError {
    
            RasterError::Io(err)
    
        }
    

}

Where as in JS, I would have to write a lot of if else if I want to convert a
third-party package's error into my own error.

if( error.name === "IoError" ){

    
    
        let err = new Error(error);
    
        err.name = "MyError";
    
        return err;
    

} else if (...)

    
    
        ...
    
    }

~~~
steveklabnik
Don't forget that try! turned into ? so that's now

    
    
      parse(path)?
    
    !

~~~
vatotemking
Yes but I still prefer try! as its easier to see (personal preference)

------
ndh2
> _Some good experiences with C_

> _Reading the POV-Ray code source code for the first time and learning how to
> do object orientation and inheritance in C._

But [https://github.com/POV-Ray/povray](https://github.com/POV-Ray/povray) is
all C++... It even says "Written in C++" on the Wikipedia page. Was he talking
about some other source, maybe some older version?

Edit: [1] Here is a discussion about this topic from 2012. It seems that POV-
Ray was ported to C++ for version 3.7.

[1]
[http://news.povray.org/povray.programming/thread/%3Cweb.4f01...](http://news.povray.org/povray.programming/thread/%3Cweb.4f01ba3c6f2bacd09be4b6d10%40news.povray.org%3E/)

------
Lev1a
Concerning the "Pattern matching" section, I've never, in all my time
programming Rust, seen that specific pattern of destructuring the input values
of the function in the fn declaration part.

Now I feel the need to use that everywhere...

~~~
steveklabnik
A lot of people didn't know! I made sure to put it in the book explicitly:
[https://doc.rust-lang.org/book/second-edition/ch18-01-all-
th...](https://doc.rust-lang.org/book/second-edition/ch18-01-all-the-places-
for-patterns.html#function-parameters)

~~~
Lev1a
Huh, was that also in the first edition?

I learned using that and if this pattern matching pattern (heh) is indeed in
there, I must have overread it...

~~~
steveklabnik
I’m not sure, to be honest, it’s been a long time!

------
gameswithgo
I tried to do a small project using Rust and SDL2 and gave up due to having to
deal with lifetimes on textures.

> you just implement the [Drop] trait and that's basically it.

Is that what I was missing? could I have done that and not had to deal with
lifetimes?

~~~
thibran
You always have to "deal" with lifetimes. The rules are not that complicated
once you understand them. It boils down to:

* An object can only have one owner.

* When giving a reference away from an object, the reference has a lifetime that makes sure the reference doesn't outlive the owner.

* You can either have one mutable reference >>or<< arbitrary read-only references.

* An owned object must be denoted mutable to be mutable, otherwise it is read-only.

~~~
grok2
Some comments with the caveat that I have used Rust only for a short learning
exercise a while ago and I don't fully understand programming in Rust (and
found it tough going), but I think the problem is not about being able to
understand the ownership models and lifetimes (which seems fairly
straightforward conceptually).

The problem is in being able to put together a program that the compiler does
not complain about. It always seems like you have to "feel" your way around by
trying different things like a jigsaw puzzle rather than having a "canonical"
way to do things that fit the lifetime/ownership model. Maybe there are more
canonical ways based on people's experiences nowadays and maybe this problem
is due to approaching solving things using a C (or other language) mind-set.
It seems like learning these ways to avoid the Rust compilers errors related
to lifetimes in any complex program takes longer than in other languages.

~~~
thibran
Rust is for sure not an easy language (but compared with Haskell also not that
hard/time consuming to learn). The major stumbling block seems to me is, that
the syntax looks some kind of familiar, which tricks you into believing that
knowledge-of-other-language applies, but in reality Rust is a completely
different beast.

For me there was a gap between reading the Rust book and applying the
knowledge. While reading I thought "that's easy" but later on "oh shit, the
compiler complains all the time". If you make it past that point – which takes
for most non-novice programmers around three weeks – then Rust becomes a very
powerful tool to you.

~~~
steveklabnik
If you have any thought or insight on how to get over that, please let me
know. The docs team wants to work on "what comes after the book" next, so any
insights into what made it work for you would be very helpful!

~~~
thibran
> If you have any thought or insight on how to get over that...

It depends with which concepts someone is already familiar. To me the only
really new concept was the lifetime and ownership one, all others have just a
different syntax plus here and there some extra goodies. As example, when I
saw the match keyword I thought "cool, Rust has a keyword for guards".

I learned Rust shortly after 1.0 was released. The real obstacle for me to
overcome was to build a mental concept of what Rust is. Something that wasn't
really communicated while reading the first pre-release Rust book.

You have to read a lot to get an idea of Rust, it should be the other way
around – a common mistake done by almost everyone teaching something. Ideally
the first chapter would be a buzzword free, Rust code free chapter about how
Rust works superficially and why there is a need for yet another computer
language. It should describe what kind of tools and concepts in Rust exist and
which problems they solve. This is better than introducing somewhat
complicated concepts/tools and trying to explain how they fit into the big
picture at the same time.

Ideally there would be an introduction like this:

A program needs a structure, variables, control flow, tools... blablabla...
this leads to some common problems [some easy examples] and [this] are some
concepts Rust invented/uses to solve them.

In later chapters the abstract ideas would be "converted into Rust". Also
don't go too much into detail. I don't think for example that a representation
of the memory layout of a vector helps much. At first I just need to know
there is a thing where I can put objects of the same type into, how it is
implemented is not that important for a beginner. It's better IMHO to know how
to do something right, than why something is right. This reduces the mental
burden in the beginning of the learning phase. The why is something that you
learn over time – or maybe you are not that interested and skip it forever,
also a valid thing to do.

Overall the Rust community does a stellar job providing so much help in so
many different ways – /r/rust on Reddit is really awesome – thanks for the
book and your open mind to ask for feedback.

~~~
steveklabnik
Thank you! Very helpful.

------
jjgreen
I found the first point on unit-testing of C odd, I always treat static
functions as implementation details and test only the public interface. CUnit
is nice enough (if a bit long in the tooth).

~~~
woogley
There's a culture of testers who believe 100% code coverage is important
enough to include "private" implementations, and will happily break
encapsulation rules for sake of hitting 100% coverage.

~~~
zbentley
Eh, I think both extremes are sub-optimal.

Some small public APIs are backed by large enough implementations that it pays
off to be able to test implementation details. Sure, it might be "poorly
factored" code that should have a bigger API and smaller guts, but that's not
always an something you can change. Also, writing tests for internal behavior
before refactoring can give you a good blueprint for how the refactored code
should behave--being able to read the tests to specify unclear behavior is,
while far from enjoyable in some cases, better than nothing.

You're right that there are some pretty silly test suites that break
encapsulation for a coverage number without actually testing anything useful,
though.

I don't think an absolute "all tests must behave thus" rule (e.g. coverage
requirements, "only test public functionality", "refactor the _instant_
something isn't easily testable") is useful. Explain the benefits of each
path, and make sure the decision of what compromise to make--and in any
project more than a one-developer hobby, you _will_ have to compromise here
eventually--is in the hands of people with the experience and common sense to
make the right one.

------
kzrdude
I think that C would have lasted us much longer if it just had C++-style
templates. No classes, exceptions or other C++ features needed.

~~~
Retra
Can't macros already do everything templates can? I mean, it would be nicer to
have templates, but I don't see why that would be a bottleneck for anything.
Someone could probably write a templating macro and you could use that to the
same effect.

~~~
kzrdude
I've seen that many times, written a few of them, and it's pretty horrible.
Real templates are needed for such basics as generic data structures.

------
peternicky
Enjoyed this article so much.

------
sureaboutthis
It seems to me he, first, talks about all the wonderful code he has seen
written in C, then complains about C when he runs into terribly written C code
but wants to blame it on C. I have a problem with that.

He then concludes that he likes things about Rust but then states Rust is a
lot harder to learn but you become a better programmer cause you have to work
harder on it. But isn't that what he complains about with C?

~~~
lmm
> He then concludes that he likes things about Rust but then states Rust is a
> lot harder to learn but you become a better programmer cause you have to
> work harder on it. But isn't that what he complains about with C?

The difference is that when you haven't learned/aren't good enough in C,
you'll make a codebase that sort-of works for now but is buggy and
unmaintainable - code that reads uninitialized values or never free()s or
works as an application but could never be turned into a library - whereas in
Rust your code won't compile until you get good enough to write it properly.

~~~
sureaboutthis
That is a fault of the programmer, not C. Sure, having those things in rust is
nice but, my point about the above line is, he's talking out of both sides of
his mouth (with all due respect).

I don't want to come across as dissing rust. I've only tinkered with it and
liked the parts I saw but feel some of his comments are misplaced.

EDIT: Obviously no one gets my point after twice explaining, that he's
complaining about badly written code by bad programmers as his reason for
dissing C, so I'll refrain from commenting anymore.

~~~
Hemospectrum
> That is a fault of the programmer, not C.

It's common to hear this line from the C fandom, and very rare to hear a
serious proposal for what to do with all this Bad Code written by Bad
Programmers. The status quo is to rewrite the former from scratch, and totally
ignore the latter in the hopes that they'll be magically pulled down to hell
and have their hands burned off by daemons. As far as I know, that part
doesn't actually happen. Instead, they just keep writing more Bad Code, and
meanwhile the rewrites are just as liable to cause problems as whatever they
replaced, because often as not, the people doing the rewrites also turn out to
be Bad Programmers.

If pressed for actionable solutions, C advocates will sometimes mention
tooling such as Valgrind and ASAN, as if merely having it exist guarantees
that all C programmers are educated in its use and make it a part of their
workflow. This clearly isn't the case. New programmers are still learning C
from material written before such tooling existed. Old programmers are still
recommending it. There's no path for Bad Programmers to become Good
Programmers, except to be admonished in public to Stop Writing Bad Code.

Ultimately, I agree with you: The issue here isn't with the C language. It's
with the people who use it. Their priorities do not include concerns like
memory safety, and their normal response to questions of code quality is to
make it somebody else's problem.

If the culture of the community were to change, I expect the language would
follow.

~~~
jejones3141
Yes, and even more so for C++ than C.

------
peterwwillis
> You don't have to fight dependencies. It just works when you cargo build.

Unless you're using caret requirements, or tilde requirements, or wildcard
requirements, or inequality requirements, or multiple version requirements, or
dependency overrides.

By default, Rust, much like every other language I know of, does not require
explicit versions or checksums for dependencies. This means it's up to the
developer to work really hard to make sure their code will actually work as
expected outside of their own build environment. This could be mostly avoided
if more people used explicit version requirements in their code, but nobody
seems to want to do this (which is strange, because your code is what needs
specific deps, so why not keep the version requirements right there?)

edit: For all the duplicate comments mentioning cargo.lock: The official FAQ
details how libraries do not impose requirements at build time
([https://doc.rust-lang.org/cargo/faq.html#why-do-binaries-
hav...](https://doc.rust-lang.org/cargo/faq.html#why-do-binaries-have-
cargolock-in-version-control-but-not-libraries)): _" Users dependent on the
library will not inspect the library’s Cargo.lock (even if it exists)"_. The
user cannot be certain their app will always work, because they can't be
certain that their dependent library has been tested on each other library
dependency. Basically, there's no certainty if you use libraries.

The other thing people are missing is how being able to call duplicate
conflicting versions of the same library where it is needed in your code would
allow you to work around complicated bugs in large projects automatically,
rather than having to find a magic combination of conflicting dep versions
that work for your app and its dependencies. Apparently nobody here has ever
tried to manage more than one OpenStack tools tree for use by the same
application.

~~~
Rusky
> call duplicate conflicting versions of the same library where it is needed
> in your code

Cargo already does this automatically if two of your dependencies depend on
conflicting versions of the same library.

And for what it's worth, the libraries on crates.io seem to follow semver
closely enough that the problem you describe doesn't really come up.

~~~
peterwwillis

      > > call duplicate conflicting versions of the same library where it is needed in your code
      > 
      > Cargo already does this automatically if two of your dependencies depend on conflicting versions of the same library.
    

Assuming this is the case, your code doesn't specify which version of what
function to use. So how could Cargo know which to use?

~~~
steveklabnik
To be clear, Cargo will attempt to unify versions as much as it can. If it
can't, you'll get multiple copies.

For example, let's say my project depends on two libraries; A and B. Both
depend on library C.

If A depends on C version ^ 1.0.0, and B depends on C version ^ 1.1.0, and
1.2.0 is the latest version of C, then both A and B will end up with C 1.2.0.

If A depends on C = 1.0.0, and B depends on C version = 1.1.0, then you'll get
two copies of C.

~~~
peterwwillis
And when _A_ calls something in _B_? For this example, let's bump the major
rev.

 _B_ is expecting _C = 2.0.0_. _A_ is expecting _C = 1.0.0_. _A_ makes a call
to _B_ , which it passes to _C_. Does _A_ know that _B_ now expects to be
called differently, since _C_ is also significantly different now? According
to the above requirements, no version of the deps exist that could allow the
app to execute correctly, even though it will technically build fine.

The way to resolve this would be to say _A_ requires _C < 2.0.0_, and simply
fail to build until someone fixes _A_. But you probably won't even know about
this conflict until someone tests the app with a specific feature of _A_ that
conflicts with _C >= 2.0.0_, the build fails, someone figures out the
conflict, and updates the requirements.

But you can only know this, and add this requirement, once you have found the
conflict. Thus code in the present, even with dependency requirements, may be
indeterminate in the future. (Unless you walk back through all calls in the
code to find calls between multiple dependencies that eventually land on
conflicts... but I don't think that is possible in Turing-complete languages)

If you pinned the version in each function call in each part of the code, you
could have the compiler or interpreter walk back through dependent code,
identify mismatches, retrieve a compatible dependency, and continue execution.
Or at the very least throw warnings all over the place when code is running
using dependencies it wasn't written for.

~~~
steveklabnik
You get a type error, which was actually funny, given that the message used to
say something like "expecting type T, got type T".

Funny enough, what you describe is theoretically possible in JavaScript, but
strangely enough, it seems to work... it's something I've long been trying to
come up with a POC exploit for.

