
Rust and Nix = easier Unix systems programming - kalmar
http://kamalmarhubi.com/blog/2016/04/13/rust-nix-easier-unix-systems-programming-3/
======
zuzun
I always find it a bit unfair when I see sloppy C programs used for shock
value. What if the Rust developer uses fork().unwrap_or(default_value) in a
hurry, or writes

    
    
        if let Some(child) = fork() {
            do_only_child_stuff(); 
        } else {
            do_only_parent_stuff();
        }
    

or

    
    
        if let Some(ForkResult::Child) = fork() {
            do_only_child_stuff();
        } else {
            do_only_parent_stuff();
        }
    

Now, if you're about to tell me that the examples above are totally stupid and
no developer would do such a thing, then you know how I feel about the sloppy
C versions. Doing a system call and not checking for error is totally stupid
as well.

By the way, you can also write your own wrapper functions in C, that transform
the return value into something like

    
    
        struct fork_status {
            enum { ERROR, PARENT, CHILD } state;
            int ret;
        };
    

Then Clang and GCC will warn you about missing switch cases.

That said, the libc bindings in Rust are pretty low-level and a project that
offers higher-level wrappers can be very helpful, so I hope my comment doesn't
create the impression that I'm ripping on the project itself.

~~~
masklinn
> What if the Rust developer uses fork().unwrap_or(default_value) in a hurry

The point here is that the language's tools and APIs can significantly better
drive the developer towards the safe/right solution, that's a large point of
type theory and static type systems after all. In this case rust's type system
is used to split out the various "result cases" and _notify the developer
upfront_ of the various cases to handle. The return type pretty much tells you
how the function will behave and what you need to take care of as the caller.

That aside, why would you unwrap_or(default_value) if you're in a hurry when
unwrap() is shorter (and you can later grep for "unwrap()" to find
dodgy/hurried code, whereas unwrap_or is a perfectly legitimate recovery
strategy).

> Now, if you're about to tell me that the examples above are totally stupid
> and no developer would do such a thing, then you know how I feel about the
> sloppy C versions. Doing a system call and not checking for error is totally
> stupid as well.

The issue being that even though you have a compiled statically typed language
it's of absolutely no help in "checking for error", and interactions between
syscalls can be hard to predict, not checking for fork(2)'s error isn't the
end of the world... until you pass its result to kill(2) for instance (it
might also give strange results if you pass specific pids to waidpid)

~~~
sickbeard
This has been proven to be wrong over and over again with the many variations
of languages that came after C. Newer "safer" languages don't improve bugs

~~~
nickpsecurity
On contrary, empirical studies done in the 90's and such by the likes of Mitre
Inc showed people using Ada and C++ were more productive than C developers
while producing way less defects. That the language and libraries were
designed specifically to counter hard-to-track issues were one of the reasons
why. That languages would improve safety was known far back as MULTICS with
PL/0 where it's prefixed strings and reverse stack prevented two of the most
common crashes/hacks in UNIX/C land.

The evidence is on our side that well-design language significantly reduces
number of defects in production code if other variables are equal.

------
geocar
I was very confused. I thought this had something to do with Rust and nix[1].

[1]: [https://nixos.org/nix/](https://nixos.org/nix/)

~~~
k__
Same here.

I thought "A match made in heaven"

But it will probably never happen, because Cargo is too good, haha

~~~
wizeman
What do you mean, it will never happen?

The nix package manager has (admittedly, undocumented) support for Rust /
Cargo projects.

------
subway
Neat library, way too already-overloaded name.

~~~
Ericson2314
Yeah as a Rust and NixOS fan, I was let down.

------
justincormack
I maintain LuaJIT syscall bindings
[https://github.com/justincormack/ljsyscall](https://github.com/justincormack/ljsyscall)
\- they cover quite a lot, namespaces, netlink and so on. I spent quite a bit
of time making them more intuitive than the raw bindings, with consistent
error handling, also namespacing constants and so on. It is definitely useful
to have these types of interfaces not in C.

~~~
kalmar
This project looks really cool! I'm very curious to find out more about how
you make sure constants are correct across platforms and architectures. I will
be poking around!

~~~
justincormack
There are a bunch of tests but the whole Linux ABI spec is a mess.

------
bigger_cheese
Minor nit pick but don't you typically do something like this in C

pid_t childPid;

switch (childPid = fork()) {

case -1: ... / _error handling_ /;

case 0: ... / _Child Specific_ /

default: sleep (5); }

edit - seems to mangle formatting but something like that seems fairly clean.

~~~
laughinghan
You're missing the point in actually a really important way.

Nobody is claiming that C makes it impossible to cleanly do the right
thing—obviously the whole world runs on C.

The point is that nothing about the C language, libraries, or toolchain
_discourage_ the example given in the blogpost compared to your more correct
code. Unless you remember exactly the right details from the manpages, there's
nothing about the example in the blogpost that's less natural to write than
your more correct code. (And people do forget those details:
[http://rachelbythebay.com/w/2014/08/19/fork/](http://rachelbythebay.com/w/2014/08/19/fork/)
)

By contrast, as illustrated in the blogpost, the most natural way to do the
same thing in Rust turns out to be the more correct thing. If you wanted the
bad behavior to happen, you'd have to go out of your way to pass -1 to kill().
Hence in this example, Rust's design is an improvement.

It's great that C gives you enough rope to hang yourself with, but it's even
better if tying yourself to things safely is easy, and to hang yourself you
have to really go out of your way.

------
sergiolp
I can't help but think they're trying to fix something that isn't broken at
all.

Adding new abstraction layers rarely helps when doing systems programming. You
(as in "the developer") want to be as near to the machine as possible. C does
this pretty well.

Perhaps I'm just getting old :-(

~~~
draven
In this case it seems like a very thin wrapper that leverages the type system
to allow catching a whole class of errors at compile time, like using
exhaustiveness checks to make sure a function call handles all possible return
values. I think the small overhead is well worth it.

The original API is not "broken" per se, it's just limited by the language
features ("magical" return values vs. tagged unions or whatever they're called
in Rust, I don't remember.)

~~~
wtallis
It's not even clear to me that there's any overhead to the Rust version.
Checking error codes that should be checked isn't overhead. Checking them
inefficiently would be overhead, but the Rust version looks like it should
compile down to something pretty similar to what the equivalent C switch
blocks would produce.

~~~
masklinn
> It's not even clear to me that there's any overhead to the Rust version.

There is a slight bit of stack overhead: Option<ForkResult> is at least
{tag:u8, {tag:u8, pid:i32}}, and due to alignment constraints it's actually
{tag: u32, {tag: u32, pid: i32 }}). A nonzero wrapper[0] would allow folding
either ForkResult or Option into a 0-valued pid_t and remove one level of
tagging: [http://is.gd/yxStW1](http://is.gd/yxStW1)

Beyond that you'd need generalised enum folding in order to fold _two_ tags
into the underlying value (you'd denote that pid_t is nonzero and nonnegative
for instance)

[0] which is unstable, so not really an option

~~~
arielb1
We _do_ have a planned optimization that would fold the tags for cases like
`Option<ForkResult>` to give a word pair, which should be returned in
%eax:%edx (or %rax:%rdx).

~~~
Jweb_Guru
Really? That's exciting. Missed enum layout optimizations are one of my few
issues with Rust right now.

------
superobserver
This gets me thinking how awesome it would be to have functional programming
on *nix systems, like Haskell (specifically). At least then it might be
forcibly designed to be made more useful and ultimately get more people on
board. One can dream.

~~~
GhotiFish
Kinda like turtle!
[https://hackage.haskell.org/package/turtle](https://hackage.haskell.org/package/turtle)

Oh by the way, I accidentally hit downvote on your post and HN doesn't let me
undo that action... I was just trying to hide it! Sorry!

~~~
superobserver
Hey, thanks! Hadn't heard of turtle before, I don't believe. Time to install
it on my crouton chroot and see what it can do. :)

------
SixSigma
In reliability theory "X failed" is a poor error message. What we want to know
is which failure mode has been triggered.

The function of kill is to kill a given pid, so there are two failure modes :
"the pid didn't exist" or "the pid didn't die"

~~~
Sean1708
"kill failed" isn't actually the message which is printed. What you get is

    
    
        thread '<main>' panicked at 'kill failed: Sys(ESRCH)', ../src/libcore/result.rs:746
    

So you know that it failed because of ESRCH (no such process).

~~~
SixSigma
Ah, that makes much more sense, thanks

------
bogomipz
The term NIX is becoming a bit overloaded - we've got the Nix package manger
which run on NixOS, Nix the Rust library all of which can run on most 'Nix
systems.

------
ZephyrP
I feel there are more promising options for a name than "Nix".

------
etrain
type systems are great.

------
larozin
We have switched to Nix as internal dependency manager for our C++ project. It
is really exciting! No more "after commit XXX you need to (re)build/update YYY
with ZZZ". Developers just type `nix-shell` and get sane guaranted to work
environment on their local machines corresponding to git HEAD. If we need to
add or patch dependency we just edit and commit nix file. And if developer
need to rollback to old commit/branch it will get old/custom environment from
cache without submodule rebuilds.

~~~
comex
That's pretty cool... but has nothing to do with the different project named
"nix" discussed in the post.

------
peterwwillis
_" the return value is conveying three different things all at once. [...]
That’s a lot of information for one poor little pid_t—usually a 32-bit
integer—to convey!"_

Someone never had to bit-pack their programs to save memory, disk space, or
bandwidth. In fact, it's a huge waste of memory; if you only need 3 bits, a
'char' would have sufficed. Saves 24 bits!

Of course, we could use nibbles to make data structures where the fork return
value only takes up 3 bits instead of a whole byte, but that could be
considered micro-optimizing. (the compiler may do this for us anyway, though)

~~~
Rusky
The return value is going to stay in a register the whole time anyway, so a
char won't save you anything.

But regardless, the point of that sentence is nothing to do with memory usage,
but with semantics. Whether you or the compiler packs all the information into
3 bits or 3 words, that's fine, as long as the language helps you distinguish
the parts.

